diff options
| author | Star Rauchenberger <fefferburbia@gmail.com> | 2025-08-27 17:14:57 -0400 |
|---|---|---|
| committer | Star Rauchenberger <fefferburbia@gmail.com> | 2025-08-27 17:14:57 -0400 |
| commit | 4ae885f392261b939e6f21b47d545f609c6e1965 (patch) | |
| tree | 7c6c4266db9a1aff3718f21d097974d0e4625a2e | |
| parent | b548329f740e7f548864e62e4998b59b2d76f86a (diff) | |
| download | lingo2-archipelago-4ae885f392261b939e6f21b47d545f609c6e1965.tar.gz lingo2-archipelago-4ae885f392261b939e6f21b47d545f609c6e1965.tar.bz2 lingo2-archipelago-4ae885f392261b939e6f21b47d545f609c6e1965.zip | |
Set apworld victory condition
| -rw-r--r-- | apworld/__init__.py | 5 | ||||
| -rw-r--r-- | apworld/options.py | 21 | ||||
| -rw-r--r-- | apworld/player_logic.py | 60 | ||||
| -rw-r--r-- | apworld/regions.py | 18 |
4 files changed, 76 insertions, 28 deletions
| 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): | |||
| 24 | game = "Lingo 2" | 24 | game = "Lingo 2" |
| 25 | web = Lingo2WebWorld() | 25 | web = Lingo2WebWorld() |
| 26 | 26 | ||
| 27 | topology_present = True | ||
| 28 | |||
| 27 | options_dataclass = Lingo2Options | 29 | options_dataclass = Lingo2Options |
| 28 | options: Lingo2Options | 30 | options: Lingo2Options |
| 29 | 31 | ||
| @@ -57,3 +59,6 @@ class Lingo2World(World): | |||
| 57 | def create_item(self, name: str) -> Item: | 59 | def create_item(self, name: str) -> Item: |
| 58 | return Lingo2Item(name, ItemClassification.filler if name == "Nothing" else ItemClassification.progression, | 60 | return Lingo2Item(name, ItemClassification.filler if name == "Nothing" else ItemClassification.progression, |
| 59 | self.item_name_to_id.get(name), self.player) | 61 | self.item_name_to_id.get(name), self.player) |
| 62 | |||
| 63 | def set_rules(self): | ||
| 64 | 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 @@ | |||
| 1 | from dataclasses import dataclass | 1 | from dataclasses import dataclass |
| 2 | 2 | ||
| 3 | from Options import PerGameCommonOptions, Toggle | 3 | from Options import PerGameCommonOptions, Toggle, Choice |
| 4 | 4 | ||
| 5 | 5 | ||
| 6 | class ShuffleDoors(Toggle): | 6 | class ShuffleDoors(Toggle): |
| @@ -8,6 +8,25 @@ class ShuffleDoors(Toggle): | |||
| 8 | display_name = "Shuffle Doors" | 8 | display_name = "Shuffle Doors" |
| 9 | 9 | ||
| 10 | 10 | ||
| 11 | class VictoryCondition(Choice): | ||
| 12 | """Victory condition.""" | ||
| 13 | display_name = "Victory Condition" | ||
| 14 | option_gray_ending = 0 | ||
| 15 | option_purple_ending = 1 | ||
| 16 | option_mint_ending = 2 | ||
| 17 | option_black_ending = 3 | ||
| 18 | option_blue_ending = 4 | ||
| 19 | option_cyan_ending = 5 | ||
| 20 | option_red_ending = 6 | ||
| 21 | option_plum_ending = 7 | ||
| 22 | option_orange_ending = 8 | ||
| 23 | option_gold_ending = 9 | ||
| 24 | option_yellow_ending = 10 | ||
| 25 | option_green_ending = 11 | ||
| 26 | option_white_ending = 12 | ||
| 27 | |||
| 28 | |||
| 11 | @dataclass | 29 | @dataclass |
| 12 | class Lingo2Options(PerGameCommonOptions): | 30 | class Lingo2Options(PerGameCommonOptions): |
| 13 | shuffle_doors: ShuffleDoors | 31 | shuffle_doors: ShuffleDoors |
| 32 | 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: | |||
| 63 | world: "Lingo2World" | 63 | world: "Lingo2World" |
| 64 | 64 | ||
| 65 | locations_by_room: dict[int, list[PlayerLocation]] | 65 | locations_by_room: dict[int, list[PlayerLocation]] |
| 66 | event_loc_item_by_room: dict[int, dict[str, str]] | ||
| 67 | |||
| 66 | item_by_door: dict[int, str] | 68 | item_by_door: dict[int, str] |
| 67 | 69 | ||
| 68 | panel_reqs: dict[int, AccessRequirements] | 70 | panel_reqs: dict[int, AccessRequirements] |
| @@ -74,6 +76,7 @@ class Lingo2PlayerLogic: | |||
| 74 | def __init__(self, world: "Lingo2World"): | 76 | def __init__(self, world: "Lingo2World"): |
| 75 | self.world = world | 77 | self.world = world |
| 76 | self.locations_by_room = {} | 78 | self.locations_by_room = {} |
| 79 | self.event_loc_item_by_room = {} | ||
| 77 | self.item_by_door = {} | 80 | self.item_by_door = {} |
| 78 | self.panel_reqs = dict() | 81 | self.panel_reqs = dict() |
| 79 | self.proxy_reqs = dict() | 82 | self.proxy_reqs = dict() |
| @@ -105,6 +108,14 @@ class Lingo2PlayerLogic: | |||
| 105 | self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, | 108 | self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, |
| 106 | AccessRequirements())) | 109 | AccessRequirements())) |
| 107 | 110 | ||
| 111 | event_name = f"{ending.name.capitalize()} Ending (Achieved)" | ||
| 112 | item_name = event_name | ||
| 113 | |||
| 114 | if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: | ||
| 115 | item_name = "Victory" | ||
| 116 | |||
| 117 | self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name | ||
| 118 | |||
| 108 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: | 119 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: |
| 109 | if answer is None: | 120 | if answer is None: |
| 110 | if panel_id not in self.panel_reqs: | 121 | if panel_id not in self.panel_reqs: |
| @@ -131,6 +142,12 @@ class Lingo2PlayerLogic: | |||
| 131 | proxy_reqs.add_solution(proxy.answer) | 142 | proxy_reqs.add_solution(proxy.answer) |
| 132 | 143 | ||
| 133 | reqs.or_logic.append([proxy_reqs]) | 144 | reqs.or_logic.append([proxy_reqs]) |
| 145 | |||
| 146 | if not any(proxy.answer == panel.answer for proxy in panel.proxies): | ||
| 147 | proxy_reqs = AccessRequirements() | ||
| 148 | proxy_reqs.add_solution(panel.answer) | ||
| 149 | |||
| 150 | reqs.or_logic.append([proxy_reqs]) | ||
| 134 | else: | 151 | else: |
| 135 | reqs.add_solution(panel.answer) | 152 | reqs.add_solution(panel.answer) |
| 136 | 153 | ||
| @@ -158,32 +175,25 @@ class Lingo2PlayerLogic: | |||
| 158 | door = self.world.static_logic.objects.doors[door_id] | 175 | door = self.world.static_logic.objects.doors[door_id] |
| 159 | reqs = AccessRequirements() | 176 | reqs = AccessRequirements() |
| 160 | 177 | ||
| 161 | use_item = False | 178 | # TODO: complete_at, control_center_color, switches, keyholders |
| 162 | if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors: | 179 | if not door.HasField("complete_at") or door.complete_at == 0: |
| 163 | use_item = True | 180 | for proxy in door.panels: |
| 164 | 181 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) | |
| 165 | if use_item: | 182 | reqs.merge(panel_reqs) |
| 166 | reqs.items.add(self.world.static_logic.get_door_item_name(door.id)) | 183 | elif door.complete_at == 1: |
| 184 | reqs.or_logic.append([self.get_panel_reqs(proxy.panel, | ||
| 185 | proxy.answer if proxy.HasField("answer") else None) | ||
| 186 | for proxy in door.panels]) | ||
| 167 | else: | 187 | else: |
| 168 | # TODO: complete_at, control_center_color, switches, keyholders | 188 | # TODO: Handle complete_at > 1 |
| 169 | if not door.HasField("complete_at") or door.complete_at == 0: | 189 | pass |
| 170 | for proxy in door.panels: | 190 | |
| 171 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) | 191 | for room in door.rooms: |
| 172 | reqs.merge(panel_reqs) | 192 | reqs.rooms.add(self.world.static_logic.get_room_region_name(room)) |
| 173 | elif door.complete_at == 1: | 193 | |
| 174 | reqs.or_logic.append([self.get_panel_reqs(proxy.panel, | 194 | for sub_door_id in door.doors: |
| 175 | proxy.answer if proxy.HasField("answer") else None) | 195 | sub_reqs = self.get_door_open_reqs(sub_door_id) |
| 176 | for proxy in door.panels]) | 196 | reqs.merge(sub_reqs) |
| 177 | else: | ||
| 178 | # TODO: Handle complete_at > 1 | ||
| 179 | pass | ||
| 180 | |||
| 181 | for room in door.rooms: | ||
| 182 | reqs.rooms.add(self.world.static_logic.get_room_region_name(room)) | ||
| 183 | |||
| 184 | for sub_door_id in door.doors: | ||
| 185 | sub_reqs = self.get_door_open_reqs(sub_door_id) | ||
| 186 | reqs.merge(sub_reqs) | ||
| 187 | 197 | ||
| 188 | return reqs | 198 | return reqs |
| 189 | 199 | ||
| 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 @@ | |||
| 1 | from typing import TYPE_CHECKING | 1 | from typing import TYPE_CHECKING |
| 2 | 2 | ||
| 3 | from BaseClasses import Region | 3 | from BaseClasses import Region, ItemClassification, Entrance |
| 4 | from .items import Lingo2Item | ||
| 4 | from .locations import Lingo2Location | 5 | from .locations import Lingo2Location |
| 5 | from .player_logic import AccessRequirements | 6 | from .player_logic import AccessRequirements |
| 6 | from .rules import make_location_lambda | 7 | from .rules import make_location_lambda |
| @@ -18,6 +19,12 @@ def create_region(room, world: "Lingo2World") -> Region: | |||
| 18 | new_location.access_rule = make_location_lambda(location.reqs, world) | 19 | new_location.access_rule = make_location_lambda(location.reqs, world) |
| 19 | new_region.locations.append(new_location) | 20 | new_region.locations.append(new_location) |
| 20 | 21 | ||
| 22 | for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): | ||
| 23 | new_location = Lingo2Location(world.player, event_name, None, new_region) | ||
| 24 | event_item = Lingo2Item(item_name, ItemClassification.progression, None, world.player) | ||
| 25 | new_location.place_locked_item(event_item) | ||
| 26 | new_region.locations.append(new_location) | ||
| 27 | |||
| 21 | return new_region | 28 | return new_region |
| 22 | 29 | ||
| 23 | 30 | ||
| @@ -73,6 +80,13 @@ def create_regions(world: "Lingo2World"): | |||
| 73 | connection_name = f"{connection_name} (via panel {panel.name})" | 80 | connection_name = f"{connection_name} (via panel {panel.name})" |
| 74 | 81 | ||
| 75 | if from_region in regions and to_region in regions: | 82 | if from_region in regions and to_region in regions: |
| 76 | regions[from_region].connect(regions[to_region], connection_name, make_location_lambda(reqs, world)) | 83 | connection = Entrance(world.player, connection_name, regions[from_region]) |
| 84 | connection.access_rule = make_location_lambda(reqs, world) | ||
| 85 | |||
| 86 | regions[from_region].exits.append(connection) | ||
| 87 | connection.connect(regions[to_region]) | ||
| 88 | |||
| 89 | for region in reqs.rooms: | ||
| 90 | world.multiworld.register_indirect_condition(regions[region], connection) | ||
| 77 | 91 | ||
| 78 | world.multiworld.regions += regions.values() | 92 | world.multiworld.regions += regions.values() |
