Lingo: Add option to prevent shuffling postgame (#3350)
* Lingo: Add option to prevent shuffling postgame * Allow roof access on door shuffle * Fix broken unit test * Simplified THE END edge case * Revert unnecessary change * Review comments * Fix mastery unit test * Update generated.dat * Added player's name to error message
This commit is contained in:
parent
878d5141ce
commit
e714d2e129
|
@ -3,7 +3,7 @@ Archipelago init file for Lingo
|
|||
"""
|
||||
from logging import warning
|
||||
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, Tutorial
|
||||
from Options import OptionError
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .datatypes import Room, RoomEntrance
|
||||
|
@ -68,6 +68,37 @@ class LingoWorld(World):
|
|||
def create_regions(self):
|
||||
create_regions(self)
|
||||
|
||||
if not self.options.shuffle_postgame:
|
||||
state = CollectionState(self.multiworld)
|
||||
state.collect(LingoItem("Prevent Victory", ItemClassification.progression, None, self.player), True)
|
||||
|
||||
# Note: relies on the assumption that real_items is a definitive list of real progression items in this
|
||||
# world, and is not modified after being created.
|
||||
for item in self.player_logic.real_items:
|
||||
state.collect(self.create_item(item), True)
|
||||
|
||||
# Exception to the above: a forced good item is not considered a "real item", but needs to be here anyway.
|
||||
if self.player_logic.forced_good_item != "":
|
||||
state.collect(self.create_item(self.player_logic.forced_good_item), True)
|
||||
|
||||
all_locations = self.multiworld.get_locations(self.player)
|
||||
state.sweep_for_events(locations=all_locations)
|
||||
|
||||
unreachable_locations = [location for location in all_locations
|
||||
if not state.can_reach_location(location.name, self.player)]
|
||||
|
||||
for location in unreachable_locations:
|
||||
if location.name in self.player_logic.event_loc_to_item.keys():
|
||||
continue
|
||||
|
||||
self.player_logic.real_locations.remove(location.name)
|
||||
location.parent_region.locations.remove(location)
|
||||
|
||||
if len(self.player_logic.real_items) > len(self.player_logic.real_locations):
|
||||
raise OptionError(f"{self.player_name}'s Lingo world does not have enough locations to fit the number"
|
||||
f" of required items without shuffling the postgame. Either enable postgame"
|
||||
f" shuffling, or choose different options.")
|
||||
|
||||
def create_items(self):
|
||||
pool = [self.create_item(name) for name in self.player_logic.real_items]
|
||||
|
||||
|
|
|
@ -879,6 +879,8 @@
|
|||
panel: DRAWL + RUNS
|
||||
- room: Owl Hallway
|
||||
panel: READS + RUST
|
||||
- room: Ending Area
|
||||
panel: THE END
|
||||
paintings:
|
||||
- id: eye_painting
|
||||
disable: True
|
||||
|
@ -2322,7 +2324,7 @@
|
|||
orientation: east
|
||||
- id: hi_solved_painting
|
||||
orientation: west
|
||||
Orange Tower Seventh Floor:
|
||||
Ending Area:
|
||||
entrances:
|
||||
Orange Tower Sixth Floor:
|
||||
room: Orange Tower
|
||||
|
@ -2334,6 +2336,18 @@
|
|||
check: True
|
||||
tag: forbid
|
||||
non_counting: True
|
||||
location_name: Orange Tower Seventh Floor - THE END
|
||||
doors:
|
||||
End:
|
||||
event: True
|
||||
panels:
|
||||
- THE END
|
||||
Orange Tower Seventh Floor:
|
||||
entrances:
|
||||
Ending Area:
|
||||
room: Ending Area
|
||||
door: End
|
||||
panels:
|
||||
THE MASTER:
|
||||
# We will set up special rules for this in code.
|
||||
id: Countdown Panels/Panel_master_master
|
||||
|
|
Binary file not shown.
|
@ -272,8 +272,9 @@ panels:
|
|||
PAINTING (4): 445081
|
||||
PAINTING (5): 445082
|
||||
ROOM: 445083
|
||||
Orange Tower Seventh Floor:
|
||||
Ending Area:
|
||||
THE END: 444620
|
||||
Orange Tower Seventh Floor:
|
||||
THE MASTER: 444621
|
||||
MASTERY: 444622
|
||||
Behind A Smile:
|
||||
|
|
|
@ -194,6 +194,11 @@ class EarlyColorHallways(Toggle):
|
|||
display_name = "Early Color Hallways"
|
||||
|
||||
|
||||
class ShufflePostgame(Toggle):
|
||||
"""When off, locations that could not be reached without also reaching your victory condition are removed."""
|
||||
display_name = "Shuffle Postgame"
|
||||
|
||||
|
||||
class TrapPercentage(Range):
|
||||
"""Replaces junk items with traps, at the specified rate."""
|
||||
display_name = "Trap Percentage"
|
||||
|
@ -263,6 +268,7 @@ class LingoOptions(PerGameCommonOptions):
|
|||
mastery_achievements: MasteryAchievements
|
||||
level_2_requirement: Level2Requirement
|
||||
early_color_hallways: EarlyColorHallways
|
||||
shuffle_postgame: ShufflePostgame
|
||||
trap_percentage: TrapPercentage
|
||||
trap_weights: TrapWeights
|
||||
puzzle_skip_percentage: PuzzleSkipPercentage
|
||||
|
|
|
@ -19,22 +19,25 @@ class AccessRequirements:
|
|||
doors: Set[RoomAndDoor]
|
||||
colors: Set[str]
|
||||
the_master: bool
|
||||
postgame: bool
|
||||
|
||||
def __init__(self):
|
||||
self.rooms = set()
|
||||
self.doors = set()
|
||||
self.colors = set()
|
||||
self.the_master = False
|
||||
self.postgame = False
|
||||
|
||||
def merge(self, other: "AccessRequirements"):
|
||||
self.rooms |= other.rooms
|
||||
self.doors |= other.doors
|
||||
self.colors |= other.colors
|
||||
self.the_master |= other.the_master
|
||||
self.postgame |= other.postgame
|
||||
|
||||
def __str__(self):
|
||||
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors})," \
|
||||
f" the_master={self.the_master}"
|
||||
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}," \
|
||||
f" the_master={self.the_master}, postgame={self.postgame})"
|
||||
|
||||
|
||||
class PlayerLocation(NamedTuple):
|
||||
|
@ -190,16 +193,6 @@ class LingoPlayerLogic:
|
|||
if color_shuffle:
|
||||
self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR]
|
||||
|
||||
# Create events for each achievement panel, so that we can determine when THE MASTER is accessible.
|
||||
for room_name, room_data in PANELS_BY_ROOM.items():
|
||||
for panel_name, panel_data in room_data.items():
|
||||
if panel_data.achievement:
|
||||
access_req = AccessRequirements()
|
||||
access_req.merge(self.calculate_panel_requirements(room_name, panel_name, world))
|
||||
access_req.rooms.add(room_name)
|
||||
|
||||
self.mastery_reqs.append(access_req)
|
||||
|
||||
# Handle the victory condition. Victory conditions other than the chosen one become regular checks, so we need
|
||||
# to prevent the actual victory condition from becoming a check.
|
||||
self.mastery_location = "Orange Tower Seventh Floor - THE MASTER"
|
||||
|
@ -207,7 +200,7 @@ class LingoPlayerLogic:
|
|||
|
||||
if victory_condition == VictoryCondition.option_the_end:
|
||||
self.victory_condition = "Orange Tower Seventh Floor - THE END"
|
||||
self.add_location("Orange Tower Seventh Floor", "The End (Solved)", None, [], world)
|
||||
self.add_location("Ending Area", "The End (Solved)", None, [], world)
|
||||
self.event_loc_to_item["The End (Solved)"] = "Victory"
|
||||
elif victory_condition == VictoryCondition.option_the_master:
|
||||
self.victory_condition = "Orange Tower Seventh Floor - THE MASTER"
|
||||
|
@ -231,6 +224,16 @@ class LingoPlayerLogic:
|
|||
[RoomAndPanel("Pilgrim Antechamber", "PILGRIM")], world)
|
||||
self.event_loc_to_item["PILGRIM (Solved)"] = "Victory"
|
||||
|
||||
# Create events for each achievement panel, so that we can determine when THE MASTER is accessible.
|
||||
for room_name, room_data in PANELS_BY_ROOM.items():
|
||||
for panel_name, panel_data in room_data.items():
|
||||
if panel_data.achievement:
|
||||
access_req = AccessRequirements()
|
||||
access_req.merge(self.calculate_panel_requirements(room_name, panel_name, world))
|
||||
access_req.rooms.add(room_name)
|
||||
|
||||
self.mastery_reqs.append(access_req)
|
||||
|
||||
# Create groups of counting panel access requirements for the LEVEL 2 check.
|
||||
self.create_panel_hunt_events(world)
|
||||
|
||||
|
@ -470,6 +473,11 @@ class LingoPlayerLogic:
|
|||
if panel == "THE MASTER":
|
||||
access_reqs.the_master = True
|
||||
|
||||
# Evil python magic (so sayeth NewSoupVi): this checks victory_condition against the panel's location name
|
||||
# override if it exists, or the auto-generated location name if it's None.
|
||||
if self.victory_condition == (panel_object.location_name or f"{room} - {panel}"):
|
||||
access_reqs.postgame = True
|
||||
|
||||
self.panel_reqs[room][panel] = access_reqs
|
||||
|
||||
return self.panel_reqs[room][panel]
|
||||
|
|
|
@ -62,6 +62,9 @@ def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequir
|
|||
if access.the_master and not lingo_can_use_mastery_location(state, world):
|
||||
return False
|
||||
|
||||
if access.postgame and state.has("Prevent Victory", world.player):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ class TestMasteryWhenVictoryIsTheEnd(LingoTestBase):
|
|||
options = {
|
||||
"mastery_achievements": "22",
|
||||
"victory_condition": "the_end",
|
||||
"shuffle_colors": "true"
|
||||
"shuffle_colors": "true",
|
||||
"shuffle_postgame": "true",
|
||||
}
|
||||
|
||||
def test_requirement(self):
|
||||
|
@ -43,7 +44,8 @@ class TestMasteryBlocksDependents(LingoTestBase):
|
|||
options = {
|
||||
"mastery_achievements": "24",
|
||||
"shuffle_colors": "true",
|
||||
"location_checks": "insanity"
|
||||
"location_checks": "insanity",
|
||||
"victory_condition": "level_2",
|
||||
}
|
||||
|
||||
def test_requirement(self):
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
from . import LingoTestBase
|
||||
|
||||
|
||||
class TestPostgameVanillaTheEnd(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "none",
|
||||
"victory_condition": "the_end",
|
||||
"shuffle_postgame": "false",
|
||||
}
|
||||
|
||||
def test_requirement(self):
|
||||
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
|
||||
|
||||
self.assertTrue("The End (Solved)" in location_names)
|
||||
self.assertTrue("Champion's Rest - YOU" in location_names)
|
||||
self.assertFalse("Orange Tower Seventh Floor - THE MASTER" in location_names)
|
||||
self.assertFalse("The Red - Achievement" in location_names)
|
||||
|
||||
|
||||
class TestPostgameComplexDoorsTheEnd(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "complex",
|
||||
"victory_condition": "the_end",
|
||||
"shuffle_postgame": "false",
|
||||
}
|
||||
|
||||
def test_requirement(self):
|
||||
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
|
||||
|
||||
self.assertTrue("The End (Solved)" in location_names)
|
||||
self.assertFalse("Orange Tower Seventh Floor - THE MASTER" in location_names)
|
||||
self.assertTrue("The Red - Achievement" in location_names)
|
||||
|
||||
|
||||
class TestPostgameLateColorHunt(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "none",
|
||||
"victory_condition": "the_end",
|
||||
"sunwarp_access": "disabled",
|
||||
"shuffle_postgame": "false",
|
||||
}
|
||||
|
||||
def test_requirement(self):
|
||||
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
|
||||
|
||||
self.assertFalse("Champion's Rest - YOU" in location_names)
|
||||
|
||||
|
||||
class TestPostgameVanillaTheMaster(LingoTestBase):
|
||||
options = {
|
||||
"shuffle_doors": "none",
|
||||
"victory_condition": "the_master",
|
||||
"shuffle_postgame": "false",
|
||||
}
|
||||
|
||||
def test_requirement(self):
|
||||
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
|
||||
|
||||
self.assertTrue("Orange Tower Seventh Floor - THE END" in location_names)
|
||||
self.assertTrue("Orange Tower Seventh Floor - Mastery Achievements" in location_names)
|
||||
self.assertTrue("The Red - Achievement" in location_names)
|
||||
self.assertFalse("Mastery Panels" in location_names)
|
Loading…
Reference in New Issue