Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lingo: Various generation optimizations #2479

Merged
merged 20 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions worlds/lingo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ def create_regions(self):
create_regions(self, self.player_logic)

def create_items(self):
pool = [self.create_item(name) for name in self.player_logic.REAL_ITEMS]
pool = [self.create_item(name) for name in self.player_logic.real_items]

if self.player_logic.FORCED_GOOD_ITEM != "":
new_item = self.create_item(self.player_logic.FORCED_GOOD_ITEM)
if self.player_logic.forced_good_item != "":
new_item = self.create_item(self.player_logic.forced_good_item)
location_obj = self.multiworld.get_location("Second Room - Good Luck", self.player)
location_obj.place_locked_item(new_item)

item_difference = len(self.player_logic.REAL_LOCATIONS) - len(pool)
item_difference = len(self.player_logic.real_locations) - len(pool)
if item_difference:
trap_percentage = self.options.trap_percentage
traps = int(item_difference * trap_percentage / 100.0)
Expand Down Expand Up @@ -93,7 +93,7 @@ def create_item(self, name: str) -> Item:

classification = item.classification
if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0\
and len(item.door_ids) == 0 and all(painting_id not in self.player_logic.PAINTING_MAPPING
and len(item.door_ids) == 0 and all(painting_id not in self.player_logic.painting_mapping
for painting_id in item.painting_ids):
# If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings
# go nowhere, then this item should not be progression.
Expand All @@ -116,6 +116,6 @@ def fill_slot_data(self):
}

if self.options.shuffle_paintings:
slot_data["painting_entrance_to_exit"] = self.player_logic.PAINTING_MAPPING
slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping

return slot_data
2 changes: 0 additions & 2 deletions worlds/lingo/data/LL1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,6 @@
tag: forbid
non_counting: True
check: True
required_panel:
- panel: ANOTHER TRY
doors:
Exit Door:
id: Entry Room Area Doors/Door_hi_high
Expand Down
8 changes: 6 additions & 2 deletions worlds/lingo/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ class ShufflePaintings(Toggle):


class VictoryCondition(Choice):
"""Change the victory condition."""
"""Change the victory condition.
On "the_end", the goal is to solve THE END at the top of the tower.
On "the_master", the goal is to solve THE MASTER at the top of the tower, after getting the number of achievements specified in the Mastery Achievements option.
On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option."""
display_name = "Victory Condition"
option_the_end = 0
option_the_master = 1
Expand All @@ -75,9 +78,10 @@ class Level2Requirement(Range):
"""The number of panel solves required to unlock LEVEL 2.
In the base game, 223 are needed.
Note that this count includes ANOTHER TRY.
When set to 1, the panel hunt is disabled, and you can access LEVEL 2 for free.
"""
display_name = "Level 2 Requirement"
range_start = 2
range_start = 1
range_end = 800
default = 223

Expand Down
298 changes: 213 additions & 85 deletions worlds/lingo/player_logic.py

Large diffs are not rendered by default.

48 changes: 30 additions & 18 deletions worlds/lingo/regions.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
from typing import Dict, TYPE_CHECKING
from typing import Dict, Optional, TYPE_CHECKING

from BaseClasses import ItemClassification, Region
from BaseClasses import Entrance, ItemClassification, Region
from .items import LingoItem
from .locations import LingoLocation
from .player_logic import LingoPlayerLogic
from .rules import lingo_can_use_entrance, lingo_can_use_pilgrimage, make_location_lambda
from .static_logic import ALL_ROOMS, PAINTINGS, Room
from .static_logic import ALL_ROOMS, PAINTINGS, Room, RoomAndDoor

if TYPE_CHECKING:
from . import LingoWorld


def create_region(room: Room, world: "LingoWorld", player_logic: LingoPlayerLogic) -> Region:
new_region = Region(room.name, world.player, world.multiworld)
for location in player_logic.LOCATIONS_BY_ROOM.get(room.name, {}):
for location in player_logic.locations_by_room.get(room.name, {}):
new_location = LingoLocation(world.player, location.name, location.code, new_region)
new_location.access_rule = make_location_lambda(location, room.name, world, player_logic)
new_location.access_rule = make_location_lambda(location, world, player_logic)
new_region.locations.append(new_location)
if location.name in player_logic.EVENT_LOC_TO_ITEM:
event_name = player_logic.EVENT_LOC_TO_ITEM[location.name]
if location.name in player_logic.event_loc_to_item:
event_name = player_logic.event_loc_to_item[location.name]
event_item = LingoItem(event_name, ItemClassification.progression, None, world.player)
new_location.place_locked_item(event_item)

Expand All @@ -31,7 +31,22 @@ def handle_pilgrim_room(regions: Dict[str, Region], world: "LingoWorld", player_
source_region.connect(
target_region,
"Pilgrimage",
lambda state: lingo_can_use_pilgrimage(state, world.player, player_logic))
lambda state: lingo_can_use_pilgrimage(state, world, player_logic))


