From df68febc1b784085a320d5109db0ab0ed1059790 Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 16:20:35 -0500 Subject: [PATCH 01/13] fixed starting inventory still being added to itempool --- worlds/blueprince/items.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/worlds/blueprince/items.py b/worlds/blueprince/items.py index 51ba355ab899..7bf1a76810c6 100644 --- a/worlds/blueprince/items.py +++ b/worlds/blueprince/items.py @@ -389,6 +389,8 @@ def create_all_items(world: BluePrinceWorld) -> None: itempool: list[Item] = [] to_precollect: list[Item] = [] + exclude = [item for item in world.multiworld.precollected_items[world.player]] + standard_item_list = [world.create_item(k) for k in other_items] if world.options.standard_item_sanity: itempool += standard_item_list @@ -426,7 +428,17 @@ def create_all_items(world: BluePrinceWorld) -> None: # Precollects all room items, except for those that should be at their in-game locations, which are handled in locations.py to_precollect += [room for room in room_item_list if NONSANITY_LOCATION_KEY not in rooms[room.name] or rooms[room.name][NONSANITY_LOCATION_KEY] == STARTING_INVENTORY] - [world.push_precollected(item) for item in to_precollect] + # remove any items that are in starting inventory + for item in to_precollect: + if item in exclude: + exclude.remove(item) + else: + world.push_precollected(item) + + for item in itempool.copy(): + if item in exclude: + exclude.remove(item) + itempool.remove(item) # # Add Filler Stuff From eef1839e134fbd8348ca020c4b77e671745cafae Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 16:49:43 -0500 Subject: [PATCH 02/13] Added item/location groups --- worlds/blueprince/data_items.py | 12 +++- worlds/blueprince/data_other_locations.py | 69 +++++++++++++++-------- worlds/blueprince/data_rooms.py | 10 ++++ worlds/blueprince/locations.py | 14 +++-- worlds/blueprince/world.py | 4 ++ 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/worlds/blueprince/data_items.py b/worlds/blueprince/data_items.py index 6debe3c5304f..8259054e4291 100644 --- a/worlds/blueprince/data_items.py +++ b/worlds/blueprince/data_items.py @@ -612,4 +612,14 @@ ], } -# None of the Tier 5 items can be received, so there's no point in defining it atm \ No newline at end of file +# None of the Tier 5 items can be received, so there's no point in defining it atm + +ITEMS_BY_GROUPS = { + "Upgrade Disks": [disk for disk in upgrade_disks], + "Sanctum Keys": [key for key in sanctum_keys], + "Keys": [key for key in keys], + "Showroom Items": [item for item in showroom_items], + "Armory Items": [item for item in armory_items], + "Workshop Items": [item for item in workshop_items], + "Standard Items": [item for item in other_items] +} \ No newline at end of file diff --git a/worlds/blueprince/data_other_locations.py b/worlds/blueprince/data_other_locations.py index 042f23234c64..3dda55d9a516 100644 --- a/worlds/blueprince/data_other_locations.py +++ b/worlds/blueprince/data_other_locations.py @@ -1,5 +1,6 @@ from BaseClasses import CollectionState, ItemClassification +from .locations import LOCATIONS_BY_GROUPS from .constants import * from .data_rooms import rooms, core_rooms, classrooms, room_layout_lists from .data_items import * @@ -200,84 +201,84 @@ def can_reach_item_location(item_name: str, state: CollectionState, player: int) } } -aries_court_mora_jia_boxes = { - f"Aries Court Mora Jia Box {n}": { +aries_court_mora_jai_boxes = { + f"Aries Court Mora Jai Box {n}": { LOCATION_ID_KEY: get_room_location_id("Aries Court", n), LOCATION_ROOM_KEY: "Aries Court", } for n in range(1, 9) } -mora_jia_boxes = { - "Master Bedroom Mora Jia Box": { +mora_jai_boxes = { + "Master Bedroom Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Master Bedroom", 0), LOCATION_ROOM_KEY: "Master Bedroom", }, - "Closed Exhibit Mora Jia Box": { + "Closed Exhibit Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Closed Exhibit", 0), LOCATION_ROOM_KEY: "Closed Exhibit", }, - "Underpass Mora Jia Box": { + "Underpass Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("The Underpass", 1), LOCATION_ROOM_KEY: "The Underpass", }, - "Tomb Mora Jia Box": { + "Tomb Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Tomb", 0), LOCATION_ROOM_KEY: "Tomb", }, - "Trading Post Mora Jia Box": { + "Trading Post Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Trading Post", 0), LOCATION_ROOM_KEY: "Trading Post", }, - "Tunnel Mora Jia Box": { + "Tunnel Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Tunnel", 0), LOCATION_ROOM_KEY: "Tunnel", }, - "Solarium Mora Jia Box": { + "Solarium Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Solarium", 0), LOCATION_ROOM_KEY: "Solarium", }, - "Lost And Found Mora Jia Box": { + "Lost And Found Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Lost And Found", 1), LOCATION_ROOM_KEY: "Lost And Found", }, - "Throne of the Blue Prince Mora Jia Box": { + "Throne of the Blue Prince Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Throne Room", 0), LOCATION_ROOM_KEY: "Throne Room", LOCATION_RULE_SIMPLE_COMMON: lambda state, world: state.has("Ascend The Throne", world.player) }, - "Arch Aries Sanctum Mora Jia Box": { + "Arch Aries Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Arch Aries Sanctum", 0), LOCATION_ROOM_KEY: "Arch Aries Sanctum", }, - "Corarica Sanctum Mora Jia Box": { + "Corarica Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Corarica Sanctum", 0), LOCATION_ROOM_KEY: "Corarica Sanctum", }, - "Eraja Sanctum Mora Jia Box": { + "Eraja Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Eraja Sanctum", 0), LOCATION_ROOM_KEY: "Eraja Sanctum", }, - "Fenn Aries Sanctum Mora Jia Box": { + "Fenn Aries Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Fenn Aries Sanctum", 0), LOCATION_ROOM_KEY: "Fenn Aries Sanctum", }, - "Mora Jai Sanctum Mora Jia Box": { + "Mora Jai Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Mora Jai Sanctum", 0), LOCATION_ROOM_KEY: "Mora Jai Sanctum", }, - "Orinda Aries Sanctum Mora Jia Box": { + "Orinda Aries Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Orinda Aries Sanctum", 0), LOCATION_ROOM_KEY: "Orinda Aries Sanctum", }, - "Verra Sanctum Mora Jia Box": { + "Verra Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Verra Sanctum", 0), LOCATION_ROOM_KEY: "Verra Sanctum", }, - "Nuance Sanctum Mora Jia Box": { + "Nuance Sanctum Mora Jai Box": { LOCATION_ID_KEY: get_room_location_id("Nuance Sanctum", 0), LOCATION_ROOM_KEY: "Nuance Sanctum", } -} | aries_court_mora_jia_boxes +} | aries_court_mora_jai_boxes # not adding atelier boxes, since they are basically already at the latest goal drafting_studio_additions = { @@ -1592,5 +1593,27 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: # Deposit Box 053? (The one opened with Key 8) -locations = trophies | safes_and_small_gates | mora_jia_boxes | floorplans | shop_items | upgrade_disks | keys | misc_locations | item_pickups | workshop_contraptions - \ No newline at end of file +# Basement to Reservior door button (Basement puzzle) + +# West gate unlock + +# Swansong/SwansongHSS + Blackbridge passwords + +# Foundation Elevator controls + +# Tomb puzzle 1 & 2 (Catacombs and Book Drafts) + +locations = trophies | safes_and_small_gates | mora_jai_boxes | floorplans | shop_items | upgrade_disks | keys | misc_locations | item_pickups | workshop_contraptions + +LOCATIONS_BY_GROUPS |= { + "Trophies": [k for k in trophies], + "Safes and Small Gates": [k for k in safes_and_small_gates], + "Mora Jai Boxes": [k for k in mora_jai_boxes], + "Floorplans": [k for k in floorplans], + "Shop Items": [k for k in shop_items], + "Upgrade Disks": [k for k in upgrade_disks], + "Keys": [k for k in keys], + "Miscellaneous": [k for k in misc_locations], + "Item Pickups": [k for k in item_pickups], + "Workshop Contraptions": [k for k in workshop_contraptions], +} \ No newline at end of file diff --git a/worlds/blueprince/data_rooms.py b/worlds/blueprince/data_rooms.py index 7939c9dcee9f..e5354e9959df 100644 --- a/worlds/blueprince/data_rooms.py +++ b/worlds/blueprince/data_rooms.py @@ -1837,4 +1837,14 @@ ROOM_LAYOUT_TYPE_I: [k for k, v in rooms.items() if v[ROOM_LAYOUT_TYPE_KEY] == ROOM_LAYOUT_TYPE_I], INNER_ROOM_KEY: [k for k, v in rooms.items() if not v[OUTER_ROOM_KEY]], OUTER_ROOM_KEY: [k for k, v in rooms.items() if v[OUTER_ROOM_KEY]], +} + +ROOMS_BY_GROUPS = { + "Blue Rooms": [room for room in blue_rooms], + "Bedrooms": [room for room in bedrooms], + "Hallways": [room for room in hallways], + "Green Rooms": [room for room in green_rooms], + "Shops": [room for room in shops], + "Red Rooms": [room for room in red_rooms], + "Black Rooms": [room for room in black_rooms], } \ No newline at end of file diff --git a/worlds/blueprince/locations.py b/worlds/blueprince/locations.py index dca794087840..c10aab2404ae 100644 --- a/worlds/blueprince/locations.py +++ b/worlds/blueprince/locations.py @@ -54,18 +54,21 @@ def create_all_locations(world: BluePrinceWorld) -> None: create_regular_locations(world) create_events(world) - +LOCATIONS_BY_GROUPS : dict[str, list[str]] = {} def create_regular_locations(world: BluePrinceWorld) -> None: armory = world.get_region("The Armory") - + LOCATIONS_BY_GROUPS["Armory Purchases"] = [] # Ignoring chance to get Knight's Shield by digging with Jack Hammer for now. for k, v in armory_items.items(): location_key = f"{k} First Pickup" locs = get_location_names_with_ids([location_key]) armory.add_locations(locs, BluePrinceLocation) + LOCATIONS_BY_GROUPS["Armory Purchases"].append(location_key) + LOCATIONS_BY_GROUPS["Room Entrances"] = [] + LOCATIONS_BY_GROUPS["Trunks"] = [] for room_key, v in rooms.items(): room = world.get_region(room_key) @@ -73,17 +76,20 @@ def create_regular_locations(world: BluePrinceWorld) -> None: location_key = f"{room_key} First Entering" locs = get_location_names_with_ids([location_key]) room.add_locations(locs, BluePrinceLocation) + LOCATIONS_BY_GROUPS["Room Entrances"].append(location_key) # Add Nth locked trunk open - locs = get_location_names_with_ids([f"{room_key} Locked Trunk {idx}" for idx in range(1, world.options.locked_trunks + 1) if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0]) + trunks = [f"{room_key} Locked Trunk {idx}" for idx in range(1, world.options.locked_trunks + 1) if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0] + locs = get_location_names_with_ids(trunks) room.add_locations(locs, BluePrinceLocation) + LOCATIONS_BY_GROUPS["Trunks"].extend(trunks) + # These trunks require extra logic if room_key == "Entrance Hall": # TODO: switch to using set_rule once 0.6.7 is released. for idx in range(1, world.options.locked_trunks + 1): world.get_location(f"Entrance Hall Locked Trunk {idx}").access_rule = lambda state: state.can_reach_region("Observatory", world.player) or state.can_reach_region("Laboratory", world.player) - for k, v in locations.items(): if NONSANITY_LOCATION_KEY in v and world.options.room_draft_sanity == False: if v[NONSANITY_LOCATION_KEY] != STARTING_INVENTORY: diff --git a/worlds/blueprince/world.py b/worlds/blueprince/world.py index 3a7fe4afe45f..8771dbd51d02 100644 --- a/worlds/blueprince/world.py +++ b/worlds/blueprince/world.py @@ -4,6 +4,8 @@ # Imports of base Archipelago modules must be absolute. from worlds.AutoWorld import World +from .data_items import ITEMS_BY_GROUPS +from .data_rooms import ROOMS_BY_GROUPS # Imports of your world's files must be relative. from . import items, locations, regions, rules, web_world from . import ( @@ -36,6 +38,8 @@ class BluePrinceWorld(World): # Technically, Simon starts in the Entrance Hall, but for lore reasons, Starting at The Campsite is also acceptable, and is not a "room" origin_region_name = "Campsite" + item_name_groups = ITEMS_BY_GROUPS | ROOMS_BY_GROUPS + # # Our world class must have certain functions ("steps") that get called during generation. # # The main ones are: create_regions, set_rules, create_items. # # For better structure and readability, we put each of these in their own file. From d320bda137c437ddf10a803dd99e19a50dab9127 Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 16:57:31 -0500 Subject: [PATCH 03/13] Fixed groups import --- worlds/blueprince/constants.py | 10 +++++++++- worlds/blueprince/data_other_locations.py | 2 +- worlds/blueprince/data_rooms.py | 5 ++--- worlds/blueprince/locations.py | 2 -- worlds/blueprince/world.py | 5 ++--- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/worlds/blueprince/constants.py b/worlds/blueprince/constants.py index b480bc5678e9..e45eb58d1955 100644 --- a/worlds/blueprince/constants.py +++ b/worlds/blueprince/constants.py @@ -168,4 +168,12 @@ ################################# TRADING_POST_GIVE = "GIVE" -TRADING_POST_RECEIVE = "RECEIVE" \ No newline at end of file +TRADING_POST_RECEIVE = "RECEIVE" + + +######################## +# Item/Location GROUPS # +######################## + +ITEMS_BY_GROUPS : dict[str, list[str]] = {} +LOCATIONS_BY_GROUPS : dict[str, list[str]] = {} \ No newline at end of file diff --git a/worlds/blueprince/data_other_locations.py b/worlds/blueprince/data_other_locations.py index 3dda55d9a516..9d87cb891898 100644 --- a/worlds/blueprince/data_other_locations.py +++ b/worlds/blueprince/data_other_locations.py @@ -1,9 +1,9 @@ from BaseClasses import CollectionState, ItemClassification -from .locations import LOCATIONS_BY_GROUPS from .constants import * from .data_rooms import rooms, core_rooms, classrooms, room_layout_lists from .data_items import * +# from .world import LOCATIONS_BY_GROUPS room_location_mem : dict[str, list[int]] = {} diff --git a/worlds/blueprince/data_rooms.py b/worlds/blueprince/data_rooms.py index e5354e9959df..6780b8f9b3d8 100644 --- a/worlds/blueprince/data_rooms.py +++ b/worlds/blueprince/data_rooms.py @@ -1,8 +1,7 @@ from BaseClasses import ItemClassification - from .constants import * - +from .world import ITEMS_BY_GROUPS core_rooms = { "Antechamber": { @@ -1839,7 +1838,7 @@ OUTER_ROOM_KEY: [k for k, v in rooms.items() if v[OUTER_ROOM_KEY]], } -ROOMS_BY_GROUPS = { +ITEMS_BY_GROUPS |= { "Blue Rooms": [room for room in blue_rooms], "Bedrooms": [room for room in bedrooms], "Hallways": [room for room in hallways], diff --git a/worlds/blueprince/locations.py b/worlds/blueprince/locations.py index c10aab2404ae..17212fb84e2d 100644 --- a/worlds/blueprince/locations.py +++ b/worlds/blueprince/locations.py @@ -54,8 +54,6 @@ def create_all_locations(world: BluePrinceWorld) -> None: create_regular_locations(world) create_events(world) -LOCATIONS_BY_GROUPS : dict[str, list[str]] = {} - def create_regular_locations(world: BluePrinceWorld) -> None: armory = world.get_region("The Armory") diff --git a/worlds/blueprince/world.py b/worlds/blueprince/world.py index 8771dbd51d02..e19d8f30bb5b 100644 --- a/worlds/blueprince/world.py +++ b/worlds/blueprince/world.py @@ -5,7 +5,6 @@ from worlds.AutoWorld import World from .data_items import ITEMS_BY_GROUPS -from .data_rooms import ROOMS_BY_GROUPS # Imports of your world's files must be relative. from . import items, locations, regions, rules, web_world from . import ( @@ -38,7 +37,7 @@ class BluePrinceWorld(World): # Technically, Simon starts in the Entrance Hall, but for lore reasons, Starting at The Campsite is also acceptable, and is not a "room" origin_region_name = "Campsite" - item_name_groups = ITEMS_BY_GROUPS | ROOMS_BY_GROUPS + item_name_groups = ITEMS_BY_GROUPS # # Our world class must have certain functions ("steps") that get called during generation. # # The main ones are: create_regions, set_rules, create_items. @@ -66,4 +65,4 @@ def get_filler_item_name(self) -> str: # # If you need access to the player's chosen options on the client side, there is a helper for that. # return self.options.as_dict( # "hard_mode", "hammer", "extra_starting_chest", "confetti_explosiveness", "player_sprite" - # ) + # ) \ No newline at end of file From 482c340e59e1752eb74148f7a9b1f2a41463fffa Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 16:59:27 -0500 Subject: [PATCH 04/13] e --- worlds/blueprince/data_other_locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/blueprince/data_other_locations.py b/worlds/blueprince/data_other_locations.py index 9d87cb891898..eea65fab40f3 100644 --- a/worlds/blueprince/data_other_locations.py +++ b/worlds/blueprince/data_other_locations.py @@ -1328,7 +1328,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: } upgrade_disks = { - "Upgrade Disk - Offic": { + "Upgrade Disk - Office": { LOCATION_ID_KEY: get_room_location_id("Office", 1), LOCATION_ROOM_KEY: "Office", }, From 67ebe84bf30377d78777d28ab6b0e13be3eb0a6c Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 17:07:29 -0500 Subject: [PATCH 05/13] Made Classrooms a progressive item --- worlds/blueprince/regions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/worlds/blueprince/regions.py b/worlds/blueprince/regions.py index 15f03253d0a9..b3be60b8fa8f 100644 --- a/worlds/blueprince/regions.py +++ b/worlds/blueprince/regions.py @@ -227,12 +227,14 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: elif k in classrooms and k != "Classroom 1": if k == "Classroom Exam": prev = "Classroom 8" + cnum = 9 else: prev = f"Classroom {int(k[-1]) - 1}" + cnum = int(k[-1]) world.get_region(prev).connect( room, f"{prev} {k}", - lambda state: state.has(k, world.player), + lambda state: state.count_from_list_unique(classrooms, world.player) >= cnum, ) # TODO: Add Her Ladyship's Chamber, it has weird requirements From 810ebe1f86a5c431c85bebea23f6126848e24488 Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 19:12:19 -0500 Subject: [PATCH 06/13] split tests --- worlds/blueprince/test/test_items.py | 24 +++++++++++++++++++ .../blueprince/test/test_location_access.py | 7 ------ 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 worlds/blueprince/test/test_items.py diff --git a/worlds/blueprince/test/test_items.py b/worlds/blueprince/test/test_items.py new file mode 100644 index 000000000000..ad88e3cec3c0 --- /dev/null +++ b/worlds/blueprince/test/test_items.py @@ -0,0 +1,24 @@ +from BaseClasses import CollectionState, Location +from ..options import GoalType +from ..test import BluePrinceTestBase +from ..data_rooms import rooms, core_rooms +from ..constants import * +from ..locations import LOCATION_NAME_TO_ID +from ..items import ITEM_NAME_TO_ID + +class TestLocationAccess(BluePrinceTestBase): + options = { + "room_draft_sanity": True, + "goal_type": GoalType.option_room46, + } + + def test_all_item_ids_unique(self): + mem = dict() + for name, id in ITEM_NAME_TO_ID.items(): + if id in mem: + self.fail(f"Duplicate item ID {id} for {name} and {mem[id]}") + mem[id] = name + + def test_item_groups(self): + for name, group in self.world.item_name_groups.items(): + print(f"Group {name} contains: {group}\n") \ No newline at end of file diff --git a/worlds/blueprince/test/test_location_access.py b/worlds/blueprince/test/test_location_access.py index 579d34f137f9..2a6ae17483be 100644 --- a/worlds/blueprince/test/test_location_access.py +++ b/worlds/blueprince/test/test_location_access.py @@ -19,13 +19,6 @@ def test_all_location_ids_unique(self): self.fail(f"Duplicate location ID {id} for {name} and {mem[id]}") mem[id] = name - def test_all_item_ids_unique(self): - mem = dict() - for name, id in ITEM_NAME_TO_ID.items(): - if id in mem: - self.fail(f"Duplicate item ID {id} for {name} and {mem[id]}") - mem[id] = name - def test_can_reach_tunnel_floorplan_after_crates(self): self.collect_by_name(["Laboratory", "Boiler Room", "Parlor", "MICROCHIP 1", "MICROCHIP 2", "MICROCHIP 3", "Garage", "Hovel", "Utility Closet", "Schoolhouse", "SHOVEL", "SLEDGE HAMMER", "Workshop", "MAGNIFYING GLASS", "METAL DETECTOR", "Library", "Burning Glass"]) self.debug_print_regions_items_locations(True) From 5050a2a7229bf25797edbff2e438b3ea53bcdf2e Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 19:19:34 -0500 Subject: [PATCH 07/13] Github pytest workflow --- .github/workflows/pytest.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 000000000000..5d112f8c0bbf --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,31 @@ +name: Run Pytest + +on: + pull_request: + branches: + - main # change if your default branch is different + paths: + - 'worlds/blueprince/**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" # match your project version + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install pytest + + - name: Run pytest + run: | + pytest worlds/blueprince/test \ No newline at end of file From 9d06a572c1a5ac8c9bbfdd1e24de351914256b79 Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 19:31:43 -0500 Subject: [PATCH 08/13] maybe fixed workflow --- .github/workflows/pytest.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 5d112f8c0bbf..09181d6f0a36 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -2,6 +2,10 @@ name: Run Pytest on: pull_request: + types: + - opened + - synchronize + - reopened branches: - main # change if your default branch is different paths: @@ -11,6 +15,9 @@ jobs: test: runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: - name: Checkout repo uses: actions/checkout@v4 From a27bfca6e9b190c9b611fa2f26a7ab75f69e04c7 Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 19:39:20 -0500 Subject: [PATCH 09/13] Removed unnessecary Access events --- worlds/blueprince/locations.py | 26 -------------------------- worlds/blueprince/regions.py | 10 +--------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/worlds/blueprince/locations.py b/worlds/blueprince/locations.py index 17212fb84e2d..a15d4f82db2c 100644 --- a/worlds/blueprince/locations.py +++ b/worlds/blueprince/locations.py @@ -231,32 +231,6 @@ def create_events(world: BluePrinceWorld) -> None: item_type=items.BluePrinceItem, ) - # Set access events for the 4 blue flames. - world.get_region("Apple Orchard").add_event( - "Has Apple Orchard Access", - "Apple Orchard Access", - location_type=BluePrinceLocation, - item_type=items.BluePrinceItem, - ) - world.get_region("Schoolhouse").add_event( - "Has School House Access", - "School House Access", - location_type=BluePrinceLocation, - item_type=items.BluePrinceItem, - ) - world.get_region("Hovel").add_event( - "Has Hovel Access", - "Hovel Access", - location_type=BluePrinceLocation, - item_type=items.BluePrinceItem, - ) - world.get_region("Gemstone Cavern").add_event( - "Has Gemstone Cavern Access", - "Gemstone Cavern Access", - location_type=BluePrinceLocation, - item_type=items.BluePrinceItem, - ) - # Set North Lever Access world.get_region("Inner Sanctum").add_event( "Inner Sanctum North Lever", diff --git a/worlds/blueprince/regions.py b/worlds/blueprince/regions.py index b3be60b8fa8f..d81ad77593ad 100644 --- a/worlds/blueprince/regions.py +++ b/worlds/blueprince/regions.py @@ -280,15 +280,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: grounds.connect( the_precipice, "Grounds To Precipice", - lambda state: state.has_all( - { - "Apple Orchard Access", - "School House Access", - "Hovel Access", - "Gemstone Cavern Access", - }, - world.player, - ), + lambda state: all([state.can_reach_region(x, world.player) for x in ["Apple Orchard", "Schoolhouse", "Hovel", "Gemstone Cavern"]]), ) grounds.connect( sealed_entrance, From f52ec4a9a1c206e21ba126c84f9c2dc39484d70f Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 3 Mar 2026 20:12:03 -0500 Subject: [PATCH 10/13] split trunks into 3 seperate options --- worlds/blueprince/constants.py | 6 +++ worlds/blueprince/data_items.py | 74 ++++++++++++++++----------------- worlds/blueprince/data_rooms.py | 44 ++++++++++++-------- worlds/blueprince/locations.py | 11 ++++- worlds/blueprince/options.py | 42 ++++++++++++++++--- 5 files changed, 115 insertions(+), 62 deletions(-) diff --git a/worlds/blueprince/constants.py b/worlds/blueprince/constants.py index e45eb58d1955..60353849b1bd 100644 --- a/worlds/blueprince/constants.py +++ b/worlds/blueprince/constants.py @@ -24,6 +24,8 @@ ROOM_PICK_POSITIONS_KEY = "room_picker_positions" # Defines the key for setting the data in a room's info-dict. Used specifically to define how many spots the room can place an item like a shovel. ROOM_ITEM_SPOT_COUNT_KEY = "item_spot_count" +# Defines which trunk spawn setting a room should be affected by. +ROOM_CHEST_SPOT_TYPE_KEY = "chest_spot_type" # Defines the key for setting the data in a room's info-dict. Used specifically to define how many chest spots the room can have. ROOM_CHEST_SPOT_COUNT_KEY = "chest_spot_count" # defines the key for setting the data in a room's info-dict. Used specifically to define which chess piece is in a room @@ -42,6 +44,10 @@ ROOM_MULTIPLIER = 100_000 +ROOM_CHEST_SPOT_COMMON = "common" +ROOM_CHEST_SPOT_RARE = "rare" +ROOM_CHEST_SPOT_COMPLEX = "complex" + # One of the room layout types. Specifically, for dead-end rooms ROOM_LAYOUT_TYPE_D = "room_layout_type_d" # One of the room layout types. Specifically, for rooms with 3 entrances diff --git a/worlds/blueprince/data_items.py b/worlds/blueprince/data_items.py index 8259054e4291..1e87e2cf7ec5 100644 --- a/worlds/blueprince/data_items.py +++ b/worlds/blueprince/data_items.py @@ -132,17 +132,17 @@ "CAR KEYS": { ITEM_ELEMENT_INDEX_KEY: 3, ITEM_ID_KEY: 10003, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "KEYCARD": { ITEM_ELEMENT_INDEX_KEY: 10, ITEM_ID_KEY: 10010, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "SILVER KEY": { ITEM_ELEMENT_INDEX_KEY: 19, ITEM_ID_KEY: 10019, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "KEY 8": { ITEM_ELEMENT_INDEX_KEY: 27, @@ -198,7 +198,7 @@ # Known exception to formatting. Keeping consistent with game format. ITEM_ELEMENT_INDEX_KEY: 50, ITEM_ID_KEY: 10050, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "KEY of Aries": { # Known exception to formatting. Keeping consistent with game format. @@ -209,7 +209,7 @@ "SECRET GARDEN KEY": { ITEM_ELEMENT_INDEX_KEY: 17, ITEM_ID_KEY: 10017, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "MICROCHIP 1": { ITEM_ELEMENT_INDEX_KEY: 39, @@ -242,7 +242,7 @@ "MASTER KEY": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2003, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "MOON PENDANT": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, @@ -264,22 +264,22 @@ "MORNING STAR": { ITEM_ELEMENT_INDEX_KEY: 35, ITEM_ID_KEY: 1035, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "THE AXE": { ITEM_ELEMENT_INDEX_KEY: 36, ITEM_ID_KEY: 1036, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "TORCH": { ITEM_ELEMENT_INDEX_KEY: 37, ITEM_ID_KEY: 1037, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "KNIGHTS SHIELD": { ITEM_ELEMENT_INDEX_KEY: 38, ITEM_ID_KEY: 1038, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, } @@ -287,82 +287,82 @@ "BATTERY PACK": { ITEM_ELEMENT_INDEX_KEY: 1, ITEM_ID_KEY: 1001, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "BROKEN LEVER": { ITEM_ELEMENT_INDEX_KEY: 2, ITEM_ID_KEY: 1002, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "MAGNIFYING GLASS": { ITEM_ELEMENT_INDEX_KEY: 13, ITEM_ID_KEY: 1013, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "METAL DETECTOR": { ITEM_ELEMENT_INDEX_KEY: 14, ITEM_ID_KEY: 1014, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "SHOVEL": { ITEM_ELEMENT_INDEX_KEY: 18, ITEM_ID_KEY: 1018, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "SLEDGE HAMMER": { ITEM_ELEMENT_INDEX_KEY: 20, ITEM_ID_KEY: 1020, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "TELESCOPE": { ITEM_ELEMENT_INDEX_KEY: 34, ITEM_ID_KEY: 1034, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "RUNNING SHOES": { ITEM_ELEMENT_INDEX_KEY: 15, ITEM_ID_KEY: 1015, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "SALT SHAKER": { ITEM_ELEMENT_INDEX_KEY: 16, ITEM_ID_KEY: 1016, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "SLEEPING MASK": { ITEM_ELEMENT_INDEX_KEY: 21, ITEM_ID_KEY: 1021, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "COIN PURSE": { ITEM_ELEMENT_INDEX_KEY: 4, ITEM_ID_KEY: 1004, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "COUPON BOOK": { ITEM_ELEMENT_INDEX_KEY: 6, ITEM_ID_KEY: 1006, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "LOCK PICK KIT": { ITEM_ELEMENT_INDEX_KEY: 11, ITEM_ID_KEY: 1011, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "LUCKY RABBIT'S FOOT": { ITEM_ELEMENT_INDEX_KEY: 12, ITEM_ID_KEY: 1012, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "TREASURE MAP": { ITEM_ELEMENT_INDEX_KEY: 26, ITEM_ID_KEY: 1026, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "STOPWATCH": { ITEM_ELEMENT_INDEX_KEY: 29, ITEM_ID_KEY: 1029, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "REPELLENT": { ITEM_ELEMENT_INDEX_KEY: 30, @@ -372,12 +372,12 @@ "WATERING CAN": { ITEM_ELEMENT_INDEX_KEY: 31, ITEM_ID_KEY: 1031, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, "HALL PASS": { ITEM_ELEMENT_INDEX_KEY: 32, ITEM_ID_KEY: 1032, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "LUNCH BOX": { ITEM_ELEMENT_INDEX_KEY: 43, @@ -407,12 +407,12 @@ "GEAR WRENCH": { ITEM_ELEMENT_INDEX_KEY: 33, ITEM_ID_KEY: 1033, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "COMPASS": { ITEM_ELEMENT_INDEX_KEY: 5, ITEM_ID_KEY: 1005, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, } @@ -425,37 +425,37 @@ "Detector Shovel": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3002, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "Dowsing Rod": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3003, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "Electromagnet": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3004, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "Jack Hammer": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3005, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized | ItemClassification.useful, }, "Lucky Purse": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3006, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized | ItemClassification.useful, }, "Pick Sound Amplifier": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3007, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, }, "Power Hammer": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3008, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, }, } diff --git a/worlds/blueprince/data_rooms.py b/worlds/blueprince/data_rooms.py index 6780b8f9b3d8..9e6ec2a566cf 100644 --- a/worlds/blueprince/data_rooms.py +++ b/worlds/blueprince/data_rooms.py @@ -28,7 +28,8 @@ ROOM_ITEM_ID_KEY: 2, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, - ROOM_CHEST_SPOT_COUNT_KEY: 2, # EXCEPTION: REQUIRES OBSERVATORY + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_COMPLEX, + ROOM_CHEST_SPOT_COUNT_KEY: 17, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_X, ROOM_PICK_POSITIONS_KEY: [], OUTER_ROOM_KEY: False, @@ -197,6 +198,7 @@ ROOM_ITEM_ID_KEY: 204, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_COMPLEX, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_D, ROOM_PICK_POSITIONS_KEY: [ @@ -349,6 +351,7 @@ ROOM_ITEM_ID_KEY: 7, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_RARE, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_I, ROOM_PICK_POSITIONS_KEY: [ @@ -477,6 +480,7 @@ ROOM_ITEM_ID_KEY: 15, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_RARE, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_J, ROOM_PICK_POSITIONS_KEY: [ @@ -546,6 +550,7 @@ ROOM_ITEM_ID_KEY: 19, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_RARE, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_J, ROOM_PICK_POSITIONS_KEY: [ @@ -654,8 +659,9 @@ blue_025_036 = { "Drawing Room": { ROOM_ITEM_ID_KEY: 25, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_RARE, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, ROOM_PICK_POSITIONS_KEY: [ @@ -675,7 +681,7 @@ }, "Study": { ROOM_ITEM_ID_KEY: 26, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_D, @@ -721,9 +727,10 @@ }, "The Pool": { ROOM_ITEM_ID_KEY: 29, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, - ROOM_CHEST_SPOT_COUNT_KEY: 1, # REQUIRES MOUNT HOLLY GIFT SHOP + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_COMPLEX, + ROOM_CHEST_SPOT_COUNT_KEY: 2, # REQUIRES MOUNT HOLLY GIFT SHOP ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, ROOM_PICK_POSITIONS_KEY: [ ROOM_PICK_POSITION_FRONT_BACK_RARE_GEMS, @@ -741,7 +748,7 @@ }, "Drafting Studio": { ROOM_ITEM_ID_KEY: 30, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_I, @@ -755,7 +762,7 @@ }, "Utility Closet": { ROOM_ITEM_ID_KEY: 31, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_D, @@ -773,7 +780,7 @@ }, "Boiler Room": { ROOM_ITEM_ID_KEY: 32, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, @@ -787,7 +794,7 @@ }, "Pump Room": { ROOM_ITEM_ID_KEY: 33, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_J, @@ -806,7 +813,7 @@ }, "Security": { ROOM_ITEM_ID_KEY: 34, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, @@ -820,7 +827,7 @@ }, "Workshop": { ROOM_ITEM_ID_KEY: 35, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_I, @@ -835,7 +842,7 @@ }, "Laboratory": { ROOM_ITEM_ID_KEY: 36, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_J, @@ -1200,6 +1207,7 @@ ROOM_ITEM_ID_KEY: 403, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_COMPLEX, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, ROOM_PICK_POSITIONS_KEY: [ @@ -1288,8 +1296,9 @@ }, "Great Hall": { ROOM_ITEM_ID_KEY: 410, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, + ROOM_CHEST_SPOT_TYPE_KEY: ROOM_CHEST_SPOT_RARE, ROOM_CHEST_SPOT_COUNT_KEY: 1, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_X, ROOM_PICK_POSITIONS_KEY: [ROOM_PICK_POSITION_CENTER_TIER_3], @@ -1408,7 +1417,7 @@ }, "Greenhouse": { ROOM_ITEM_ID_KEY: 509, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_J, @@ -1436,7 +1445,7 @@ }, "Secret Garden": { ROOM_ITEM_ID_KEY: 511, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, @@ -1805,7 +1814,7 @@ }, "Throne Room": { ROOM_ITEM_ID_KEY: 802, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_T, @@ -1816,7 +1825,7 @@ }, "Tomb": { ROOM_ITEM_ID_KEY: 803, - ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, ROOM_ITEM_SPOT_COUNT_KEY: 0, ROOM_CHEST_SPOT_COUNT_KEY: 0, ROOM_LAYOUT_TYPE_KEY: ROOM_LAYOUT_TYPE_D, @@ -1846,4 +1855,5 @@ "Shops": [room for room in shops], "Red Rooms": [room for room in red_rooms], "Black Rooms": [room for room in black_rooms], + "Outer Rooms": [room for room in rooms if rooms[room][OUTER_ROOM_KEY]], } \ No newline at end of file diff --git a/worlds/blueprince/locations.py b/worlds/blueprince/locations.py index a15d4f82db2c..39a46ba362e5 100644 --- a/worlds/blueprince/locations.py +++ b/worlds/blueprince/locations.py @@ -77,7 +77,9 @@ def create_regular_locations(world: BluePrinceWorld) -> None: LOCATIONS_BY_GROUPS["Room Entrances"].append(location_key) # Add Nth locked trunk open - trunks = [f"{room_key} Locked Trunk {idx}" for idx in range(1, world.options.locked_trunks + 1) if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0] + trunk_count = world.options.locked_trunks_common if ROOM_CHEST_SPOT_TYPE_KEY not in v or v[ROOM_CHEST_SPOT_TYPE_KEY] == ROOM_CHEST_SPOT_COMMON else world.options.locked_trunks_rare if v[ROOM_CHEST_SPOT_TYPE_KEY] == ROOM_CHEST_SPOT_RARE else world.options.locked_trunks_complex + + trunks = [f"{room_key} Locked Trunk {idx}" for idx in range(1, trunk_count + 1) if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0] locs = get_location_names_with_ids(trunks) room.add_locations(locs, BluePrinceLocation) LOCATIONS_BY_GROUPS["Trunks"].extend(trunks) @@ -85,8 +87,13 @@ def create_regular_locations(world: BluePrinceWorld) -> None: # These trunks require extra logic if room_key == "Entrance Hall": # TODO: switch to using set_rule once 0.6.7 is released. - for idx in range(1, world.options.locked_trunks + 1): + for idx in range(1, trunk_count + 1): world.get_location(f"Entrance Hall Locked Trunk {idx}").access_rule = lambda state: state.can_reach_region("Observatory", world.player) or state.can_reach_region("Laboratory", world.player) + + elif room_key == "The Pool": + # TODO: switch to using set_rule once 0.6.7 is released. + for idx in range(1, trunk_count + 1): + world.get_location(f"The Pool Locked Trunk {idx}").access_rule = lambda state: state.can_reach_region("Gift Shop", world.player) for k, v in locations.items(): if NONSANITY_LOCATION_KEY in v and world.options.room_draft_sanity == False: diff --git a/worlds/blueprince/options.py b/worlds/blueprince/options.py index de8647518d5f..d1c44dbf0141 100644 --- a/worlds/blueprince/options.py +++ b/worlds/blueprince/options.py @@ -91,18 +91,42 @@ class SpecialShopSanity(Toggle): # TODO-2 Crate Sanity? # TODO-2 Document full list of potential checks/locations posted in blue prince thread. -class LockedTrunkCount(Range): +class LockedTrunkCommonCount(Range): """ - This is the number of locked trunks per room that need to be opened for archipelago items. + This is the number of common locked trunks per room that need to be opened for archipelago items. """ - display_name = "Locked Trunks" + display_name = "Common Locked Trunks" range_start = 0 range_end = 100 default = 2 +class LockedTrunkRareCount(Range): + """ + This is the number of rare locked trunks per room that need to be opened for archipelago items. + """ + + display_name = "Rare Locked Trunks" + + range_start = 0 + range_end = 100 + + default = 0 + +class LockedTrunkComplexCount(Range): + """ + This is the number of complex locked trunks per room that need to be opened for archipelago items. + """ + + display_name = "Complex Locked Trunks" + + range_start = 0 + range_end = 100 + + default = 0 + class ItemLogicMode(Choice): """ This option controls which possible item spawns are considered for an item being obtainable. @@ -343,7 +367,9 @@ class BluePrinceOptions(PerGameCommonOptions): # Development Options room_draft_sanity: RoomDraftSanity - locked_trunks: LockedTrunkCount + locked_trunks_common: LockedTrunkCommonCount + locked_trunks_rare: LockedTrunkRareCount + locked_trunks_complex: LockedTrunkComplexCount item_logic_mode: ItemLogicMode standard_item_sanity: StandardItemSanity @@ -373,7 +399,9 @@ class BluePrinceOptions(PerGameCommonOptions): "Sanity Options", [ RoomDraftSanity, - LockedTrunkCount, + LockedTrunkCommonCount, + LockedTrunkRareCount, + LockedTrunkComplexCount, ItemLogicMode, StandardItemSanity, WorkshopSanity, @@ -403,7 +431,9 @@ class BluePrinceOptions(PerGameCommonOptions): # with no death link, with the goal set to room 64, and with no filler items or traps added to the pool. "Room 46 Extra Drafting": { "room_draft_sanity": True, - "locked_trunks": 2, + "locked_trunks_common": 2, + "locked_trunks_rare": 0, + "locked_trunks_complex": 0, "standard_item_sanity": True, "workshop_sanity": True, "upgrade_disk_sanity": True, From 80153d5850fd6ce0b98aa4fb16dc4d751e8afd5f Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Wed, 4 Mar 2026 12:54:27 -0500 Subject: [PATCH 11/13] added examples --- worlds/blueprince/options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/blueprince/options.py b/worlds/blueprince/options.py index d1c44dbf0141..0bd0f1a760fe 100644 --- a/worlds/blueprince/options.py +++ b/worlds/blueprince/options.py @@ -93,7 +93,7 @@ class SpecialShopSanity(Toggle): class LockedTrunkCommonCount(Range): """ - This is the number of common locked trunks per room that need to be opened for archipelago items. + This is the number of common locked trunks per room that need to be opened for archipelago items. Example: Bedroom and Den trunks. """ display_name = "Common Locked Trunks" @@ -105,7 +105,7 @@ class LockedTrunkCommonCount(Range): class LockedTrunkRareCount(Range): """ - This is the number of rare locked trunks per room that need to be opened for archipelago items. + This is the number of rare locked trunks per room that need to be opened for archipelago items. Exmple: Drawing Room trunk. """ display_name = "Rare Locked Trunks" @@ -117,7 +117,7 @@ class LockedTrunkRareCount(Range): class LockedTrunkComplexCount(Range): """ - This is the number of complex locked trunks per room that need to be opened for archipelago items. + This is the number of complex locked trunks per room that need to be opened for archipelago items. Example: Entrance Hall trunks from The Twins constellation or Laboratory experiments. """ display_name = "Complex Locked Trunks" From 980ea7a0cf58b43dd40291257bfbcfa30f4ee130 Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 10 Mar 2026 20:30:12 -0400 Subject: [PATCH 12/13] started dares; added fill_slot_data --- worlds/blueprince/constants.py | 14 ++- worlds/blueprince/dares.py | 114 ++++++++++++++++++++++ worlds/blueprince/data_items.py | 17 ++-- worlds/blueprince/data_other_locations.py | 50 +++++----- worlds/blueprince/regions.py | 10 +- worlds/blueprince/world.py | 44 ++++++--- 6 files changed, 201 insertions(+), 48 deletions(-) create mode 100644 worlds/blueprince/dares.py diff --git a/worlds/blueprince/constants.py b/worlds/blueprince/constants.py index 60353849b1bd..20f156cb5d61 100644 --- a/worlds/blueprince/constants.py +++ b/worlds/blueprince/constants.py @@ -2,6 +2,9 @@ # OPTIONS CONSTANTS # ##################### +from typing import Dict, Set + + ROOM_DRAFT_SANITY = "room_draft_sanity" STANDARD_ITEM_SANITY = "standard_item_sanity" WORKSHOP_SANITY = "workshop_sanity" @@ -37,7 +40,6 @@ INNER_ROOM_KEY = "is_inner_room" - ################## # ITEM CONSTANTS # ################## @@ -176,10 +178,16 @@ TRADING_POST_GIVE = "GIVE" TRADING_POST_RECEIVE = "RECEIVE" +################## +# DARE CONSTANTS # +################## + +DARE_CAN_REACH_RULE = "Can Reach Rule" +DARE_IS_POSSIBLE_RULE = "Is Possible Rule" ######################## # Item/Location GROUPS # ######################## -ITEMS_BY_GROUPS : dict[str, list[str]] = {} -LOCATIONS_BY_GROUPS : dict[str, list[str]] = {} \ No newline at end of file +ITEMS_BY_GROUPS : Dict[str, Set[str]] = {} +LOCATIONS_BY_GROUPS : Dict[str, Set[str]] = {} \ No newline at end of file diff --git a/worlds/blueprince/dares.py b/worlds/blueprince/dares.py new file mode 100644 index 000000000000..45967f9466a5 --- /dev/null +++ b/worlds/blueprince/dares.py @@ -0,0 +1,114 @@ +from typing import Union + +from .world import BluePrinceWorld +from .constants import * +from .data_rooms import blue_rooms, red_rooms, bedrooms, shops, black_rooms, hallways, green_rooms + +from BaseClasses import CollectionState +from collections.abc import Callable + +def dare_is_possible(dare_name: str, state: CollectionState, player: int, win_day: bool = False) -> bool: + + if dare_name not in dares: + return False + + dare = dares[dare_name] + + if DARE_IS_POSSIBLE_RULE not in dare: + return True + + return dare[DARE_IS_POSSIBLE_RULE](state, player, win_day) + +def can_reach_with_dares(world: BluePrinceWorld, to_check: str, type_hint: str = "Region", win_day: bool = False) -> bool: + for d in world.dares: + if not can_reach_with_dare(d, to_check, type_hint, win_day): + return False + + return True + +def can_reach_with_dare(dare_name: str, to_check: str, type_hint: str = "Region", win_day: bool = False) -> bool: + + if dare_name not in dares: + return False + + dare = dares[dare_name] + + if DARE_CAN_REACH_RULE not in dare: + return True + + return dare[DARE_CAN_REACH_RULE](to_check, type_hint, win_day) + +dares : dict[str, dict[str, Callable]] = { + "Lavatory30s": { + DARE_IS_POSSIBLE_RULE: lambda state, player, win_day: state.can_reach_region("Lavatory", player) or win_day + }, # Can reach Lavatory or can win today + "NoNorthEntranceHall": { + # TODO: rework region logic so this can be implemented + }, + "DraftFirstEntranceHall": { + # TODO: check if conflicts with other dares + }, + "ExcatlyOnePurchasePerShop": { + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not (to_check == "Aquarium" and type_hint == "Region") or win_day + }, + "AlwaysAtLeast20Steps": { + # Should always be possible + }, + "NeverDraftFullRank": { + # Should always be possible + }, + "OpenEmptyBoxParlor": { + # Should always be possible, unless we lock the windup key + }, + "OnlyOneButtonUtilityCloset": { + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not ((to_check == "Gemstone Cavern" and type_hint == "Region") or to_check == "VAC Controls") + }, + "LeaveBlueprint": { + # Should always be possible + }, + "NoBilliardFail": { + # Should always be possible + }, + "NeverStepThePool": { + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not (to_check == "The Pool" and type_hint == "Region") + }, + "OpenEachLockedTrunk": { + # Should always be possible + }, + "NeverEnterMoreThan3x": { + # Should always be possible + }, + "NeverDraftDen": { + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not (to_check == "Den" and type_hint == "Region") + }, + "AlwaysDraftMostExpensive": { + # Should always be possible + }, + "AlwaysDraftRed": { + # Should always be possible + }, + "NeverEatFruit": { + # Should always be possible + }, + "NeverRideElevator": { + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not ((to_check in ["The Foundation", "Blackbridge Grotto", "Tunnel Area Past Red Door", "Reservoir Bottom"] and type_hint == "Region") or (to_check in ["Underpass Gate", "Treasure Trove Floorplan"] and type_hint == "Location")) + }, + "NeverDraftSouth": { + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not (to_check == "Her Ladyship's Chambers" and type_hint == "Region") + }, + "EndDayAtLeast1Gem": { + # Should always be possible + }, + "Draft6DifferentColors": { + DARE_IS_POSSIBLE_RULE: lambda state, player, win_day: win_day or state.can_reach_region("Aquarium", player) + }, + "NeverExitEntranceHall": { + # TODO: region logic would need to be fully rewritten to support this + }, + "EndDay0Gem0Coin0Key": { + # Should always be possible + }, + "NeverHaveMoreThan2Items": { + + }, +} \ No newline at end of file diff --git a/worlds/blueprince/data_items.py b/worlds/blueprince/data_items.py index 1e87e2cf7ec5..b0b59d1ab713 100644 --- a/worlds/blueprince/data_items.py +++ b/worlds/blueprince/data_items.py @@ -1,3 +1,4 @@ +from typing import Dict, Set from .constants import * from BaseClasses import Item, ItemClassification @@ -614,12 +615,12 @@ # None of the Tier 5 items can be received, so there's no point in defining it atm -ITEMS_BY_GROUPS = { - "Upgrade Disks": [disk for disk in upgrade_disks], - "Sanctum Keys": [key for key in sanctum_keys], - "Keys": [key for key in keys], - "Showroom Items": [item for item in showroom_items], - "Armory Items": [item for item in armory_items], - "Workshop Items": [item for item in workshop_items], - "Standard Items": [item for item in other_items] +ITEMS_BY_GROUPS : Dict[str, Set[str]] = { + "Upgrade Disks": {disk for disk in upgrade_disks}, + "Sanctum Keys": {key for key in sanctum_keys}, + "Keys": {key for key in keys}, + "Showroom Items": {item for item in showroom_items}, + "Armory Items": {item for item in armory_items}, + "Workshop Items": {item for item in workshop_items}, + "Standard Items": {item for item in other_items} } \ No newline at end of file diff --git a/worlds/blueprince/data_other_locations.py b/worlds/blueprince/data_other_locations.py index eea65fab40f3..1aa47a6dc52f 100644 --- a/worlds/blueprince/data_other_locations.py +++ b/worlds/blueprince/data_other_locations.py @@ -1,9 +1,12 @@ from BaseClasses import CollectionState, ItemClassification +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .world import BluePrinceWorld from .constants import * from .data_rooms import rooms, core_rooms, classrooms, room_layout_lists from .data_items import * -# from .world import LOCATIONS_BY_GROUPS +from .dares import can_reach_with_dares room_location_mem : dict[str, list[int]] = {} @@ -189,7 +192,7 @@ def can_reach_item_location(item_name: str, state: CollectionState, player: int) "Underpass Gate": { LOCATION_ID_KEY: get_room_location_id("The Underpass", 0), LOCATION_ROOM_KEY: "The Underpass", - LOCATION_RULE_SIMPLE_COMMON: lambda state, world: state.can_reach_region("Boiler Room", world.player) + LOCATION_RULE_SIMPLE_COMMON: lambda state, world: state.can_reach_region("Boiler Room", world.player) and can_reach_with_dares(world, "Boiler Room", "Region") }, "Shelter Safe": { LOCATION_ID_KEY: get_room_location_id("Shelter", 0), @@ -338,7 +341,7 @@ def can_reach_item_location(item_name: str, state: CollectionState, player: int) "Treasure Trove Floorplan": { LOCATION_ID_KEY: get_room_location_id("The Underpass", 2), LOCATION_ROOM_KEY: "The Underpass", - LOCATION_RULE_SIMPLE_COMMON: lambda state, world: state.can_reach_region("Boiler Room", world.player), + LOCATION_RULE_SIMPLE_COMMON: lambda state, world: state.can_reach_region("Boiler Room", world.player) and can_reach_with_dares(world, "Boiler Room", "Region"), NONSANITY_LOCATION_KEY: "Treasure Trove" }, "Throne Room Floorplan": { @@ -489,7 +492,7 @@ def advanced_experiment_rule(state: CollectionState, player: int) -> bool: def trading_post_rule(item_name: str, state: CollectionState, player: int) -> bool: return state.can_reach_region("Trading Post", player) and any(can_reach_item_location(item, state, player) for item in get_trading_post_offers(item_name)) -def dig_spot_rule(state: CollectionState, player: int) -> bool: +def dig_spot_rule(state: CollectionState, player: int, world: BluePrinceWorld) -> bool: return any(state.can_reach_region(region, player) for region in [ "The Foundation", "Wine Cellar", @@ -503,7 +506,6 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Patio", "Storeroom", "Garage", - "Boiler Room", "Pump Room", "Workshop", "Secret Garden", @@ -514,7 +516,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Solarium", "Tunnel", "Conservatory", - ]) or (state.can_reach_region("Planetarium", player) and can_reach_item_location("TELESCOPE", state, player)) + ]) or (state.can_reach_region("Planetarium", player) and can_reach_item_location("TELESCOPE", state, player)) or (state.can_reach_region("Boiler Room", player) and can_reach_with_dares(world, "Boiler Room", "Region")) standard_item_pickup = { "BATTERY PACK First Pickup": { @@ -579,7 +581,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Garage", "Utility Closet", "Kitchen", - ]) or (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player)), + ]) or (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world)), LOCATION_RULE_COMPLEX: darkroom_rule, @@ -968,7 +970,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Lost And Found", ]), - LOCATION_RULE_SIMPLE_RARE: lambda state, world: can_reach_item_location("JACK HAMMER", state, world.player) and dig_spot_rule(state, world.player), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: can_reach_item_location("JACK HAMMER", state, world.player) and dig_spot_rule(state, world.player, world), }, "TELESCOPE First Pickup": { LOCATION_ID_KEY: get_room_location_id("Campsite", 20), # Doesn't spawn there, but putting it there and adding spawn locations as requirements @@ -1131,7 +1133,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Billiard Room", ]), - LOCATION_RULE_SIMPLE_RARE: lambda state, world: can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world), LOCATION_RULE_COMPLEX: lambda state, world: (state.can_reach_region("Garage", world.player) and can_reach_item_location("CAR KEYS", state, world.player)) or trunk_rule(state, world.player), @@ -1152,7 +1154,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: ]), # Also ignoring chance to spawn in trunks for the moment - LOCATION_RULE_SIMPLE_RARE: lambda state, world: can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world), LOCATION_RULE_COMPLEX: lambda state, world: (state.can_reach_region("Freezer", world.player) and any(can_reach_item_location(item, state, world.player) for item in ["Burning Glass", "TORCH"]) and can_reach_item_location("PRISM KEY_0", state, world)) or trunk_rule(state, world.player), @@ -1416,7 +1418,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Music Room", ]), - LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player)) or state.can_reach_region("Trophy Room", world.player), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world)) or state.can_reach_region("Trophy Room", world.player), LOCATION_RULE_EXTREME: advanced_experiment_rule, }, @@ -1434,7 +1436,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Music Room", ]), - LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player)), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world)), LOCATION_RULE_COMPLEX: lavatory_rule, LOCATION_RULE_EXTREME: advanced_experiment_rule, }, @@ -1450,7 +1452,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: "Hovel", ]), # Can also spawn in Spare Hall, but we aren't adding upgraded rooms seperately atm. - LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player)) or state.can_reach_region("Drawing Room", world.player), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world)) or state.can_reach_region("Drawing Room", world.player), }, "Vault Key 370": { LOCATION_ID_KEY: get_room_location_id("Entrance Hall", 7), # Doesn't spawn there, but putting it there and adding spawn locations as requirements @@ -1458,7 +1460,7 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: LOCATION_ITEM_KEY: "VAULT KEY 370", LOCATION_RULE_SIMPLE_COMMON: lambda state, world: state.can_reach_region("Lost And Found", world.player), - LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player)), + LOCATION_RULE_SIMPLE_RARE: lambda state, world: (can_reach_item_location("SHOVEL", state, world.player) and dig_spot_rule(state, world.player, world)), } } @@ -1606,14 +1608,14 @@ def dig_spot_rule(state: CollectionState, player: int) -> bool: locations = trophies | safes_and_small_gates | mora_jai_boxes | floorplans | shop_items | upgrade_disks | keys | misc_locations | item_pickups | workshop_contraptions LOCATIONS_BY_GROUPS |= { - "Trophies": [k for k in trophies], - "Safes and Small Gates": [k for k in safes_and_small_gates], - "Mora Jai Boxes": [k for k in mora_jai_boxes], - "Floorplans": [k for k in floorplans], - "Shop Items": [k for k in shop_items], - "Upgrade Disks": [k for k in upgrade_disks], - "Keys": [k for k in keys], - "Miscellaneous": [k for k in misc_locations], - "Item Pickups": [k for k in item_pickups], - "Workshop Contraptions": [k for k in workshop_contraptions], + "Trophies": {k for k in trophies}, + "Safes and Small Gates": {k for k in safes_and_small_gates}, + "Mora Jai Boxes": {k for k in mora_jai_boxes}, + "Floorplans": {k for k in floorplans}, + "Shop Items": {k for k in shop_items}, + "Upgrade Disks": {k for k in upgrade_disks}, + "Keys": {k for k in keys}, + "Miscellaneous": {k for k in misc_locations}, + "Item Pickups": {k for k in item_pickups}, + "Workshop Contraptions": {k for k in workshop_contraptions}, } \ No newline at end of file diff --git a/worlds/blueprince/regions.py b/worlds/blueprince/regions.py index d81ad77593ad..ced14a0d16bc 100644 --- a/worlds/blueprince/regions.py +++ b/worlds/blueprince/regions.py @@ -9,6 +9,7 @@ from .constants import * from .room_min_pieces import * from .data_other_locations import can_reach_item_location +from .dares import can_reach_with_dares if TYPE_CHECKING: from .world import BluePrinceWorld @@ -237,6 +238,13 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: lambda state: state.count_from_list_unique(classrooms, world.player) >= cnum, ) + elif k == "Aquarium": + entrance_hall.connect( + room, + f"Entrance Hall Aquarium", + lambda state: can_reach_pick_position("Aquarium", world, state) and can_reach_with_dares(world, "Aquarium"), + ) + # TODO: Add Her Ladyship's Chamber, it has weird requirements elif k == "Entrance Hall": continue @@ -565,7 +573,7 @@ def can_reach_pick_position(room: str, world: BluePrinceWorld, state: Collection return False -def matches_minimum_inventory(required: list[tuple[int]], inventory: dict[str, int]) -> bool: +def matches_minimum_inventory(required: list[tuple[int, int, int, int]], inventory: dict[str, int]) -> bool: inv = tuple(inventory[k] for k in inventory) for req in required: if all(inv[i] >= req[i] for i in range(4)): diff --git a/worlds/blueprince/world.py b/worlds/blueprince/world.py index e19d8f30bb5b..328c255e40ec 100644 --- a/worlds/blueprince/world.py +++ b/worlds/blueprince/world.py @@ -1,5 +1,5 @@ from collections.abc import Mapping -from typing import Any +from typing import Any, Set # Imports of base Archipelago modules must be absolute. from worlds.AutoWorld import World @@ -8,7 +8,7 @@ # Imports of your world's files must be relative. from . import items, locations, regions, rules, web_world from . import ( - options as blue_prince_optionss, + options as blue_prince_options, ) # rename due to a name conflict with World.options @@ -26,8 +26,8 @@ class BluePrinceWorld(World): web = web_world.BluePrinceWebWorld() # Set the Options - options_dataclass = blue_prince_optionss.BluePrinceOptions - options: blue_prince_optionss.BluePrinceOptions + options_dataclass = blue_prince_options.BluePrinceOptions + options: blue_prince_options.BluePrinceOptions # Our world class must have a static location_name_to_id and item_name_to_id defined. # We define these in regions.py and items.py respectively, so we just set them here. @@ -39,6 +39,8 @@ class BluePrinceWorld(World): item_name_groups = ITEMS_BY_GROUPS + dares : Set[str] = set() + # # Our world class must have certain functions ("steps") that get called during generation. # # The main ones are: create_regions, set_rules, create_items. # # For better structure and readability, we put each of these in their own file. @@ -58,11 +60,29 @@ def create_item(self, name: str) -> items.BluePrinceItem: def get_filler_item_name(self) -> str: return items.get_random_filler_item_name(self) - # # There may be data that the game client will need to modify the behavior of the game. - # # This is what slot_data exists for. Upon every client connection, the slot's slot_data is sent to the client. - # # slot_data is just a dictionary using basic types, that will be converted to json when sent to the client. - # def fill_slot_data(self) -> Mapping[str, Any]: - # # If you need access to the player's chosen options on the client side, there is a helper for that. - # return self.options.as_dict( - # "hard_mode", "hammer", "extra_starting_chest", "confetti_explosiveness", "player_sprite" - # ) \ No newline at end of file + # There may be data that the game client will need to modify the behavior of the game. + # This is what slot_data exists for. Upon every client connection, the slot's slot_data is sent to the client. + # slot_data is just a dictionary using basic types, that will be converted to json when sent to the client. + def fill_slot_data(self) -> Mapping[str, Any]: + # If you need access to the player's chosen options on the client side, there is a helper for that. + return self.options.as_dict( + "room_draft_sanity", + "locked_trunks_common", + "locked_trunks_rare", + "locked_trunks_complex", + "standard_item_sanity", + "workshop_sanity", + "upgrade_disk_sanity", + "key_sanity", + "special_shop_sanity", + "item_logic_mode", + "filler_item_distribution", + "trap_type_distribution", + "trap_percentage", + "death_link_type", + "death_link_grace", + "death_link_monk_exception", + "goal_type", + "goal_sanctum_solves", + "start_inventory", + ) \ No newline at end of file From 741875ba22c8473bf4ce0a5a40a103d32a99901f Mon Sep 17 00:00:00 2001 From: BatmenzDW Date: Tue, 10 Mar 2026 21:07:38 -0400 Subject: [PATCH 13/13] fixed import s --- worlds/blueprince/dares.py | 5 ++--- worlds/blueprince/data_other_locations.py | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/worlds/blueprince/dares.py b/worlds/blueprince/dares.py index 45967f9466a5..ce0f7a386b1d 100644 --- a/worlds/blueprince/dares.py +++ b/worlds/blueprince/dares.py @@ -1,6 +1,5 @@ -from typing import Union +from typing import Union, TYPE_CHECKING -from .world import BluePrinceWorld from .constants import * from .data_rooms import blue_rooms, red_rooms, bedrooms, shops, black_rooms, hallways, green_rooms @@ -19,7 +18,7 @@ def dare_is_possible(dare_name: str, state: CollectionState, player: int, win_da return dare[DARE_IS_POSSIBLE_RULE](state, player, win_day) -def can_reach_with_dares(world: BluePrinceWorld, to_check: str, type_hint: str = "Region", win_day: bool = False) -> bool: +def can_reach_with_dares(world, to_check: str, type_hint: str = "Region", win_day: bool = False) -> bool: for d in world.dares: if not can_reach_with_dare(d, to_check, type_hint, win_day): return False diff --git a/worlds/blueprince/data_other_locations.py b/worlds/blueprince/data_other_locations.py index 1aa47a6dc52f..39106a3ac40e 100644 --- a/worlds/blueprince/data_other_locations.py +++ b/worlds/blueprince/data_other_locations.py @@ -1,8 +1,5 @@ from BaseClasses import CollectionState, ItemClassification -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from .world import BluePrinceWorld from .constants import * from .data_rooms import rooms, core_rooms, classrooms, room_layout_lists from .data_items import * @@ -492,7 +489,7 @@ def advanced_experiment_rule(state: CollectionState, player: int) -> bool: def trading_post_rule(item_name: str, state: CollectionState, player: int) -> bool: return state.can_reach_region("Trading Post", player) and any(can_reach_item_location(item, state, player) for item in get_trading_post_offers(item_name)) -def dig_spot_rule(state: CollectionState, player: int, world: BluePrinceWorld) -> bool: +def dig_spot_rule(state: CollectionState, player: int, world) -> bool: return any(state.can_reach_region(region, player) for region in [ "The Foundation", "Wine Cellar",