From 8b91f9ff7270bfcc1275100298791bce058e29a3 Mon Sep 17 00:00:00 2001 From: Ian Robinson Date: Wed, 18 Feb 2026 14:57:05 -0500 Subject: [PATCH] Rule Builder: Make region.connect and add_event support rule builder (#5933) * make region.connect and add_event support rule builder * fix test * oops fix * update tests and typing * rm unused --- BaseClasses.py | 13 +++++++------ test/general/test_helpers.py | 26 +++++++++++++++++--------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 75036fc525..d1b9b5f6d3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -22,6 +22,7 @@ import Utils if TYPE_CHECKING: from entrance_rando import ERPlacementState + from rule_builder.rules import Rule from worlds import AutoWorld @@ -1368,7 +1369,7 @@ class Region: self, location_name: str, item_name: str | None = None, - rule: CollectionRule | None = None, + rule: CollectionRule | Rule[Any] | None = None, location_type: type[Location] | None = None, item_type: type[Item] | None = None, show_in_spoiler: bool = True, @@ -1396,7 +1397,7 @@ class Region: event_location = location_type(self.player, location_name, None, self) event_location.show_in_spoiler = show_in_spoiler if rule is not None: - event_location.access_rule = rule + self.multiworld.worlds[self.player].set_rule(event_location, rule) event_item = item_type(item_name, ItemClassification.progression, None, self.player) @@ -1407,7 +1408,7 @@ class Region: return event_item def connect(self, connecting_region: Region, name: Optional[str] = None, - rule: Optional[CollectionRule] = None) -> Entrance: + rule: Optional[CollectionRule | Rule[Any]] = None) -> Entrance: """ Connects this Region to another Region, placing the provided rule on the connection. @@ -1415,8 +1416,8 @@ class Region: :param name: name of the connection being created :param rule: callable to determine access of this connection to go from self to the exiting_region""" exit_ = self.create_exit(name if name else f"{self.name} -> {connecting_region.name}") - if rule: - exit_.access_rule = rule + if rule is not None: + self.multiworld.worlds[self.player].set_rule(exit_, rule) exit_.connect(connecting_region) return exit_ @@ -1441,7 +1442,7 @@ class Region: return entrance def add_exits(self, exits: Iterable[str] | Mapping[str, str | None], - rules: Mapping[str, CollectionRule] | None = None) -> List[Entrance]: + rules: Mapping[str, CollectionRule | Rule[Any]] | None = None) -> List[Entrance]: """ Connects current region to regions in exit dictionary. Passed region names must exist first. diff --git a/test/general/test_helpers.py b/test/general/test_helpers.py index 7e850f9744..71d1866561 100644 --- a/test/general/test_helpers.py +++ b/test/general/test_helpers.py @@ -1,9 +1,11 @@ import unittest -from typing import Callable, Dict, Optional +from typing import Any, Dict, Optional from typing_extensions import override -from BaseClasses import CollectionState, MultiWorld, Region +from BaseClasses import CollectionRule, MultiWorld, Region +from rule_builder.rules import Has, Rule +from test.general import TestWorld class TestHelpers(unittest.TestCase): @@ -16,6 +18,7 @@ class TestHelpers(unittest.TestCase): self.multiworld.game[self.player] = "helper_test_game" self.multiworld.player_name = {1: "Tester"} self.multiworld.set_seed() + self.multiworld.worlds[self.player] = TestWorld(self.multiworld, self.player) def test_region_helpers(self) -> None: """Tests `Region.add_locations()` and `Region.add_exits()` have correct behavior""" @@ -46,8 +49,9 @@ class TestHelpers(unittest.TestCase): "TestRegion1": {"TestRegion3"} } - exit_rules: Dict[str, Callable[[CollectionState], bool]] = { - "TestRegion1": lambda state: state.has("test_item", self.player) + exit_rules: Dict[str, CollectionRule | Rule[Any]] = { + "TestRegion1": lambda state: state.has("test_item", self.player), + "TestRegion2": Has("test_item2"), } self.multiworld.regions += [Region(region, self.player, self.multiworld, regions[region]) for region in regions] @@ -74,13 +78,17 @@ class TestHelpers(unittest.TestCase): self.assertTrue(f"{parent} -> {exit_reg}" in created_exit_names) if exit_reg in exit_rules: entrance_name = exit_name if exit_name else f"{parent} -> {exit_reg}" - self.assertEqual(exit_rules[exit_reg], - self.multiworld.get_entrance(entrance_name, self.player).access_rule) + rule = exit_rules[exit_reg] + if isinstance(rule, Rule): + self.assertEqual(rule.resolve(self.multiworld.worlds[self.player]), + self.multiworld.get_entrance(entrance_name, self.player).access_rule) + else: + self.assertEqual(rule, self.multiworld.get_entrance(entrance_name, self.player).access_rule) - for region in reg_exit_set: + for region, exit_set in reg_exit_set.items(): current_region = self.multiworld.get_region(region, self.player) - current_region.add_exits(reg_exit_set[region]) + current_region.add_exits(exit_set) exit_names = {_exit.name for _exit in current_region.exits} - for reg_exit in reg_exit_set[region]: + for reg_exit in exit_set: self.assertTrue(f"{region} -> {reg_exit}" in exit_names, f"{region} -> {reg_exit} not in {exit_names}")