diff --git a/worlds/lingo/LL1.yaml b/worlds/lingo/LL1.yaml index 7ae015dc..db1418f5 100644 --- a/worlds/lingo/LL1.yaml +++ b/worlds/lingo/LL1.yaml @@ -97,6 +97,11 @@ # Use "required_when_no_doors" instead if it would be # possible to enter the room without the painting in door # shuffle mode. + # - req_blocked: Marks that a painting cannot be an entrance leading to a + # required painting. Paintings within a room that has a + # required painting are automatically req blocked. + # Use "req_blocked_when_no_doors" instead if it would be + # fine in door shuffle mode. # - move: Denotes that the painting is able to move. Starting Room: entrances: @@ -2210,6 +2215,7 @@ - id: map_painting2 orientation: north enter_only: True # otherwise you might just skip the whole game! + req_blocked_when_no_doors: True # owl hallway in vanilla doors Roof: entrances: Orange Tower Seventh Floor: True @@ -2276,6 +2282,7 @@ paintings: - id: arrows_painting_11 orientation: east + req_blocked_when_no_doors: True # owl hallway in vanilla doors Courtyard: entrances: Roof: True @@ -5755,11 +5762,13 @@ move: True required_door: door: Exit + req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors - id: symmetry_painting_a_6 orientation: west exit_only: True - id: symmetry_painting_b_6 orientation: north + req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors Arrow Garden: entrances: The Wondrous: @@ -6914,6 +6923,7 @@ paintings: - id: clock_painting_3 orientation: east + req_blocked: True # outside the wise (with or without door shuffle) The Red: entrances: Roof: True @@ -7362,6 +7372,7 @@ paintings: - id: hi_solved_painting4 orientation: south + req_blocked_when_no_doors: True # owl hallway in vanilla doors Challenge Room: entrances: Welcome Back Area: diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index 217ad91f..66fe317d 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -241,43 +241,46 @@ class LingoPlayerLogic: door_shuffle = world.options.shuffle_doors - # Determine the set of exit paintings. All required-exit paintings are included, as are all - # required-when-no-doors paintings if door shuffle is off. We then fill the set with random other paintings. - chosen_exits = [] + # First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to + # required paintings. + req_exits = [] + required_painting_rooms = REQUIRED_PAINTING_ROOMS if door_shuffle == ShuffleDoors.option_none: - chosen_exits = [painting_id for painting_id, painting in PAINTINGS.items() - if painting.required_when_no_doors] - chosen_exits += [painting_id for painting_id, painting in PAINTINGS.items() - if painting.exit_only and painting.required] + required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS + req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors] + req_enterable = [painting_id for painting_id, painting in PAINTINGS.items() + if not painting.exit_only and not painting.disable and not painting.req_blocked and + not painting.req_blocked_when_no_doors and painting.room not in required_painting_rooms] + else: + req_enterable = [painting_id for painting_id, painting in PAINTINGS.items() + if not painting.exit_only and not painting.disable and not painting.req_blocked and + painting.room not in required_painting_rooms] + req_exits += [painting_id for painting_id, painting in PAINTINGS.items() + if painting.exit_only and painting.required] + req_entrances = world.random.sample(req_enterable, len(req_exits)) + + self.PAINTING_MAPPING = dict(zip(req_entrances, req_exits)) + + # Next, determine the rest of the exit paintings. exitable = [painting_id for painting_id, painting in PAINTINGS.items() - if not painting.enter_only and not painting.disable and not painting.required] - chosen_exits += world.random.sample(exitable, PAINTING_EXITS - len(chosen_exits)) + if not painting.enter_only and not painting.disable and painting_id not in req_exits and + painting_id not in req_entrances] + nonreq_exits = world.random.sample(exitable, PAINTING_EXITS - len(req_exits)) + chosen_exits = req_exits + nonreq_exits - # Determine the set of entrance paintings. + # Determine the rest of the entrance paintings. enterable = [painting_id for painting_id, painting in PAINTINGS.items() - if not painting.exit_only and not painting.disable and painting_id not in chosen_exits] - chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES) + if not painting.exit_only and not painting.disable and painting_id not in chosen_exits and + painting_id not in req_entrances] + chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES - len(req_entrances)) - # Create a mapping from entrances to exits. - for warp_exit in chosen_exits: + # Assign one entrance to each non-required exit, to ensure that the total number of exits is achieved. + for warp_exit in nonreq_exits: warp_enter = world.random.choice(chosen_entrances) - - # Check whether this is a warp from a required painting room to another (or the same) required painting - # room. This could cause a cycle that would make certain regions inaccessible. - warp_exit_room = PAINTINGS[warp_exit].room - warp_enter_room = PAINTINGS[warp_enter].room - - required_painting_rooms = REQUIRED_PAINTING_ROOMS - if door_shuffle == ShuffleDoors.option_none: - required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS - - if warp_exit_room in required_painting_rooms and warp_enter_room in required_painting_rooms: - # This shuffling is non-workable. Start over. - return False - chosen_entrances.remove(warp_enter) self.PAINTING_MAPPING[warp_enter] = warp_exit + # Assign each of the remaining entrances to any required or non-required exit. for warp_enter in chosen_entrances: warp_exit = world.random.choice(chosen_exits) self.PAINTING_MAPPING[warp_enter] = warp_exit @@ -292,7 +295,8 @@ class LingoPlayerLogic: # Just for sanity's sake, ensure that all required painting rooms are accessed. for painting_id, painting in PAINTINGS.items(): if painting_id not in self.PAINTING_MAPPING.values() \ - and (painting.required or (painting.required_when_no_doors and door_shuffle == 0)): + and (painting.required or (painting.required_when_no_doors and + door_shuffle == ShuffleDoors.option_none)): return False return True diff --git a/worlds/lingo/static_logic.py b/worlds/lingo/static_logic.py index d122169c..f6690f93 100644 --- a/worlds/lingo/static_logic.py +++ b/worlds/lingo/static_logic.py @@ -63,6 +63,8 @@ class Painting(NamedTuple): required_door: Optional[RoomAndDoor] disable: bool move: bool + req_blocked: bool + req_blocked_when_no_doors: bool class Progression(NamedTuple): @@ -471,6 +473,16 @@ def process_painting(room_name, painting_data): else: enter_only = False + if "req_blocked" in painting_data: + req_blocked = painting_data["req_blocked"] + else: + req_blocked = False + + if "req_blocked_when_no_doors" in painting_data: + req_blocked_when_no_doors = painting_data["req_blocked_when_no_doors"] + else: + req_blocked_when_no_doors = False + required_door = None if "required_door" in painting_data: door = painting_data["required_door"] @@ -480,7 +492,8 @@ def process_painting(room_name, painting_data): ) painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation, - required_painting, rwnd, required_door, disable_painting, move_painting) + required_painting, rwnd, required_door, disable_painting, move_painting, req_blocked, + req_blocked_when_no_doors) PAINTINGS[painting_id] = painting_obj PAINTINGS_BY_ROOM[room_name].append(painting_obj)