diff --git a/worlds/blueprince/dares.py b/worlds/blueprince/dares.py index ce0f7a386b1d..b80330afc818 100644 --- a/worlds/blueprince/dares.py +++ b/worlds/blueprince/dares.py @@ -93,7 +93,7 @@ def can_reach_with_dare(dare_name: str, to_check: str, type_hint: str = "Region" 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") + DARE_CAN_REACH_RULE: lambda to_check, type_hint, win_day: not (to_check == "Her Ladyship's Chamber" and type_hint == "Region") }, "EndDayAtLeast1Gem": { # Should always be possible diff --git a/worlds/blueprince/data_items.py b/worlds/blueprince/data_items.py index 3fbda76c6a56..57b7421c930a 100644 --- a/worlds/blueprince/data_items.py +++ b/worlds/blueprince/data_items.py @@ -143,7 +143,7 @@ "SILVER KEY": { ITEM_ELEMENT_INDEX_KEY: 19, ITEM_ID_KEY: 10019, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "KEY 8": { ITEM_ELEMENT_INDEX_KEY: 27, @@ -178,7 +178,7 @@ "DIARY KEY": { ITEM_ELEMENT_INDEX_KEY: 42, ITEM_ID_KEY: 10042, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "CABINET KEY 1": { ITEM_ELEMENT_INDEX_KEY: 45, @@ -199,7 +199,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 | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "KEY of Aries": { # Known exception to formatting. Keeping consistent with game format. @@ -233,32 +233,32 @@ "CHRONOGRAPH": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2001, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "EMERALD BRACELET": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2002, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "MASTER KEY": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2003, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "MOON PENDANT": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2004, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "ORNATE COMPASS": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2005, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "SILVER SPOON": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 2006, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, } armory_items = { @@ -270,7 +270,7 @@ "THE AXE": { ITEM_ELEMENT_INDEX_KEY: 36, ITEM_ID_KEY: 1036, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "TORCH": { ITEM_ELEMENT_INDEX_KEY: 37, @@ -323,17 +323,17 @@ "RUNNING SHOES": { ITEM_ELEMENT_INDEX_KEY: 15, ITEM_ID_KEY: 1015, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "SALT SHAKER": { ITEM_ELEMENT_INDEX_KEY: 16, ITEM_ID_KEY: 1016, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "SLEEPING MASK": { ITEM_ELEMENT_INDEX_KEY: 21, ITEM_ID_KEY: 1021, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "COIN PURSE": { ITEM_ELEMENT_INDEX_KEY: 4, @@ -343,7 +343,7 @@ "COUPON BOOK": { ITEM_ELEMENT_INDEX_KEY: 6, ITEM_ID_KEY: 1006, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "LOCK PICK KIT": { ITEM_ELEMENT_INDEX_KEY: 11, @@ -363,12 +363,12 @@ "STOPWATCH": { ITEM_ELEMENT_INDEX_KEY: 29, ITEM_ID_KEY: 1029, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "REPELLENT": { ITEM_ELEMENT_INDEX_KEY: 30, ITEM_ID_KEY: 1030, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "WATERING CAN": { ITEM_ELEMENT_INDEX_KEY: 31, @@ -378,12 +378,12 @@ "HALL PASS": { ITEM_ELEMENT_INDEX_KEY: 32, ITEM_ID_KEY: 1032, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "LUNCH BOX": { ITEM_ELEMENT_INDEX_KEY: 43, ITEM_ID_KEY: 1043, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "CURSED EFFIGY": { ITEM_ELEMENT_INDEX_KEY: 44, @@ -398,7 +398,7 @@ "PAPER CROWN": { ITEM_ELEMENT_INDEX_KEY: 48, ITEM_ID_KEY: 1048, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "ROYAL SCEPTER": { ITEM_ELEMENT_INDEX_KEY: 49, @@ -408,7 +408,7 @@ "GEAR WRENCH": { ITEM_ELEMENT_INDEX_KEY: 33, ITEM_ID_KEY: 1033, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.useful, }, "COMPASS": { ITEM_ELEMENT_INDEX_KEY: 5, @@ -426,7 +426,7 @@ "Detector Shovel": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3002, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, }, "Dowsing Rod": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, @@ -436,7 +436,7 @@ "Electromagnet": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3004, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, }, "Jack Hammer": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, @@ -446,12 +446,12 @@ "Lucky Purse": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3006, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, }, "Pick Sound Amplifier": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 3007, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing | ItemClassification.useful, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, }, "Power Hammer": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, @@ -525,7 +525,7 @@ "The Curse of Black Bridge": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, ITEM_ID_KEY: 5003, - ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, + ITEM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression_deprioritized_skip_balancing, }, "Realm & Rune": { ITEM_ELEMENT_INDEX_KEY: NO_ITEM_ELEMENT_INDEX, @@ -672,6 +672,7 @@ "Keys": {key for key in keys}, "Showroom Items": {item for item in showroom_items}, "Armory Items": {item for item in armory_items}, + "Bookshop Items": {item for item in bookshop_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 eb8d35020790..3bbc9e106583 100644 --- a/worlds/blueprince/data_other_locations.py +++ b/worlds/blueprince/data_other_locations.py @@ -11,44 +11,44 @@ trophies = { "Full House Trophy": { - LOCATION_ID_KEY: all_areas["Entrance Hall"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Entrance Hall"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Entrance Hall", - LOCATION_RULE_SIMPLE_COMMON: HasFromList(*[x for x in room_layout_lists[INNER_ROOM_KEY] if x not in core_rooms], count=43) + LOCATION_RULE_SIMPLE_COMMON: HasFromList(*(directory_rooms + ["SECRET GARDEN KEY", "KEY 8"]), count=43) }, "Trophy of Invention": { - LOCATION_ID_KEY: all_areas["Workshop"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Workshop"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Workshop", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("Burning Glass") & CanReachItemLocation("Detector Shovel") & CanReachItemLocation("Dowsing Rod") & CanReachItemLocation("Electromagnet") & CanReachItemLocation("Jack Hammer") & CanReachItemLocation("Lucky Purse") & CanReachItemLocation("Pick Sound Amplifier") & CanReachItemLocation("Power Hammer"), }, "Trophy of Drafting": { - LOCATION_ID_KEY: all_areas["Mail Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Mail Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Mail Room", - LOCATION_RULE_SIMPLE_COMMON: HasFromList(*[x for x in rooms if rooms[x][ROOM_LAYOUT_TYPE_KEY] == ROOM_LAYOUT_TYPE_D and not rooms[x][OUTER_ROOM_KEY] and x not in core_rooms and x not in ["Mechanarium"]], count=20), # Mechanarium is a dead end for pathing, but doesn't count for the trophy + LOCATION_RULE_SIMPLE_COMMON: CanReachRegionsFromList(*[x for x in rooms if rooms[x][ROOM_LAYOUT_TYPE_KEY] == ROOM_LAYOUT_TYPE_D and not rooms[x][OUTER_ROOM_KEY] and x not in core_rooms and x not in ["Mechanarium"]], count=20), # Mechanarium is a dead end for pathing, but doesn't count for the trophy }, "Trophy of Wealth": { - LOCATION_ID_KEY: all_areas["Showroom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Showroom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Showroom", }, "Inheritance Trophy": { - LOCATION_ID_KEY: all_areas["Room 46"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Room 46"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Room 46", }, "Bullseye Trophy": { - LOCATION_ID_KEY: all_areas["Billiard Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Billiard Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Billiard Room", }, "A Logical Trophy": { - LOCATION_ID_KEY: all_areas["Parlor"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Parlor"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Parlor", }, "Trophy 8": { - LOCATION_ID_KEY: all_areas["Room 8"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Room 8"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Room 8", }, "Explorer's Trophy": { LOCATION_ID_KEY: all_areas["Entrance Hall"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 1, LOCATION_ROOM_KEY: "Entrance Hall", - LOCATION_RULE_SIMPLE_COMMON: CanReachRegion("Secret Garden") & CanReachRegion("Room 8") & HasAllCounts({k: 1 for k in directory_rooms}), + LOCATION_RULE_SIMPLE_COMMON: CanReachRegionsFromList(*[x for x in room_layout_lists[INNER_ROOM_KEY] if x not in core_rooms], count=(len(room_layout_lists[INNER_ROOM_KEY]) - len(core_rooms))), }, "Trophy of Sigils": { LOCATION_ID_KEY: all_areas["Entrance Hall"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 2, @@ -104,38 +104,38 @@ safes_and_small_gates = { "Boudoir Safe": { - LOCATION_ID_KEY: all_areas["Boudoir"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Boudoir"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Boudoir", }, "Drafting Studio Safe": { - LOCATION_ID_KEY: all_areas["Drafting Studio"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Drafting Studio"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Drafting Studio", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("MAGNIFYING GLASS"), }, "Drawing Room Safe": { - LOCATION_ID_KEY: all_areas["Drawing Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Drawing Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Drawing Room", }, "Office Safe": { - LOCATION_ID_KEY: all_areas["Office"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Office"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Office", }, "Study Safe": { - LOCATION_ID_KEY: all_areas["Study"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Study"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Study", }, "Underpass Gate": { - LOCATION_ID_KEY: all_areas["The Underpass"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["The Underpass"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "The Underpass", LOCATION_RULE_SIMPLE_COMMON: CanReachRegion("Boiler Room"), # 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: all_areas["Shelter"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Shelter"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Shelter", }, "Orchard Gate": { - LOCATION_ID_KEY: all_areas["Campsite"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Campsite"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Campsite", } } @@ -149,11 +149,11 @@ mora_jai_boxes = { "Master Bedroom Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Master Bedroom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Master Bedroom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Master Bedroom", }, "Closed Exhibit Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Closed Exhibit"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Closed Exhibit"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Closed Exhibit", }, "Underpass Mora Jai Box": { @@ -161,19 +161,19 @@ LOCATION_ROOM_KEY: "The Underpass", }, "Tomb Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Tomb"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Tomb"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Tomb", }, "Trading Post Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Trading Post"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Trading Post"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Trading Post", }, "Tunnel Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Tunnel"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Tunnel"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Tunnel", }, "Solarium Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Solarium"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Solarium"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Solarium", }, "Lost And Found Mora Jai Box": { @@ -181,40 +181,40 @@ LOCATION_ROOM_KEY: "Lost And Found", }, "Throne of the Blue Prince Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Throne Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Throne Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Throne Room", LOCATION_RULE_SIMPLE_COMMON: Has("Ascend The Throne", options=[OptionFilter(GoalType, GoalType.option_blueprints, "ge")]), }, "Arch Aries Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Arch Aries Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Arch Aries Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Arch Aries Sanctum", }, "Corarica Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Corarica Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Corarica Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Corarica Sanctum", }, "Eraja Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Eraja Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Eraja Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Eraja Sanctum", }, "Fenn Aries Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Fenn Aries Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Fenn Aries Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Fenn Aries Sanctum", }, "Mora Jai Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Mora Jai Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Mora Jai Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Mora Jai Sanctum", }, "Orinda Aries Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Orinda Aries Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Orinda Aries Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Orinda Aries Sanctum", }, "Verra Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Verra Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Verra Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Verra Sanctum", }, "Nuance Sanctum Mora Jai Box": { - LOCATION_ID_KEY: all_areas["Nuance Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Nuance Sanctum"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Nuance Sanctum", } } | aries_court_mora_jai_boxes @@ -265,12 +265,12 @@ found_floorplans = { "Planetarium Floorplan": { - LOCATION_ID_KEY: all_areas["Observatory"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Observatory"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Observatory", NONSANITY_LOCATION_KEY: "Planetarium" }, "Mechanarium Floorplan": { - LOCATION_ID_KEY: all_areas["Rotating Gear"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Rotating Gear"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Rotating Gear", NONSANITY_LOCATION_KEY: "Mechanarium" }, @@ -282,12 +282,12 @@ NONSANITY_LOCATION_KEY: "Treasure Trove" }, "Throne Room Floorplan": { - LOCATION_ID_KEY: all_areas["Orindian Ruins"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Orindian Ruins"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Orindian Ruins", NONSANITY_LOCATION_KEY: "Throne Room" }, "Tunnel Floorplan": { - LOCATION_ID_KEY: all_areas["Tunnel Area Past Crates"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Tunnel Area Past Crates"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Tunnel Area Past Crates", NONSANITY_LOCATION_KEY: "Tunnel" }, @@ -298,7 +298,7 @@ LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("SHOVEL"), }, "Lost And Found Floorplan": { - LOCATION_ID_KEY: all_areas["Basement"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Basement"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Basement", NONSANITY_LOCATION_KEY: "Lost And Found", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("SHOVEL"), @@ -314,7 +314,7 @@ gift_shop_items = { "Gift Shop - Mt. Holly Tee": { - LOCATION_ID_KEY: all_areas["Gift Shop"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Gift Shop"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Gift Shop", NONSANITY_LOCATION_KEY: "Mt. Holly Tee" }, @@ -349,7 +349,7 @@ bookshop_items = { "Bookshop - The History of Orindia (1st ed.)": { - LOCATION_ID_KEY: all_areas["Bookshop"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Bookshop"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Bookshop", NONSANITY_LOCATION_KEY: "History of Orindia (1st ed.)" }, @@ -611,7 +611,7 @@ "Courtyard", "Drafting Studio", "Guest Bedroom", - "Her Ladyship's Chambers", + "Her Ladyship's Chamber", "Laboratory", "Library", "Mail Room", @@ -649,7 +649,7 @@ "Closet", "Courtyard", "Greenhouse", - "Maids Chamber", + "Maid's Chamber", "Mail Room", "Patio", "Veranda", @@ -744,7 +744,7 @@ "Boiler Room", ]], options=rare_logic_filter - ) | AdvancedExperimentRule() | TradingPostRule("SHOVEL") | SpiralOfStarsRule() | + ) | TradingPostRule("SHOVEL") | SpiralOfStarsRule() | # Also from AdvancedExperimentRule, but that requires SHOVEL UpgradedRoomRule("Mail Room", "Freight Mail") | UpgradedRoomRule("Spare Room", "Spare Greenroom") | UpgradedRoomRule("Spare Room", "Spare Patio") | UpgradedRoomRule("Spare Room", "Spare Veranda") | UpgradedRoomRule("Spare Room", "Spare Terrace"), }, "SLEDGE HAMMER First Pickup": { @@ -813,7 +813,7 @@ LOCATION_ITEM_KEY: "TELESCOPE", LOCATION_RULE_SIMPLE_COMMON: Or( *[CanReachRegion(region) for region in [ - "Her Ladyship's Chambers", + "Her Ladyship's Chamber", "Walk-In Closet", "Planetarium", ]] @@ -951,7 +951,7 @@ *[CanReachRegion(region) for region in [ "Closet", "Music Room", - "Her Ladyship's Chambers", + "Her Ladyship's Chamber", "Locksmith", "Billiard Room", # Dartboard puzzle "Mail Room", # Packages @@ -1021,7 +1021,7 @@ LOCATION_ITEM_KEY: "CROWN", }, "CURSED EFFIGY First Pickup": { - LOCATION_ID_KEY: all_areas["Shrine"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Shrine"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Shrine", LOCATION_ITEM_KEY: "CURSED EFFIGY", LOCATION_RULE_SIMPLE_COMMON: And( @@ -1032,23 +1032,23 @@ ), }, "DIARY KEY First Pickup": { - LOCATION_ID_KEY: all_areas["Her Ladyship's Chambers"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, - LOCATION_ROOM_KEY: "Her Ladyship's Chambers", + LOCATION_ID_KEY: all_areas["Her Ladyship's Chamber"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, + LOCATION_ROOM_KEY: "Her Ladyship's Chamber", LOCATION_ITEM_KEY: "DIARY KEY", }, "KEY of Aries First Pickup": { - LOCATION_ID_KEY: all_areas["Aries Court"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Aries Court"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Aries Court", LOCATION_ITEM_KEY: "KEY of Aries", }, "LUNCH BOX First Pickup": { - LOCATION_ID_KEY: all_areas["Dining Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Dining Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Dining Room", LOCATION_ITEM_KEY: "LUNCH BOX", LOCATION_RULE_SIMPLE_COMMON: CanReachRegion("Gift Shop"), }, "MICROCHIP 1 First Pickup": { - LOCATION_ID_KEY: all_areas["West Path"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["West Path"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "West Path", LOCATION_ITEM_KEY: "MICROCHIP 1", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("SHOVEL"), @@ -1062,7 +1062,7 @@ ), }, "MICROCHIP 3 First Pickup": { - LOCATION_ID_KEY: all_areas["Blackbridge Grotto"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Blackbridge Grotto"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Blackbridge Grotto", LOCATION_ITEM_KEY: "MICROCHIP 3", }, @@ -1072,7 +1072,7 @@ LOCATION_ITEM_KEY: "PAPER CROWN", }, "ROYAL SCEPTER First Pickup": { - LOCATION_ID_KEY: all_areas["Treasure Trove"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Treasure Trove"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Treasure Trove", LOCATION_ITEM_KEY: "ROYAL SCEPTER", LOCATION_RULE_SIMPLE_COMMON: And( @@ -1156,28 +1156,28 @@ LOCATION_ROOM_KEY: "Office", }, "Upgrade Disk - Morning Room": { - LOCATION_ID_KEY: all_areas["Morning Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Morning Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Morning Room", }, - "Upgrade Disk - Her Ladyship's Chambers": { - LOCATION_ID_KEY: all_areas["Her Ladyship's Chambers"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 1, - LOCATION_ROOM_KEY: "Her Ladyship's Chambers", + "Upgrade Disk - Her Ladyship's Chamber": { + LOCATION_ID_KEY: all_areas["Her Ladyship's Chamber"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 1, + LOCATION_ROOM_KEY: "Her Ladyship's Chamber", }, "Upgrade Disk - Commissary": { - LOCATION_ID_KEY: all_areas["Commissary"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Commissary"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Commissary", }, "Upgrade Disk - Garage": { - LOCATION_ID_KEY: all_areas["Garage"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Garage"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Garage", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("CAR KEYS"), }, "Upgrade Disk - Great Hall": { - LOCATION_ID_KEY: all_areas["Great Hall"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Great Hall"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Great Hall", }, "Upgrade Disk - Vault": { - LOCATION_ID_KEY: all_areas["Vault"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Vault"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Vault", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("VAULT KEY 304"), }, @@ -1189,7 +1189,7 @@ ), }, "Upgrade Disk - Freezer": { - LOCATION_ID_KEY: all_areas["Freezer"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Freezer"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Freezer", LOCATION_RULE_SIMPLE_COMMON: Or( CanReachItemLocation("Burning Glass"), CanReachItemLocation("TORCH"), CanReachItemLocation("Power Hammer"), CanReachRegion("Furnace") @@ -1200,24 +1200,24 @@ LOCATION_ROOM_KEY: "Tomb", }, "Upgrade Disk - The Foundation": { - LOCATION_ID_KEY: all_areas["The Foundation"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["The Foundation"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "The Foundation", }, "Upgrade Disk - Abandoned Mine": { - LOCATION_ID_KEY: all_areas["Abandoned Mine"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Abandoned Mine"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Abandoned Mine", }, "Upgrade Disk - Lost And Found": { - LOCATION_ID_KEY: all_areas["Lost And Found"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Lost And Found"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Lost And Found", }, "Upgrade Disk - Mechanarium": { - LOCATION_ID_KEY: all_areas["Mechanarium"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Mechanarium"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Mechanarium", LOCATION_RULE_SIMPLE_COMMON: MechanariumDoorRule(3), }, "Upgrade Disk - Archives": { - LOCATION_ID_KEY: all_areas["Archives"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Archives"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Archives", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("CABINET KEY 1"), }, @@ -1264,7 +1264,7 @@ LOCATION_RULE_SIMPLE_COMMON: Or( *[CanReachRegion(region) for region in [ "Conference Room", - "Her Ladyship's Chambers", + "Her Ladyship's Chamber", "Walk-In Closet", "Hovel", ] @@ -1291,12 +1291,12 @@ LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("VAULT KEY 370") }, "Sanctum Key - Clock Tower": { - LOCATION_ID_KEY: all_areas["Clock Tower"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Clock Tower"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Clock Tower", LOCATION_ITEM_KEY: "SANCTUM KEY CLOCK TOWER" }, "Sanctum Key - Reservoir Bottom": { - LOCATION_ID_KEY: all_areas["Reservoir Bottom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Reservoir Bottom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Reservoir Bottom", LOCATION_ITEM_KEY: "SANCTUM KEY RESERVOIR" }, @@ -1306,12 +1306,12 @@ LOCATION_ITEM_KEY: "SANCTUM KEY THRONE ROOM" }, "Sanctum Key - Safehouse": { - LOCATION_ID_KEY: all_areas["Safehouse"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Safehouse"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Safehouse", LOCATION_ITEM_KEY: "SANCTUM KEY SAFEHOUSE" }, "Sanctum Key - Music Room": { - LOCATION_ID_KEY: all_areas["Music Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Music Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Music Room", LOCATION_ITEM_KEY: "SANCTUM KEY MUSIC ROOM" }, @@ -1325,13 +1325,13 @@ file_cabinet_keys = { "File Cabinet Key - Patio": { - LOCATION_ID_KEY: all_areas["Patio"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Patio"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Patio", LOCATION_ITEM_KEY: "CABINET KEY 1", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("SHOVEL"), }, "File Cabinet Key - Laundry Room": { - LOCATION_ID_KEY: all_areas["Laundry Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Laundry Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Laundry Room", LOCATION_ITEM_KEY: "CABINET KEY 2", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("SHOVEL"), @@ -1351,7 +1351,7 @@ LOCATION_ROOM_KEY: "West Path", }, "Break Grounds to Sealed Entrance Wall": { - LOCATION_ID_KEY: all_areas["Sealed Entrance"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Sealed Entrance"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Sealed Entrance", }, "Break Basement to Sealed Entrance Wall": { @@ -1359,17 +1359,17 @@ LOCATION_ROOM_KEY: "Sealed Entrance", }, "Break Weight Room Wall": { - LOCATION_ID_KEY: all_areas["Weight Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Weight Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Weight Room", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("Power Hammer") }, "Break Greenhouse Wall": { - LOCATION_ID_KEY: all_areas["Greenhouse"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Greenhouse"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Greenhouse", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("Power Hammer") }, "Break Secret Garden Wall": { - LOCATION_ID_KEY: all_areas["Secret Garden"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Secret Garden"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Secret Garden", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("Power Hammer") }, @@ -1384,12 +1384,12 @@ ), }, "Break Precipice Wall": { - LOCATION_ID_KEY: all_areas["The Precipice"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["The Precipice"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "The Precipice", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("Power Hammer") }, "Break Tunnel Wall": { - LOCATION_ID_KEY: all_areas["Tunnel Area Past Sealed Door"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Tunnel Area Past Sealed Door"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Tunnel Area Past Sealed Door", }, "Unlock Basement Door The Foundation": { @@ -1398,16 +1398,16 @@ LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("BASEMENT KEY") }, "Unlock Basement Door The Well": { - LOCATION_ID_KEY: all_areas["The Well"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["The Well"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "The Well", LOCATION_RULE_SIMPLE_COMMON: CanReachItemLocation("BASEMENT KEY") }, "Unlock Basement Door Tunnel Area": { - LOCATION_ID_KEY: all_areas["Tunnel Area Past Basement key Door"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Tunnel Area Past Basement key Door"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Tunnel Area Past Basement key Door", }, "Solve Tomb Puzzle 1": { - LOCATION_ID_KEY: all_areas["Catacombs"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Catacombs"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Catacombs", }, "Solve Tomb Puzzle 2": { @@ -1463,19 +1463,19 @@ ) }, "Gasline Valve - Orchard": { - LOCATION_ID_KEY: all_areas["Apple Orchard"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Apple Orchard"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Apple Orchard", }, "Gasline Valve - Schoolhouse": { - LOCATION_ID_KEY: all_areas["Schoolhouse"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Schoolhouse"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Schoolhouse", }, "Gasline Valve - Hovel": { - LOCATION_ID_KEY: all_areas["Hovel"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Hovel"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Hovel", }, "Gasline Valve - Gemstone Cavern": { - LOCATION_ID_KEY: all_areas["Gemstone Cavern"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Gemstone Cavern"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Gemstone Cavern", }, "Scorch Sundial": { @@ -1487,16 +1487,16 @@ ) }, "VAC Controls": { - LOCATION_ID_KEY: all_areas["Utility Closet"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Utility Closet"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Utility Closet", }, # Almost all of the other Allowance Tokens are directly behind/next to another location "Allowance Token - Cloister Statue": { - LOCATION_ID_KEY: all_areas["Cloister"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Cloister"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Cloister", }, "Allowance Token - Outer Entrance Hall Vase": { - LOCATION_ID_KEY: all_areas["Outer Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Outer Room"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Outer Room", LOCATION_RULE_SIMPLE_COMMON: And( CanReachRegion("Shrine"), @@ -1512,7 +1512,7 @@ }, # Ignoring deposit box allowance tokens for now, since they are missable (don't respawn if not picked up) "Dig up The Bedroom Treasure Chest": { - LOCATION_ID_KEY: all_areas["Bedroom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 0, + LOCATION_ID_KEY: all_areas["Bedroom"][ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 100, LOCATION_ROOM_KEY: "Bedroom", LOCATION_RULE_SIMPLE_COMMON: And( CanReachItemLocation("SHOVEL"), @@ -1533,6 +1533,21 @@ # Reservoir/Treasure Trove chests? (46 in Trove, 23 in Reservoir) +# Gallery painting puzzles + +# Boiler solve +# Blackbridge Opened (Labratory Puzzle) +# Well Drained +# Pool Drained +# Room Upgrades + +# Possible locations that are currently Events: +# Raise Satellite +# Sanctum Solves +# Ascend the Throne +# Unseal Blue Doors +# Levers + locations = trophies | safes_and_small_gates | mora_jai_boxes | floorplans | shop_items | upgrade_disks | keys | misc_locations | item_pickups | workshop_contraptions | doors_walls_and_gates LOCATIONS_BY_GROUPS |= { @@ -1549,4 +1564,5 @@ "Doors, Walls, and Gates": {k for k in doors_walls_and_gates}, "Gift Shop": {k for k in gift_shop_items}, "Bookshop": {k for k in bookshop_items}, + "Armory Purchases": {f"{k} First Pickup" for k in armory_items}, } \ No newline at end of file diff --git a/worlds/blueprince/data_rooms.py b/worlds/blueprince/data_rooms.py index d1a106f201c3..a4bbc8dd5d1c 100644 --- a/worlds/blueprince/data_rooms.py +++ b/worlds/blueprince/data_rooms.py @@ -1148,7 +1148,7 @@ OUTER_ROOM_KEY: False, ROOM_CHESS_PIECE_KEY: CHESS_PIECE_PAWN, }, - "Her Ladyship's Chambers": { + "Her Ladyship's Chamber": { ROOM_ITEM_ID_KEY: 309, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, @@ -1701,7 +1701,7 @@ OUTER_ROOM_KEY: False, ROOM_CHESS_PIECE_KEY: CHESS_PIECE_BISHOP, }, - "Maids Chamber": { + "Maid's Chamber": { ROOM_ITEM_ID_KEY: 705, ROOM_ITEM_CLASSIFICATION_KEY: ItemClassification.progression, ROOM_ITEM_SPOT_COUNT_KEY: 0, @@ -1950,4 +1950,15 @@ "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]], +} + +LOCATIONS_BY_GROUPS |= { + "Room Entrances": {f"{k} First Entering" for k in rooms}, + "Trunks": { + # Create 100 locked trunk check locations for each room that has the ability to have locked trunks + f"{k} Locked Trunk {idx}" + for k, v in rooms.items() + for idx in range(1, 101) + if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0 + } } \ No newline at end of file diff --git a/worlds/blueprince/items.py b/worlds/blueprince/items.py index ca1a105842cf..4c5df51e74d2 100644 --- a/worlds/blueprince/items.py +++ b/worlds/blueprince/items.py @@ -59,6 +59,7 @@ "Extra Starting Steps 1": 11201, "Extra Starting Steps 2": 11202, "Extra Starting Steps 5": 11203, + "Extra Starting Steps 10": 11204, # "Extra Stars 1": 11301, "Extra Stars 2": 11302, @@ -140,6 +141,7 @@ "Extra Starting Steps 1": ItemClassification.filler, "Extra Starting Steps 2": ItemClassification.filler, "Extra Starting Steps 5": ItemClassification.filler, + "Extra Starting Steps 10": ItemClassification.filler, # "Extra Stars 1": ItemClassification.filler, "Extra Stars 2": ItemClassification.filler, @@ -401,7 +403,8 @@ def create_all_items(world: BluePrinceWorld) -> None: exclude = [item for item in world.multiworld.precollected_items[world.player]] - standard_item_list = [world.create_item(k) for k in other_items if k not in ["LUNCH BOX", "CURSED EFFIGY"] or world.options.goal_type.value > 1] + standard_item_list = [world.create_item(k) for k in other_items if (k not in ["LUNCH BOX", "CURSED EFFIGY"] or world.options.goal_type.value > 1) + and (k not in ["CROWN", "ROYAL SCEPTER"] or world.options.goal_type.value > 2)] if world.options.standard_item_sanity: itempool += standard_item_list else: @@ -419,7 +422,8 @@ def create_all_items(world: BluePrinceWorld) -> None: else: to_precollect += upgrade_disk_item_list - key_item_list = [world.create_item(k) for k in keys] + key_item_list = [world.create_item(k) for k in keys if (k not in sanctum_keys or world.options.goal_type.value > 1) + and (k not in ["KEY of Aries"] or world.options.goal_type.value > 2)] if world.options.key_sanity: itempool += key_item_list else: @@ -442,16 +446,22 @@ def create_all_items(world: BluePrinceWorld) -> None: room_item_list = [world.create_item(room) for room in rooms if room not in core_rooms and room not in ["Secret Garden", "Room 8"] and room not in classrooms] if world.options.room_draft_sanity: - world.starting_rooms = world.random.choices( - [room for room in room_item_list if ROOM_PICK_POSITIONS_KEY in rooms[room.name] and (set(rooms[room.name][ROOM_PICK_POSITIONS_KEY]) & ENTRANCE_HALL_DRAFTABLE) and not (room.name == "Gift Shop" and world.options.goal_type.value <= 1)] + [data_rooms.progressive_classroom[0]], + world.starting_rooms = world.random.choices([room for room in room_item_list + if ROOM_PICK_POSITIONS_KEY in rooms[room.name] + and (set(rooms[room.name][ROOM_PICK_POSITIONS_KEY]) & ENTRANCE_HALL_DRAFTABLE) + and room.name not in ["Sauna"] + and not (room.name in ["Treasure Trove", "Gift Shop"] and world.options.goal_type.value <= 1)], k=world.options.starting_room_amount.value, ) world.starting_rooms += [r for r in room_item_list if r.name == "Closet"] - itempool += [room for room in room_item_list if room not in world.starting_rooms and not (room.name == "Gift Shop" and world.options.goal_type.value <= 1)] + itempool += [room for room in room_item_list if room not in world.starting_rooms + and not (room.name in ["Treasure Trove", "Gift Shop"] and world.options.goal_type.value <= 1)] to_precollect += world.starting_rooms else: # 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) and not (room.name == "Gift Shop" and world.options.goal_type.value <= 1)] + 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) + and not (room.name in ["Treasure Trove", "Gift Shop"] and world.options.goal_type.value <= 1)] if world.options.room_draft_sanity: n = len([room for room in world.starting_rooms if room in data_rooms.progressive_classroom]) diff --git a/worlds/blueprince/locations.py b/worlds/blueprince/locations.py index 7b2c0d432864..79e142831ad4 100644 --- a/worlds/blueprince/locations.py +++ b/worlds/blueprince/locations.py @@ -13,7 +13,7 @@ from .data_rooms import rooms, blue_rooms, core_rooms from .data_items import armory_items -from .data_other_locations import locations, keys, floorplans, shop_items, trophies +from .data_other_locations import locations, keys, floorplans, shop_items, trophies, sanctum_keys, aries_court_mora_jai_boxes from .items import BluePrinceItem if TYPE_CHECKING: @@ -29,13 +29,13 @@ f"{k} First Entering": v[ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER for k, v in rooms.items() } - # | { - # # Create 100 locked trunk check locations for each room that has the ability to have locked trunks - # f"{k} Locked Trunk {idx}": v[ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 10_000 + idx - # for k, v in rooms.items() - # for idx in range(1, 101) - # if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0 - # } + | { + # Create 100 locked trunk check locations for each room that has the ability to have locked trunks + f"{k} Locked Trunk {idx}": v[ROOM_ITEM_ID_KEY] * ROOM_MULTIPLIER + 10_000 + idx + for k, v in rooms.items() + for idx in range(1, 101) + if v[ROOM_CHEST_SPOT_COUNT_KEY] > 0 + } | { # Add First Pickup as locations for armory items. f"{k} First Pickup": v[ITEM_ID_KEY] * ROOM_MULTIPLIER @@ -58,19 +58,17 @@ def create_all_locations(world: BluePrinceWorld) -> None: def create_regular_locations(world: BluePrinceWorld) -> None: + locations_to_setup : dict[str, Rule] = dict() + armory = world.get_region("The Armory") - LOCATIONS_BY_GROUPS["Armory Purchases"] = set() # 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"].add(location_key) - - LOCATIONS_BY_GROUPS["Room Entrances"] = set() - LOCATIONS_BY_GROUPS["Trunks"] = set() + for room_key, v in rooms.items(): - if world.options.goal_type.value < 2 and room_key in ["Room 46", "Gift Shop"]: + if world.options.goal_type.value < 2 and room_key in ["Room 46", "Gift Shop", "Treasure Trove"]: continue # Skip Rooms that are past or at the goal if world.options.goal_type.value < 1 and room_key in ["Antechamber"]: @@ -85,33 +83,63 @@ 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"].add(location_key) # Add Nth locked trunk open - # 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 + 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"].update(trunks) + 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) # These trunks require extra logic - # if room_key == "Entrance Hall": - # for idx in range(1, trunk_count + 1): - # world.set_rule(world.get_location(f"Entrance Hall Locked Trunk {idx}"), lambda state: state.can_reach_region("Observatory", world.player) or state.can_reach_region("Laboratory", world.player)) + if room_key == "Entrance Hall": + for idx in range(1, trunk_count + 1): + world.set_rule(world.get_location(f"Entrance Hall Locked Trunk {idx}"), lambda state: state.can_reach_region("Observatory", world.player) or state.can_reach_region("Laboratory", world.player)) - # elif room_key == "The Pool": - # for idx in range(1, trunk_count + 1): - # world.set_rule(world.get_location(f"The Pool Locked Trunk {idx}"), lambda state: state.can_reach_region("Gift Shop", world.player)) - - locations_to_setup = [] + elif room_key == "The Pool": + for idx in range(1, trunk_count + 1): + world.set_rule(world.get_location(f"The Pool Locked Trunk {idx}"), lambda state: state.can_reach_region("Gift Shop", world.player)) for k, v in locations.items(): if world.options.goal_type.value < 4 and k in ["Ascend The Throne", "Throne of the Blue Prince Mora Jai Box"]: continue - if world.options.goal_type.value < 2 and k in ["LUNCH BOX First Pickup", "CURSED EFFIGY First Pickup", "Gift Shop - Mt. Holly Tee", "Gift Shop - Lunch Box", "Gift Shop - Swim Trunks", "Gift Shop - Swim Bird Plushie", "Gift Shop - Blue Tents", "Gift Shop - Cursed Coffers"]: + if world.options.goal_type.value < 3 and (k in aries_court_mora_jai_boxes or k in ["KEY of Aries First Pickup", "ROYAL SCEPTER First Pickup"]): + continue + + if world.options.goal_type.value < 2 and (k in sanctum_keys or + k in ["LUNCH BOX First Pickup", + "CURSED EFFIGY First Pickup", + "Cursed Coffers", + "CROWN First Pickup", + "Underpass Mora Jai Box", + "Treasure Trove Floorplan", + "Gift Shop - Mt. Holly Tee", + "Gift Shop - Lunch Box", + "Gift Shop - Swim Trunks", + "Gift Shop - Swim Bird Plushie", + "Gift Shop - Blue Tents", + "Gift Shop - Cursed Coffers"] + or k in [f"Solved {s}" for s in [ + "Orinda Aries Sanctum", + "Fenn Aries Sanctum", + "Arch Aries Sanctum", + "Eraja Sanctum", + "Corarica Sanctum", + "Mora Jai Sanctum", + "Verra Sanctum", + "Nuance Sanctum", + ]] or k in [f"{s} Mora Jai Box" for s in [ + "Orinda Aries Sanctum", + "Fenn Aries Sanctum", + "Arch Aries Sanctum", + "Eraja Sanctum", + "Corarica Sanctum", + "Mora Jai Sanctum", + "Verra Sanctum", + "Nuance Sanctum", + ]]): continue # Skip locations that are past or at the goal if world.options.trophy_sanity == False and k in trophies: @@ -125,8 +153,7 @@ def create_regular_locations(world: BluePrinceWorld) -> None: reg.locations.append(loc) - - world.set_rule(world.get_location(k), get_location_rule(k)) + locations_to_setup[k] = get_location_rule(k) continue if NONSANITY_LOCATION_KEY in v and world.options.room_draft_sanity == False and k in floorplans.keys(): @@ -138,7 +165,7 @@ def create_regular_locations(world: BluePrinceWorld) -> None: reg.locations.append(loc) - world.set_rule(world.get_location(k), get_location_rule(k)) + locations_to_setup[k] = get_location_rule(k) continue if LOCATION_ITEM_KEY in v and world.options.key_sanity == False and k in keys.keys(): if v[LOCATION_ITEM_KEY] != STARTING_INVENTORY: @@ -149,24 +176,29 @@ def create_regular_locations(world: BluePrinceWorld) -> None: reg.locations.append(loc) - world.set_rule(world.get_location(k), get_location_rule(k)) + locations_to_setup[k] = get_location_rule(k) continue location_key = k locs = get_location_names_with_ids([location_key]) world.get_region(v[LOCATION_ROOM_KEY]).add_locations(locs, BluePrinceLocation) - locations_to_setup.append(location_key) + locations_to_setup[location_key] = get_location_rule(location_key) - for location_key in locations_to_setup: - world.set_rule(world.get_location(location_key), get_location_rule(location_key)) + for location_key, rule in locations_to_setup.items(): + world.set_rule(world.get_location(location_key), rule) def get_location_rule(location_key: str) -> Rule: location_data = locations[location_key] if LOCATION_RULE_SIMPLE_COMMON not in location_data: return True_() + + rule = location_data[LOCATION_RULE_SIMPLE_COMMON] - return location_data[LOCATION_RULE_SIMPLE_COMMON] + if isinstance(rule, Rule): # Added this because pylance was showing an error + return rule + + raise Exception(f"Invalid rule for location {location_key}: {rule}") def create_events(world: BluePrinceWorld) -> None: @@ -237,7 +269,7 @@ def create_events(world: BluePrinceWorld) -> None: "Ascend The Throne", (CanReachItemLocation("CROWN") & CanReachItemLocation("ROYAL SCEPTER") & - CanReachItemLocation("CURSED EFFIGY")).resolve(world), + CanReachItemLocation("CURSED EFFIGY")), location_type=BluePrinceLocation, item_type=items.BluePrinceItem, ) @@ -247,14 +279,14 @@ def create_events(world: BluePrinceWorld) -> None: "Ascend The Throne", (CanReachItemLocation("CROWN") & CanReachItemLocation("ROYAL SCEPTER") & - CanReachItemLocation("CURSED EFFIGY")).resolve(world), + CanReachItemLocation("CURSED EFFIGY")), location_type=BluePrinceLocation, item_type=items.BluePrinceItem, ) throne_room.add_event( "Unseal Blue Doors", "Blue Door Access", - HasFromList(*[x for x in blue_rooms if x not in core_rooms], count = 8).resolve(world), + HasFromList(*[x for x in blue_rooms if x not in core_rooms], count = 8), location_type=BluePrinceLocation, item_type=items.BluePrinceItem, ) @@ -288,8 +320,8 @@ def create_events(world: BluePrinceWorld) -> None: "Satellite Raised", And( *[CanReachItemLocation(x) for x in ["MICROCHIP 1", "MICROCHIP 2", "MICROCHIP 3"]], - CanReachItemLocation("Scorch Sundial") - ).resolve(world), + CanReachLocation("Scorch Sundial") + ), location_type=BluePrinceLocation, item_type=items.BluePrinceItem, ) @@ -298,6 +330,9 @@ def create_events(world: BluePrinceWorld) -> None: for k, v in rooms.items(): if v[ROOM_CHESS_PIECE_KEY] == CHESS_PIECE_NONE: continue + if world.options.goal_type.value < 2 and k in ["Treasure Trove"]: + continue # Skip Rooms that are past or at the goal + world.get_region(k).add_event( f"Has {k} Chess Piece", f"Chess Piece {v[ROOM_CHESS_PIECE_KEY]}", diff --git a/worlds/blueprince/regions.py b/worlds/blueprince/regions.py index d051802bca50..95e47647b33e 100644 --- a/worlds/blueprince/regions.py +++ b/worlds/blueprince/regions.py @@ -136,6 +136,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: foundation = world.get_region("The Foundation") entrance_hall = world.get_region("Entrance Hall") antechamber = world.get_region("Antechamber") + pool = world.get_region("The Pool") # Go through the rooms and connect them to the outer room/campsite (starting area) for k, v in rooms.items(): @@ -163,32 +164,32 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: (CanReachPickPosition("Antechamber") & ( CanReachRegion("Great Hall") | - (CanReachRegion("Greenhouse") & CanReachItemLocation("BROKEN LEVER")) | + (CanReachRegion("Greenhouse") & CanReachItemLocation("BROKEN LEVER", parent_region_name="Entrance Hall")) | MechanariumDoorRule(1) | - (CanReachRegion("Weight Room") & CanReachItemLocation("Power Hammer")) | + (CanReachRegion("Weight Room") & CanReachItemLocation("Power Hammer", parent_region_name="Entrance Hall")) | CanReachRegion("Secret Garden") # This check is redundant # (CanReachRegion("Secret Garden") & CanReachItemLocation("Power Hammer")) - )).resolve(world), + )), ) elif k == "Room 46": antechamber.connect( room, "Antechamber To Room 46", - Has("North Lever Access").resolve(world), + Has("North Lever Access"), ) elif k == "Bookshop": library.connect( room, "Library To Bookshop", - Has("Bookshop").resolve(world), + Has("Bookshop"), ) # Can only be drafted from the library, so only requires having the bookshop as an item. elif k == "The Armory": entrance_hall.connect( room, "Entrance Hall The Armory", - (CanReachRegion("Aries Court") & CanReachPickPosition("The Armory")).resolve(world), + (CanReachRegion("Aries Court") & CanReachPickPosition("The Armory")), ) # # This is only necessary if we track the day count @@ -204,31 +205,31 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: room, "Entrance Hall Trophy Room", ((CanReachRegion("Room 46") | Or( - CanReachItemLocation("Full House Trophy"), - CanReachItemLocation("Trophy of Invention"), - CanReachItemLocation("Trophy of Drafting"), - CanReachItemLocation("Trophy of Wealth"), + CanReachLocation("Full House Trophy", parent_region_name="Entrance Hall"), + CanReachLocation("Trophy of Invention", parent_region_name="Workshop"), + CanReachLocation("Trophy of Drafting", parent_region_name="Mail Room"), + CanReachLocation("Trophy of Wealth", parent_region_name="Showroom"), options=[OptionFilter(TrophySanity, True)] ) - ) & CanReachPickPosition("Trophy Room")).resolve(world), + ) & CanReachPickPosition("Trophy Room")), ) # Has reached Room 46 or has one of the 4 listed Trophies elif k == "Gift Shop": entrance_hall.connect( room, "Entrance Hall Gift Shop", - (CanReachRegion("Room 46") & CanReachPickPosition("Gift Shop")).resolve(world), + (CanReachRegion("Room 46") & CanReachPickPosition("Gift Shop")), ) # Has reached Room 46 elif k == "Room 8": entrance_hall.connect( room, "Entrance Hall Room 8", - (CanReachItemLocation("KEY 8") & CanReachPickPosition("Room 8", always_have=True)).resolve(world), + (CanReachItemLocation("KEY 8", parent_region_name="Entrance Hall") & CanReachPickPosition("Room 8", always_have=True)), ) # Has Key 8 elif k == "Secret Garden": entrance_hall.connect( room, "Entrance Hall Secret Garden", - (CanReachItemLocation("SECRET GARDEN KEY") & CanReachPickPosition("Secret Garden", always_have=True)).resolve(world), + (CanReachItemLocation("SECRET GARDEN KEY", parent_region_name="Entrance Hall") & CanReachPickPosition("Secret Garden", always_have=True)), ) elif k in classrooms and k != "Classroom 1": if k == "Classroom Exam": @@ -240,13 +241,27 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: world.get_region(prev).connect( room, f"{prev} {k}", - Has("Progressive Classroom", count=cnum).resolve(world), + Has("Progressive Classroom", count=cnum) & CanReachRegion("Schoolhouse"), ) elif k == "Classroom 1": entrance_hall.connect( room, "Entrance Hall Classroom 1", - Has("Progressive Classroom").resolve(world), + Has("Progressive Classroom"), + ) + + elif k in ["Boiler Room", "Pump Room", "Sauna"]: + pool.connect( + room, + f"The Pool To {k}", + Has(k) + ) + + elif k == "Morning Room": + entrance_hall.connect( + room, + "Entrance Hall Morning Room", + CanReachPickPosition("Morning Room") & CanReachRegion("Kitchen"), # Requires Kitchen or Breakfast Nook, but we don't handle room upgrades yet. ) # TODO: Add Her Ladyship's Chamber, it has weird requirements @@ -257,7 +272,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: entrance_hall.connect( room, f"Entrance Hall {k}", - CanReachPickPosition(k).resolve(world), + CanReachPickPosition(k), ) foundation.connect( @@ -276,35 +291,35 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: campsite.connect( gemstone_cavern, "Campsite To Gemstone Cavern", - (CanReachRegion("Utility Closet")).resolve(world), + (CanReachRegion("Utility Closet")), ) # Rules of are found in office emails. Solution is in office emails. May be able to adjust pattern? private_drive.connect( blakbridge_grotto, "Private Drive To Blackbridge Grotto", - (CanReachRegion("Boiler Room") & CanReachRegion("Laboratory")).resolve(world), + (CanReachRegion("Boiler Room") & CanReachRegion("Laboratory")), ) private_drive.connect(grounds, "Private Drive To Grounds") blakbridge_grotto.connect( orindian_ruins, "Blackbridge Grotto To Orindian Ruins", - (CanReachItemLocation("MICROCHIP 1") & CanReachItemLocation("MICROCHIP 2") & CanReachItemLocation("MICROCHIP 3")).resolve(world), + (CanReachItemLocation("MICROCHIP 1", parent_region_name="West Path") & CanReachItemLocation("MICROCHIP 2", parent_region_name="Entrance Hall") & CanReachItemLocation("MICROCHIP 3", parent_region_name="Blackbridge Grotto")), ) grounds.connect( the_precipice, "Grounds To Precipice", - (CanReachRegion("Apple Orchard") & CanReachRegion("Schoolhouse") & CanReachRegion("Hovel") & CanReachRegion("Gemstone Cavern")).resolve(world), + (CanReachRegion("Apple Orchard") & CanReachRegion("Schoolhouse") & CanReachRegion("Hovel") & CanReachRegion("Gemstone Cavern")), ) grounds.connect( sealed_entrance, "Grounds To Sealed Entrance", - (CanReachItemLocation("Power Hammer")).resolve(world), + (CanReachItemLocation("Power Hammer", parent_region_name="Workshop")), ) grounds.connect(entrance_hall, "Grounds To Entrance Hall") sealed_entrance.connect( grounds, "Sealed Entrance To Grounds", - (CanReachItemLocation("Power Hammer")).resolve(world), + (CanReachItemLocation("Power Hammer", parent_region_name="Workshop")), ) the_precipice.connect( aries_court, @@ -318,17 +333,17 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: "Chess Piece Bishop", "Chess Piece Pawn", ]] - ).resolve(world), + ), ) sealed_entrance.connect( basement, "Sealed Entrance To Basement", - CanReachItemLocation("Power Hammer").resolve(world), + CanReachItemLocation("Power Hammer", parent_region_name="Workshop"), ) basement.connect( sealed_entrance, "Basement To Sealed Entrance", - CanReachItemLocation("Power Hammer").resolve(world), + CanReachItemLocation("Power Hammer", parent_region_name="Workshop"), ) basement.connect( reservoir_gear_side, @@ -350,52 +365,52 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: inner_sanctum.connect( orinda_aries_sanctum, "Inner Sanctum To Orinda Aries Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=1).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=1), ) inner_sanctum.connect( fenn_aries_sanctum, "Inner Sanctum To Fenn Aries Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=2).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=2), ) inner_sanctum.connect( arch_aries_sanctum, "Inner Sanctum To Arch Aries Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=3).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=3), ) inner_sanctum.connect( eraja_sanctum, "Inner Sanctum To Eraja Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=4).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=4), ) inner_sanctum.connect( corarica_sanctum, "Inner Sanctum To Corarica Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=5).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=5), ) inner_sanctum.connect( mora_jai_sanctum, "Inner Sanctum To Mora Jai Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=6).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=6), ) inner_sanctum.connect( verra_sanctum, "Inner Sanctum To Verra Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=7).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=7), ) inner_sanctum.connect( nuance_sanctum, "Inner Sanctum To Nuance Sanctum", - CanReachItemLocationsFromList(*sanctum_keys, count=8).resolve(world), + CanReachItemLocationsFromList(*sanctum_keys, count=8), ) abandoned_mine.connect( excavation_tunnel, "Abandoned Mine To Excavation Tunnel", - CanReachRegion("Reservoir Fountain Side").resolve(world), + CanReachRegion("Reservoir Fountain Side"), ) excavation_tunnel.connect( abandoned_mine, "Excavation Tunnel To Abandoned Mine", - CanReachRegion("Reservoir Fountain Side").resolve(world), + CanReachRegion("Reservoir Fountain Side"), ) excavation_tunnel.connect( torch_chamber, @@ -412,7 +427,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: the_well.connect( reservoir_fountain_side, "Well To Reservoir Fountain Side", - CanReachItemLocation("BASEMENT KEY").resolve(world), + CanReachItemLocation("BASEMENT KEY", parent_region_name="Antechamber"), ) west_path.connect( @@ -434,23 +449,23 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: garage.connect( west_path, "Garage To West Path", - Or(CanReachRegion("Utility Closet"), CanReachRegion("Boiler Room")).resolve(world), + Or(CanReachRegion("Utility Closet"), CanReachRegion("Boiler Room")), ) foundation_elevator.connect( basement, "Foundation Elevator To Basement", And( CanReachRegion("The Foundation"), - CanReachItemLocation("BASEMENT KEY"), - ).resolve(world), + CanReachItemLocation("BASEMENT KEY", parent_region_name="Antechamber"), + ), ) torch_chamber.connect( the_precipice, "Torch Chamber To Precipice", Or( - CanReachItemLocation("Burning Glass"), - CanReachItemLocation("TORCH"), - ).resolve(world), + CanReachItemLocation("Burning Glass", parent_region_name="Workshop"), + CanReachItemLocation("TORCH", parent_region_name="The Armory"), + ), ) grounds.connect( @@ -461,12 +476,12 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: tunnel_area_post_crates, "Tunnel Area Entrance To Tunnel Area Post Crates", And( - CanReachRegion("Satellite Raised"), + Has("Satellite Raised"), Or( CanReachRegion("Laboratory"), CanReachRegion("Blackbridge Grotto"), ), - ).resolve(world), + ), ) tunnel_area_post_crates.connect( tunnel_area_post_normal_locked_door, @@ -475,39 +490,39 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: tunnel_area_post_normal_locked_door.connect( tunnel_area_post_basement_key_door, "Tunnel Area Post Normal Locked Door to Tunnel Area Post Basement Key", - CanReachItemLocation("BASEMENT KEY").resolve(world), + CanReachItemLocation("BASEMENT KEY", parent_region_name="Antechamber"), ) tunnel_area_post_basement_key_door.connect( tunnel_area_post_security_door, "Tunnel Area Post Basement Key to Tunnel Area Post Security Door", - CanReachItemLocation("KEYCARD").resolve(world), + CanReachItemLocation("KEYCARD", parent_region_name="Entrance Hall"), ) tunnel_area_post_security_door.connect( tunnel_area_post_weak_wall, "Tunnel Area Post Security Door to Tunnel Area Post Weak Wall", - CanReachItemLocation("Power Hammer").resolve(world), + CanReachItemLocation("Power Hammer", parent_region_name="Workshop"), ) tunnel_area_post_weak_wall.connect( tunnel_area_post_red_door, "Tunnel Area Post Weak Wall to Tunnel Area Post Red Door", - CanReachRegion("Boiler Room").resolve(world), + CanReachRegion("Boiler Room"), ) tunnel_area_post_red_door.connect( tunnel_area_post_candle_door, "Tunnel Area Post Red Door to Tunnel Area Post Candle Door", Or( - CanReachItemLocation("Burning Glass"), - CanReachItemLocation("TORCH"), - ).resolve(world), + CanReachItemLocation("Burning Glass", parent_region_name="Workshop"), + CanReachItemLocation("TORCH", parent_region_name="The Armory"), + ), ) tunnel_area_post_candle_door.connect( tunnel_area_post_sealed_door, "Tunnel Area Post Candle Door to Tunnel Area Post Sealed Door", And( - CanReachItemLocation("MICROCHIP 1"), - CanReachItemLocation("MICROCHIP 2"), - CanReachItemLocation("MICROCHIP 3"), - ).resolve(world), + CanReachItemLocation("MICROCHIP 1", parent_region_name="West Path"), + CanReachItemLocation("MICROCHIP 2", parent_region_name="Entrance Hall"), + CanReachItemLocation("MICROCHIP 3", parent_region_name="Blackbridge Grotto"), + ), ) tunnel_area_post_sealed_door.connect( tunnel_area_post_blue_door, @@ -526,7 +541,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: CanReachPickPosition("Pump Room"), CanReachRegion("Reservoir Fountain Side"), CanReachRegion("Basement"), - ).resolve(world), + ), ) # Pump Room & Fountain Side Access. (take fountain side to gear side, lower again, and make it back down on gear side.) reservoir_gear_side.connect( reservoir_bottom, @@ -535,7 +550,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: CanReachRegion("Pump Room"), CanReachRegion("Reservoir Fountain Side"), CanReachRegion("Basement"), - ).resolve(world), + ), ) # Pump Room and boiler room (both this and safehouse require ability to get to gear side NOT through well side.) rotating_gear.connect( the_underpass, @@ -543,7 +558,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: And( CanReachRegion("Reservoir Fountain Side"), CanReachRegion("Reservoir Gear Side"), - ).resolve(world), + ), ) # Require Dual side access rotating_gear.connect( abandoned_mine, @@ -552,7 +567,7 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: reservoir_fountain_side.connect( reservoir_gear_side, "Reservoir Fountain Side To Reservoir Gear Side", - CanReachRegion("Pump Room").resolve(world), + CanReachRegion("Pump Room"), ) # Pump Room outer_room.connect( @@ -561,12 +576,12 @@ def create_and_connect_regions(world: BluePrinceWorld) -> None: And( CanReachRegion("Secret Passage"), CanReachRegion("Shrine"), - CanReachItemLocation("WATERING CAN"), - ).resolve(world), + CanReachItemLocation("WATERING CAN", parent_region_name="Entrance Hall"), + ), ) grounds.connect( the_well, "Grounds To The Well", - CanReachRegion("Pump Room").resolve(world), + CanReachRegion("Pump Room"), ) \ No newline at end of file diff --git a/worlds/blueprince/rules.py b/worlds/blueprince/rules.py index ce7c31f7fe1d..7f581b552751 100644 --- a/worlds/blueprince/rules.py +++ b/worlds/blueprince/rules.py @@ -14,8 +14,8 @@ from .data_items import * default_logic_filter = [OptionFilter(ItemLogicMode, ItemLogicMode.option_default)] -rare_logic_filter = [OptionFilter(ItemLogicMode, [ItemLogicMode.option_rare, ItemLogicMode.option_rare_complex, ItemLogicMode.option_extreme], operator="contains")] -complex_logic_filter = [OptionFilter(ItemLogicMode, [ItemLogicMode.option_complex, ItemLogicMode.option_rare_complex, ItemLogicMode.option_extreme], operator="contains")] +rare_logic_filter = [OptionFilter(ItemLogicMode, [ItemLogicMode.option_rare, ItemLogicMode.option_rare_complex, ItemLogicMode.option_extreme], operator="in")] +complex_logic_filter = [OptionFilter(ItemLogicMode, [ItemLogicMode.option_complex, ItemLogicMode.option_rare_complex, ItemLogicMode.option_extreme], operator="in")] extreme_logic_filter = [OptionFilter(ItemLogicMode, ItemLogicMode.option_extreme)] def set_all_rules(world: BluePrinceWorld) -> None: @@ -95,22 +95,18 @@ class CanReachItemLocation(Rule["BluePrinceWorld"], game="Blue Prince"): parent_region_name: str = "" @override def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: - from .data_other_locations import locations, armory_items, vault_keys, workshop_items + from .data_other_locations import locations, armory_items loc_name = self.location + " First Pickup" - if self.location in workshop_items: - loc_name = self.location + " First Craft" - elif self.location in vault_keys: - loc_name = self.location - - if self.parent_region_name == "": - self.parent_region_name = locations[loc_name][LOCATION_ROOM_KEY] if loc_name in locations else "" if loc_name in locations: return (Has(self.location) & CanReachLocation(loc_name, parent_region_name=self.parent_region_name)).resolve(world) + + if self.location in workshop_items: + return (Has(self.location) & CanReachLocation(f"{self.location} First Craft", parent_region_name="Workshop")).resolve(world) if self.location in armory_items: return (Has(self.location) & CanReachRegion("The Armory")).resolve(world) - + for location, data in locations.items(): if LOCATION_ITEM_KEY in data and data[LOCATION_ITEM_KEY] == self.location: return (Has(self.location) & CanReachLocation(location, parent_region_name=self.parent_region_name)).resolve(world) @@ -235,7 +231,8 @@ class AdvancedExperimentRule(Rule["BluePrinceWorld"], game="Blue Prince"): @override def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: return And(CanReachRegion("Laboratory"), Has("Satellite Raised"), options=extreme_logic_filter).resolve(world) - + +prev_trading_post_offers : set[str] = set() @dataclasses.dataclass() class TradingPostRule(Rule["BluePrinceWorld"], game="Blue Prince"): """ @@ -245,11 +242,15 @@ class TradingPostRule(Rule["BluePrinceWorld"], game="Blue Prince"): @override def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: - return And(CanReachRegion("Trading Post"), + + prev_trading_post_offers.add(self.item_name) + rule = And(CanReachRegion("Trading Post"), Or( - *[CanReachItemLocation(item) for item in self.get_trading_post_offers(self.item_name)] + *[CanReachItemLocation(item) for item in self.get_trading_post_offers(self.item_name) if self.item_name not in prev_trading_post_offers], ), options=complex_logic_filter).resolve(world) + prev_trading_post_offers.remove(self.item_name) + return rule # Trading can get any item of the same tier or lower def get_trading_post_offers(self, give: str) -> list[str]: @@ -291,7 +292,7 @@ def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: "Secret Garden", "Root Cellar", "Hovel", - "Kennel", + "The Kennel", "Dovecote", "Solarium", "Tunnel", @@ -334,7 +335,7 @@ class CarTrunkRule(Rule["BluePrinceWorld"], game="Blue Prince"): @override def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: - return (CanReachRegion("Garage") & Has("CAR KEYS")).resolve(world) + return (CanReachRegion("Garage") & CanReachItemLocation("CAR KEYS")).resolve(world) @dataclasses.dataclass() class SpiralOfStarsRule(Rule["BluePrinceWorld"], game="Blue Prince"): @@ -365,8 +366,6 @@ def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: if len(self.targets) == 1: return CanReachItemLocation(self.targets[0]).resolve(world) - - return self.Resolved(self.targets, self.count, player=world.player) class Resolved(Rule.Resolved): @@ -395,4 +394,42 @@ def _evaluate(self, state: CollectionState) -> bool: if reachable_count >= self.count: return True + return False + +@dataclasses.dataclass() +class CanReachRegionsFromList(Rule["BluePrinceWorld"], game="Blue Prince"): + """ + Check if the player can reach at least count regions from the list + """ + targets: tuple[str, ...] + count : int = 1 + + def __init__(self, *targets: str, count: int = 1): + self.targets = tuple(sorted(set(targets))) + self.count = count + + @override + def _instantiate(self, world: "BluePrinceWorld") -> Rule.Resolved: + if len(self.targets) == 0: + return False_().resolve(world) + if len(self.targets) == 1: + return CanReachRegion(self.targets[0]).resolve(world) + if self.count == 1: + return Or(*[CanReachRegion(target) for target in self.targets]).resolve(world) + return self.Resolved(self.targets, self.count, player=world.player) + + class Resolved(Rule.Resolved): + targets: tuple[str, ...] + count : int = 1 + + @override + def _evaluate(self, state: CollectionState) -> bool: + reachable_count = 0 + for target in self.targets: + if state.can_reach_region(target, self.player): + reachable_count += 1 + + if reachable_count >= self.count: + return True + return False \ No newline at end of file diff --git a/worlds/blueprince/test/__init__.py b/worlds/blueprince/test/__init__.py index b8a034a168d2..5be70bef3c3a 100644 --- a/worlds/blueprince/test/__init__.py +++ b/worlds/blueprince/test/__init__.py @@ -1,5 +1,6 @@ from typing import ClassVar +from rule_builder.rules import Rule from test.bases import WorldTestBase class BluePrinceTestBase(WorldTestBase): @@ -18,4 +19,10 @@ def debug_print_regions_items_locations(self, do_print: bool = False): print("Locations: [",", ".join([x.name for x in locations]), "]") else: return f"Regions: [{', '.join([x.name for x in regions])} ]\nItems: [{', '.join([f'{item}: {items[item]}' if items[item] > 1 else item for item in items])}]\nLocations: [{', '.join([x.name for x in locations])}]" + + def assertRuleTrue(self, rule: Rule, message=""): + self.assertTrue(rule.resolve(self.world)._evaluate(self.multiworld.state), message + "\n" + rule.resolve(self.world).explain_str(self.multiworld.state)) + + def assertRuleFalse(self, rule: Rule, message=""): + self.assertFalse(rule.resolve(self.world)._evaluate(self.multiworld.state), message + "\n" + rule.resolve(self.world).explain_str(self.multiworld.state)) \ No newline at end of file diff --git a/worlds/blueprince/test/sanity/test_draft_sanity.py b/worlds/blueprince/test/sanity/test_draft_sanity.py index c432c67f2596..033f338f8a12 100644 --- a/worlds/blueprince/test/sanity/test_draft_sanity.py +++ b/worlds/blueprince/test/sanity/test_draft_sanity.py @@ -30,7 +30,7 @@ def test_starting_room_count(self) -> None: # self.assertFalse(self.multiworld.state.has("Progressive Classroom", self.player)) def test_progressive_classroom(self) -> None: - # This fails occasionally; needs Fuzzer testing. + self.collect_all_but(["Progressive Classroom"]) for cnum in range(1, 10): current = f"Classroom {cnum}" if cnum < 9 else "Classroom Exam" @@ -61,7 +61,7 @@ def fulfills_accessibility() -> bool: self.assertTrue(state.can_reach_region("Secret Garden", 1)) self.assertTrue(state.can_reach_region("Room 8", 1)) for room in data_other_locations.directory_rooms: - if room not in ["Gift Shop"]: + if room not in ["Gift Shop", "Treasure Trove"]: self.assertTrue(state.has(room, self.player), f"Expected to have {room} in inventory for player {self.player}") self.assertTrue(sphere or self.multiworld.worlds[1].options.accessibility == "minimal", f"Unreachable locations: {locations}") @@ -72,38 +72,6 @@ def fulfills_accessibility() -> bool: state.collect(location.item, True, location) return self.multiworld.has_beaten_game(state, self.player) - with self.subTest("Game", game=self.game, seed=self.multiworld.seed): - distribute_items_restrictive(self.multiworld) - call_all(self.multiworld, "post_fill") - self.assertTrue(fulfills_accessibility(), "Collected all locations, but can't beat the game.") - placed_items = [loc.item for loc in self.multiworld.get_locations() if loc.item and loc.item.code] - self.assertLessEqual(len(self.multiworld.itempool), len(placed_items), - "Unplaced Items remaining in itempool") - - def test_fill2(self): - """Generates a multiworld and validates placements with the defined options""" - if not (self.run_default_tests and self.constructed): - return - from Fill import distribute_items_restrictive - - # basically a shortened reimplementation of this method from core, in order to force the check is done - def fulfills_accessibility() -> bool: - locations = list(self.multiworld.get_locations(1)) - state = CollectionState(self.multiworld) - while locations: - sphere: typing.List[Location] = [] - for n in range(len(locations) - 1, -1, -1): - if locations[n].can_reach(state): - sphere.append(locations.pop(n)) - self.assertTrue(sphere or self.multiworld.worlds[1].options.accessibility == "minimal", - f"Unreachable locations: {locations}") - if not sphere: - break - for location in sphere: - if location.item: - state.collect(location.item, True, location) - return self.multiworld.has_beaten_game(state, self.player) - with self.subTest("Game", game=self.game, seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) call_all(self.multiworld, "post_fill") diff --git a/worlds/blueprince/test/sanity/test_item_logic_complex.py b/worlds/blueprince/test/sanity/test_item_logic_complex.py index 62b7e3aa2ee0..8b385bcc1301 100644 --- a/worlds/blueprince/test/sanity/test_item_logic_complex.py +++ b/worlds/blueprince/test/sanity/test_item_logic_complex.py @@ -1,12 +1,19 @@ -from ...constants import * +import typing + +from ....AutoWorld import call_all + +from BaseClasses import Location, MultiWorld, CollectionState, Item from ...options import GoalType from ...test import BluePrinceTestBase +from ...data_rooms import rooms, core_rooms +from ... import data_rooms, data_other_locations class TestItemLogicComplex(BluePrinceTestBase): options = { "progression_balancing": 50, + "starting_room_amount": 3, "room_draft_sanity": False, "standard_item_sanity": True, "workshop_sanity": True, diff --git a/worlds/blueprince/test/sanity/test_no_draft_sanity.py b/worlds/blueprince/test/sanity/test_no_draft_sanity.py index 177cafd51345..3e23dca3c24f 100644 --- a/worlds/blueprince/test/sanity/test_no_draft_sanity.py +++ b/worlds/blueprince/test/sanity/test_no_draft_sanity.py @@ -1,6 +1,11 @@ +import typing + from ...constants import * +from BaseClasses import CollectionState, Location +from ....AutoWorld import call_all + from ...options import GoalType from ...test import BluePrinceTestBase from ...data_rooms import rooms, core_rooms @@ -26,4 +31,76 @@ def test_all_starting_room_items(self) -> None: if NONSANITY_LOCATION_KEY not in data or data[NONSANITY_LOCATION_KEY] == STARTING_INVENTORY: self.assertTrue(self.multiworld.state.has(room, self.player), f"Expected to have {room} in starting inventory for player {self.player}") else: - self.assertFalse(self.multiworld.state.has(room, self.player), f"Expected not to have {room} in starting inventory for player {self.player}") \ No newline at end of file + self.assertFalse(self.multiworld.state.has(room, self.player), f"Expected not to have {room} in starting inventory for player {self.player}") + + # Copied here for debugging. This is run by the base class, but its easier to debug with it here + def test_fill(self): + """Generates a multiworld and validates placements with the defined options""" + if not (self.run_default_tests and self.constructed): + return + from Fill import distribute_items_restrictive + + # basically a shortened reimplementation of this method from core, in order to force the check is done + def fulfills_accessibility() -> bool: + locations = list(self.multiworld.get_locations(1)) + state = CollectionState(self.multiworld) + spheres : list[list[str]] = [] + while locations: + sphere: typing.List[Location] = [] + for n in range(len(locations) - 1, -1, -1): + if locations[n].can_reach(state): + sphere.append(locations.pop(n)) + + spheres.append([]) + if not sphere: + break + for location in sphere: + if location.item: + state.collect(location.item, True, location) + spheres[-1].append(str(location) + ": " + str(location.item)) + + spheres_text = "\n".join([f"Sphere {i}: {', '.join(sphere)}" for i, sphere in enumerate(spheres)]) + print(spheres_text) + + return self.multiworld.has_beaten_game(state, self.player) + + with self.subTest("Game", game=self.game, seed=self.multiworld.seed): + distribute_items_restrictive(self.multiworld) + call_all(self.multiworld, "post_fill") + self.assertTrue(fulfills_accessibility(), "Collected all locations, but can't beat the game.") + placed_items = [loc.item for loc in self.multiworld.get_locations() if loc.item and loc.item.code] + self.assertLessEqual(len(self.multiworld.itempool), len(placed_items), + "Unplaced Items remaining in itempool") + + def test_fill2(self): + """Generates a multiworld and validates placements with the defined options""" + if not (self.run_default_tests and self.constructed): + return + from Fill import distribute_items_restrictive + + # basically a shortened reimplementation of this method from core, in order to force the check is done + def fulfills_accessibility() -> bool: + locations = list(self.multiworld.get_locations(1)) + state = CollectionState(self.multiworld) + while locations: + sphere: typing.List[Location] = [] + for n in range(len(locations) - 1, -1, -1): + if locations[n].can_reach(state): + sphere.append(locations.pop(n)) + self.assertTrue(sphere or self.multiworld.worlds[1].options.accessibility == "minimal", + f"Unreachable locations: {locations}") + if not sphere: + break + for location in sphere: + if location.item: + state.collect(location.item, True, location) + return self.multiworld.has_beaten_game(state, self.player) + + with self.subTest("Game", game=self.game, seed=self.multiworld.seed): + distribute_items_restrictive(self.multiworld) + call_all(self.multiworld, "post_fill") + call_all(self.multiworld, "finalize_multiworld") + self.assertTrue(fulfills_accessibility(), "Collected all locations, but can't beat the game.") + placed_items = [loc.item for loc in self.multiworld.get_locations() if loc.item and loc.item.code] + self.assertLessEqual(len(self.multiworld.itempool), len(placed_items), + "Unplaced Items remaining in itempool") \ No newline at end of file diff --git a/worlds/blueprince/test/sanity/test_trophy_sanity.py b/worlds/blueprince/test/sanity/test_trophy_sanity.py new file mode 100644 index 000000000000..660f4d368230 --- /dev/null +++ b/worlds/blueprince/test/sanity/test_trophy_sanity.py @@ -0,0 +1,61 @@ + + +from ...rules import CanReachItemLocation + +from ...options import GoalType +from ...data_other_locations import directory_rooms + +from .. import BluePrinceTestBase + + +class TrophySanity(BluePrinceTestBase): + options = { + "progression_balancing": 50, + "room_draft_sanity": True, + "standard_item_sanity": True, + "workshop_sanity": True, + "upgrade_disk_sanity": True, + "key_sanity": True, + "special_shop_sanity": True, + "trophy_sanity": True, + "goal_type": GoalType.option_blueprints, + } + + def test_full_house_requires_43_rooms(self): + self.assertFalse(self.can_reach_location("Full House Trophy"), "Expected not to be able to reach Full House Trophy without at least 43 rooms") + self.collect_by_name(["SECRET GARDEN KEY", "KEY 8"]) + self.collect_by_name(["Progressive Classroom"] * 9) + i = 2 + for room in directory_rooms: + if self.multiworld.state.has(room, self.player): + i += self.count(room) + + for room in directory_rooms: + if self.multiworld.state.has(room, self.player): + continue + i += 1 + self.collect_by_name(room) + if i < 43: + self.assertFalse(self.can_reach_location("Full House Trophy"), f"Expected not to be able to reach Full House Trophy with only {i} rooms") + else: + self.assertTrue(self.can_reach_location("Full House Trophy"), f"Expected to be able to reach Full House Trophy with {i} rooms") + break + + def test_invention_requires_workshop_and_materials(self): + self.collect_all_but(["Workshop", "SLEDGE HAMMER", "BROKEN LEVER", "BATTERY PACK", "SHOVEL", "METAL DETECTOR", "MAGNIFYING GLASS", "COMPASS", "COIN PURSE", "LUCKY RABBIT'S FOOT", "LOCK PICK KIT"]) + self.assertFalse(self.can_reach_location("Trophy of Invention"), "Expected not to be able to reach Trophy of Invention without Workshop and materials") + self.collect_by_name("Workshop") + self.assertFalse(self.can_reach_location("Trophy of Invention"), "Expected not to be able to reach Trophy of Invention without materials") + self.collect_by_name(["SLEDGE HAMMER", "BROKEN LEVER", "BATTERY PACK", "SHOVEL", "METAL DETECTOR", "MAGNIFYING GLASS", "COMPASS", "COIN PURSE", "LUCKY RABBIT'S FOOT", "LOCK PICK KIT"]) + + # self.assertTrue(self.can_reach_location("Power Hammer First Craft"), "Expected to be able to reach Power Hammer First Craft with Workshop and materials") + self.assertRuleTrue(CanReachItemLocation("Power Hammer"), "Expected to be able to reach Power Hammer with Workshop and materials") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Burning Glass"), "Expected to be able to reach Burning Glass First Craft with Workshop and materials") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Detector Shovel"), "Expected to be able to reach Detector Shovel First Craft with Workshop and materials;") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Dowsing Rod"), "Expected to be able to reach Dowsing Rod First Craft with Workshop and materials") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Electromagnet"), "Expected to be able to reach Electromagnet First Craft with Workshop and materials") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Jack Hammer"), "Expected to be able to reach Jack Hammer First Craft with Workshop and materials") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Lucky Purse"), "Expected to be able to reach Lucky Purse First Craft with Workshop and materials") # type: ignore + self.assertRuleTrue(CanReachItemLocation("Pick Sound Amplifier"), "Expected to be able to reach Pick Sound Amplifier First Craft with Workshop and materials") # type: ignore + + self.assertTrue(self.can_reach_location("Trophy of Invention"), "Expected to be able to reach Trophy of Invention with Workshop and materials") diff --git a/worlds/blueprince/test/test_location_access.py b/worlds/blueprince/test/test_location_access.py index 2a6ae17483be..946d9a7adb78 100644 --- a/worlds/blueprince/test/test_location_access.py +++ b/worlds/blueprince/test/test_location_access.py @@ -20,7 +20,7 @@ def test_all_location_ids_unique(self): 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.collect_by_name(["Laboratory", "The Pool", "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) self.assertTrue(self.can_reach_location("Raise Satellite"), "Raise Satellite should be reachable after having the microchips and burning glass") self.assertTrue(self.can_reach_region("Tunnel Area Past Crates"), "Tunnel Floorplan should be reachable after having crate experiment") diff --git a/worlds/blueprince/test/test_region_access.py b/worlds/blueprince/test/test_region_access.py index b0634114ac65..74feb11beef1 100644 --- a/worlds/blueprince/test/test_region_access.py +++ b/worlds/blueprince/test/test_region_access.py @@ -51,10 +51,13 @@ def test_outer_room_requires_garage_utility_closet(self) -> None: self.assertTrue(self.can_reach_region("Outer Room"), "Outer Room should be reachable after collecting the Garage as an item") def test_outer_room_requires_garage_boiler_room(self) -> None: - self.collect_all_but(["Garage", "Boiler Room", "Utility Closet"]) + self.collect_all_but(["Garage", "The Pool", "Boiler Room", "Utility Closet"]) if not self.multiworld.state.has("Garage", self.player): self.assertFalse(self.can_reach_region("Outer Room"), "Outer Room should not be reachable without having the Garage as an item") self.collect_by_name("Garage") + if not self.multiworld.state.has("The Pool", self.player): + self.assertFalse(self.can_reach_region("Outer Room"), "Outer Room should not be reachable without having The Pool as an item") + self.collect_by_name("The Pool") if not self.multiworld.state.has("Boiler Room", self.player): self.assertFalse(self.can_reach_region("Outer Room"), "Outer Room should not be reachable without having the Boiler Room as an item") self.collect_by_name("Boiler Room") @@ -83,6 +86,9 @@ def test_gemstone_cavern_requires_utility_closet(self) -> None: def test_blackbridge_grotto_requires_boiler_room_and_laboratory(self) -> None: self.collect_by_name("Hallway") + if not self.multiworld.state.has("The Pool", self.player): + self.assertFalse(self.can_reach_region("Blackbridge Grotto"), "Blackbridge Grotto should not be reachable without having The Pool as an item") + self.collect_by_name("The Pool") if not self.multiworld.state.has("Boiler Room", self.player): self.assertFalse(self.can_reach_region("Blackbridge Grotto"), "Blackbridge Grotto should not be reachable without having the Boiler Room as an item") self.collect_by_name("Boiler Room") @@ -105,7 +111,7 @@ def test_the_precipice_requires_gas_valves(self) -> None: self.assertTrue(self.can_reach_region("The Precipice"), "The Precipice should be reachable after having all Gas Valves") def test_orindian_ruins_requires_microchips(self) -> None: - self.collect_by_name(["Hallway", "Boiler Room", "Laboratory", "Garage", "SHOVEL", "SLEDGE HAMMER", "Attic"]) + self.collect_by_name(["Hallway", "The Pool", "Boiler Room", "Laboratory", "Garage", "SHOVEL", "SLEDGE HAMMER", "Attic"]) self.assertFalse(self.can_reach_region("Orindian Ruins"), "Orindian Ruins should not be reachable without having all Microchips") self.collect_by_name("MICROCHIP 1") self.assertFalse(self.can_reach_region("Orindian Ruins"), "Orindian Ruins should not be reachable without having all Microchips") diff --git a/worlds/blueprince/todo.md b/worlds/blueprince/todo.md deleted file mode 100644 index 313397268033..000000000000 --- a/worlds/blueprince/todo.md +++ /dev/null @@ -1,5 +0,0 @@ -Remove: -Key of Aries -CROWN and CROWN First Pickup -ROYAL SCEPTER -Sanctum Keys \ No newline at end of file diff --git a/worlds/blueprince/world.py b/worlds/blueprince/world.py index be27d0aa93d2..d6673c6c006d 100644 --- a/worlds/blueprince/world.py +++ b/worlds/blueprince/world.py @@ -27,7 +27,7 @@ class BluePrinceWorld(World): # Set the Options options_dataclass = blue_prince_options.BluePrinceOptions - options: blue_prince_options.BluePrinceOptions + options: blue_prince_options.BluePrinceOptions # type: ignore # 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.