def connect_entrance(regions: Dict[str, Region], source_region: Region, target_region: Region, description: str,
door: Optional[RoomAndDoor], world: "LingoWorld", player_logic: LingoPlayerLogic):
connection = Entrance(world.player, description, source_region)
connection.access_rule = lambda state: lingo_can_use_entrance(state, target_region.name, door, world, player_logic)

source_region.exits.append(connection)
connection.connect(target_region)

if door is not None:
effective_room = target_region.name if door.room is None else door.room
if door.door not in player_logic.item_by_door.get(effective_room, {}):
for region in player_logic.calculate_door_requirements(effective_room, door.door, world).rooms:
world.multiworld.register_indirect_condition(regions[region], connection)


def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str, world: "LingoWorld",
Expand All @@ -41,11 +56,10 @@ def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str

target_region = regions[target_painting.room]
source_region = regions[source_painting.room]
source_region.connect(
target_region,
f"{source_painting.room} to {target_painting.room} ({source_painting.id} Painting)",
lambda state: lingo_can_use_entrance(state, target_painting.room, source_painting.required_door, world.player,
player_logic))

entrance_name = f"{source_painting.room} to {target_painting.room} ({source_painting.id} Painting)"
connect_entrance(regions, source_region, target_region, entrance_name, source_painting.required_door, world,
player_logic)


def create_regions(world: "LingoWorld", player_logic: LingoPlayerLogic) -> None:
Expand Down Expand Up @@ -74,18 +88,16 @@ def create_regions(world: "LingoWorld", player_logic: LingoPlayerLogic) -> None:
else:
entrance_name += f" (through {room.name} - {entrance.door.door})"

regions[entrance.room].connect(
regions[room.name], entrance_name,
lambda state, r=room, e=entrance: lingo_can_use_entrance(state, r.name, e.door, world.player,
player_logic))
connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, entrance.door, world,
player_logic)

handle_pilgrim_room(regions, world, player_logic)

if early_color_hallways:
regions["Starting Room"].connect(regions["Outside The Undeterred"], "Early Color Hallways")

if painting_shuffle:
for warp_enter, warp_exit in player_logic.PAINTING_MAPPING.items():
for warp_enter, warp_exit in player_logic.painting_mapping.items():
connect_painting(regions, warp_enter, warp_exit, world, player_logic)

world.multiworld.regions += regions.values()
112 changes: 56 additions & 56 deletions worlds/lingo/rules.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
from typing import TYPE_CHECKING

from BaseClasses import CollectionState
from .options import VictoryCondition
from .player_logic import LingoPlayerLogic, PlayerLocation
from .static_logic import PANELS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, RoomAndDoor
from .player_logic import AccessRequirements, LingoPlayerLogic, PlayerLocation
from .static_logic import PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, RoomAndDoor

if TYPE_CHECKING:
from . import LingoWorld


def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, player: int,
def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, world: "LingoWorld",
player_logic: LingoPlayerLogic):
if door is None:
return True

return _lingo_can_open_door(state, room, room if door.room is None else door.room, door.door, player, player_logic)
effective_room = room if door.room is None else door.room
return _lingo_can_open_door(state, effective_room, door.door, world, player_logic)


