Lingo: Fix edge case painting shuffle accessibility issues (#2441)
* Lingo: Fix painting shuffle logic issue in The Wise * Lingo: More generic painting cycle prevention * Lingo: okay how about now * Lingo: Consider Owl Hallway blocked painting areas in vanilla doors * Lingo: so honestly I should've seen this one coming * Lingo: Refined req_blocked for vanilla doors * Lingo: Orange Tower Basement is also owl-blocked * Lingo: Rewrite randomize_paintings to eliminate rerolls Now, mapping is done in two phases, rather than assigning everything at once and then rerolling if the mapping is non-viable.
This commit is contained in:
parent
b5bd95771d
commit
f444d570d3
worlds/lingo
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue