From 4ae885f392261b939e6f21b47d545f609c6e1965 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 27 Aug 2025 17:14:57 -0400 Subject: Set apworld victory condition --- apworld/__init__.py | 5 +++++ apworld/options.py | 21 ++++++++++++++++- apworld/player_logic.py | 60 ++++++++++++++++++++++++++++--------------------- apworld/regions.py | 18 +++++++++++++-- 4 files changed, 76 insertions(+), 28 deletions(-) (limited to 'apworld') diff --git a/apworld/__init__.py b/apworld/__init__.py index 07e3982..14bb4bc 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py @@ -24,6 +24,8 @@ class Lingo2World(World): game = "Lingo 2" web = Lingo2WebWorld() + topology_present = True + options_dataclass = Lingo2Options options: Lingo2Options @@ -57,3 +59,6 @@ class Lingo2World(World): def create_item(self, name: str) -> Item: return Lingo2Item(name, ItemClassification.filler if name == "Nothing" else ItemClassification.progression, self.item_name_to_id.get(name), self.player) + + def set_rules(self): + self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) diff --git a/apworld/options.py b/apworld/options.py index 77f0ae3..d984beb 100644 --- a/apworld/options.py +++ b/apworld/options.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from Options import PerGameCommonOptions, Toggle +from Options import PerGameCommonOptions, Toggle, Choice class ShuffleDoors(Toggle): @@ -8,6 +8,25 @@ class ShuffleDoors(Toggle): display_name = "Shuffle Doors" +class VictoryCondition(Choice): + """Victory condition.""" + display_name = "Victory Condition" + option_gray_ending = 0 + option_purple_ending = 1 + option_mint_ending = 2 + option_black_ending = 3 + option_blue_ending = 4 + option_cyan_ending = 5 + option_red_ending = 6 + option_plum_ending = 7 + option_orange_ending = 8 + option_gold_ending = 9 + option_yellow_ending = 10 + option_green_ending = 11 + option_white_ending = 12 + + @dataclass class Lingo2Options(PerGameCommonOptions): shuffle_doors: ShuffleDoors + victory_condition: VictoryCondition diff --git a/apworld/player_logic.py b/apworld/player_logic.py index edf8c4f..83c16f2 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py @@ -63,6 +63,8 @@ class Lingo2PlayerLogic: world: "Lingo2World" locations_by_room: dict[int, list[PlayerLocation]] + event_loc_item_by_room: dict[int, dict[str, str]] + item_by_door: dict[int, str] panel_reqs: dict[int, AccessRequirements] @@ -74,6 +76,7 @@ class Lingo2PlayerLogic: def __init__(self, world: "Lingo2World"): self.world = world self.locations_by_room = {} + self.event_loc_item_by_room = {} self.item_by_door = {} self.panel_reqs = dict() self.proxy_reqs = dict() @@ -105,6 +108,14 @@ class Lingo2PlayerLogic: self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, AccessRequirements())) + event_name = f"{ending.name.capitalize()} Ending (Achieved)" + item_name = event_name + + if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: + item_name = "Victory" + + self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name + def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: if answer is None: if panel_id not in self.panel_reqs: @@ -130,6 +141,12 @@ class Lingo2PlayerLogic: proxy_reqs = AccessRequirements() proxy_reqs.add_solution(proxy.answer) + reqs.or_logic.append([proxy_reqs]) + + if not any(proxy.answer == panel.answer for proxy in panel.proxies): + proxy_reqs = AccessRequirements() + proxy_reqs.add_solution(panel.answer) + reqs.or_logic.append([proxy_reqs]) else: reqs.add_solution(panel.answer) @@ -158,32 +175,25 @@ class Lingo2PlayerLogic: door = self.world.static_logic.objects.doors[door_id] reqs = AccessRequirements() - use_item = False - if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors: - use_item = True - - if use_item: - reqs.items.add(self.world.static_logic.get_door_item_name(door.id)) + # TODO: complete_at, control_center_color, switches, keyholders + if not door.HasField("complete_at") or door.complete_at == 0: + for proxy in door.panels: + panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) + reqs.merge(panel_reqs) + elif door.complete_at == 1: + reqs.or_logic.append([self.get_panel_reqs(proxy.panel, + proxy.answer if proxy.HasField("answer") else None) + for proxy in door.panels]) else: - # TODO: complete_at, control_center_color, switches, keyholders - if not door.HasField("complete_at") or door.complete_at == 0: - for proxy in door.panels: - panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) - reqs.merge(panel_reqs) - elif door.complete_at == 1: - reqs.or_logic.append([self.get_panel_reqs(proxy.panel, - proxy.answer if proxy.HasField("answer") else None) - for proxy in door.panels]) - else: - # TODO: Handle complete_at > 1 - pass - - for room in door.rooms: - reqs.rooms.add(self.world.static_logic.get_room_region_name(room)) - - for sub_door_id in door.doors: - sub_reqs = self.get_door_open_reqs(sub_door_id) - reqs.merge(sub_reqs) + # TODO: Handle complete_at > 1 + pass + + for room in door.rooms: + reqs.rooms.add(self.world.static_logic.get_room_region_name(room)) + + for sub_door_id in door.doors: + sub_reqs = self.get_door_open_reqs(sub_door_id) + reqs.merge(sub_reqs) return reqs diff --git a/apworld/regions.py b/apworld/regions.py index 1c8e672..fe2c99b 100644 --- a/apworld/regions.py +++ b/apworld/regions.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING -from BaseClasses import Region +from BaseClasses import Region, ItemClassification, Entrance +from .items import Lingo2Item from .locations import Lingo2Location from .player_logic import AccessRequirements from .rules import make_location_lambda @@ -18,6 +19,12 @@ def create_region(room, world: "Lingo2World") -> Region: new_location.access_rule = make_location_lambda(location.reqs, world) new_region.locations.append(new_location) + for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): + new_location = Lingo2Location(world.player, event_name, None, new_region) + event_item = Lingo2Item(item_name, ItemClassification.progression, None, world.player) + new_location.place_locked_item(event_item) + new_region.locations.append(new_location) + return new_region @@ -73,6 +80,13 @@ def create_regions(world: "Lingo2World"): connection_name = f"{connection_name} (via panel {panel.name})" if from_region in regions and to_region in regions: - regions[from_region].connect(regions[to_region], connection_name, make_location_lambda(reqs, world)) + connection = Entrance(world.player, connection_name, regions[from_region]) + connection.access_rule = make_location_lambda(reqs, world) + + regions[from_region].exits.append(connection) + connection.connect(regions[to_region]) + + for region in reqs.rooms: + world.multiworld.register_indirect_condition(regions[region], connection) world.multiworld.regions += regions.values() -- cgit 1.4.1