def lingo_can_use_pilgrimage(state: CollectionState, player: int, player_logic: LingoPlayerLogic):
def lingo_can_use_pilgrimage(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
fake_pilgrimage = [
["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"],
["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"],
Expand All @@ -28,77 +28,77 @@ def lingo_can_use_pilgrimage(state: CollectionState, player: int, player_logic:
["Outside The Agreeable", "Tenacious Entrance"]
]
for entrance in fake_pilgrimage:
if not state.has(player_logic.ITEM_BY_DOOR[entrance[0]][entrance[1]], player):
if not _lingo_can_open_door(state, entrance[0], entrance[1], world, player_logic):
return False

return True


def lingo_can_use_location(state: CollectionState, location: PlayerLocation, room_name: str, world: "LingoWorld",
def lingo_can_use_location(state: CollectionState, location: PlayerLocation, world: "LingoWorld",
player_logic: LingoPlayerLogic):
for panel in location.panels:
panel_room = room_name if panel.room is None else panel.room
if not _lingo_can_solve_panel(state, room_name, panel_room, panel.panel, world, player_logic):
return False

return True

return _lingo_can_satisfy_requirements(state, location.access, world, player_logic)

def lingo_can_use_mastery_location(state: CollectionState, world: "LingoWorld"):
return state.has("Mastery Achievement", world.player, world.options.mastery_achievements.value)


def _lingo_can_open_door(state: CollectionState, start_room: str, room: str, door: str, player: int,
player_logic: LingoPlayerLogic):
"""
Determines whether a door can be opened
"""
item_name = player_logic.ITEM_BY_DOOR[room][door]
if item_name in PROGRESSIVE_ITEMS:
progression = PROGRESSION_BY_ROOM[room][door]
return state.has(item_name, player, progression.index)

return state.has(item_name, player)
def lingo_can_use_mastery_location(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
satisfied_count = 0
for access_req in player_logic.mastery_reqs:
if _lingo_can_satisfy_requirements(state, access_req, world, player_logic):
satisfied_count += 1
return satisfied_count >= world.options.mastery_achievements.value


def _lingo_can_solve_panel(state: CollectionState, start_room: str, room: str, panel: str, world: "LingoWorld",
player_logic: LingoPlayerLogic):
"""
Determines whether a panel can be solved
"""
if start_room != room and not state.can_reach(room, "Region", world.player):
return False
def lingo_can_use_level_2_location(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
counted_panels = 0
state.update_reachable_regions(world.player)
for region in state.reachable_regions[world.player]:
for access_req, panel_count in player_logic.counting_panel_reqs.get(region.name, []):
if _lingo_can_satisfy_requirements(state, access_req, world, player_logic):
counted_panels += panel_count
if counted_panels >= world.options.level_2_requirement.value - 1:
return True
return False

if room == "Second Room" and panel == "ANOTHER TRY" \
and world.options.victory_condition == VictoryCondition.option_level_2 \
and not state.has("Counting Panel Solved", world.player, world.options.level_2_requirement.value - 1):
return False

panel_object = PANELS_BY_ROOM[room][panel]
for req_room in panel_object.required_rooms:
def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequirements, world: "LingoWorld",
player_logic: LingoPlayerLogic):
for req_room in access.rooms:
if not state.can_reach(req_room, "Region", world.player):
return False

for req_door in panel_object.required_doors:
if not _lingo_can_open_door(state, start_room, room if req_door.room is None else req_door.room,
req_door.door, world.player, player_logic):
return False

for req_panel in panel_object.required_panels:
if not _lingo_can_solve_panel(state, start_room, room if req_panel.room is None else req_panel.room,
req_panel.panel, world, player_logic):
for req_door in access.doors:
if not _lingo_can_open_door(state, req_door.room, req_door.door, world, player_logic):
return False

if len(panel_object.colors) > 0 and world.options.shuffle_colors:
for color in panel_object.colors:
if len(access.colors) > 0 and world.options.shuffle_colors:
for color in access.colors:
if not state.has(color.capitalize(), world.player):
return False

return True


def make_location_lambda(location: PlayerLocation, room_name: str, world: "LingoWorld", player_logic: LingoPlayerLogic):
if location.name == player_logic.MASTERY_LOCATION:
return lambda state: lingo_can_use_mastery_location(state, world)
def _lingo_can_open_door(state: CollectionState, room: str, door: str, world: "LingoWorld",
player_logic: LingoPlayerLogic):
"""
Determines whether a door can be opened
"""
if door not in player_logic.item_by_door.get(room, {}):
return _lingo_can_satisfy_requirements(state, player_logic.door_reqs[room][door], world, player_logic)

item_name = player_logic.item_by_door[room][door]
if item_name in PROGRESSIVE_ITEMS:
progression = PROGRESSION_BY_ROOM[room][door]
return state.has(item_name, world.player, progression.index)

return state.has(item_name, world.player)


def make_location_lambda(location: PlayerLocation, world: "LingoWorld", player_logic: LingoPlayerLogic):
if location.name == player_logic.mastery_location:
return lambda state: lingo_can_use_mastery_location(state, world, player_logic)

if world.options.level_2_requirement > 1\
and (location.name == "Second Room - ANOTHER TRY" or location.name == player_logic.level_2_location):
return lambda state: lingo_can_use_level_2_location(state, world, player_logic)

return lambda state: lingo_can_use_location(state, location, room_name, world, player_logic)
return lambda state: lingo_can_use_location(state, location, world, player_logic)
19 changes: 19 additions & 0 deletions worlds/lingo/test/TestPanelsanity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from . import LingoTestBase


class TestPanelHunt(LingoTestBase):
options = {
"shuffle_doors": "complex",
"location_checks": "insanity",
"victory_condition": "level_2",
"level_2_requirement": "15"
}

def test_another_try(self) -> None:
self.collect_by_name("The Traveled - Entrance") # idk why this is needed
self.assertFalse(self.can_reach_location("Second Room - ANOTHER TRY"))
self.assertFalse(self.can_reach_location("Second Room - Unlock Level 2"))

self.collect_by_name("Second Room - Exit Door")
self.assertTrue(self.can_reach_location("Second Room - ANOTHER TRY"))
self.assertTrue(self.can_reach_location("Second Room - Unlock Level 2"))
Loading