diff options
Diffstat (limited to 'apworld')
| -rw-r--r-- | apworld/__init__.py | 10 | ||||
| -rw-r--r-- | apworld/items.py | 5 | ||||
| -rw-r--r-- | apworld/locations.py | 27 | ||||
| -rw-r--r-- | apworld/options.py | 12 | ||||
| -rw-r--r-- | apworld/player_logic.py | 13 | ||||
| -rw-r--r-- | apworld/regions.py | 7 |
6 files changed, 67 insertions, 7 deletions
| diff --git a/apworld/__init__.py b/apworld/__init__.py index 42350bc..ba5d7ea 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py | |||
| @@ -7,7 +7,7 @@ from BaseClasses import ItemClassification, Item, Tutorial | |||
| 7 | from Options import OptionError | 7 | from Options import OptionError |
| 8 | from settings import Group, UserFilePath | 8 | from settings import Group, UserFilePath |
| 9 | from worlds.AutoWorld import WebWorld, World | 9 | from worlds.AutoWorld import WebWorld, World |
| 10 | from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS | 10 | from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS, ALL_LETTERS_UPPER |
| 11 | from .options import Lingo2Options | 11 | from .options import Lingo2Options |
| 12 | from .player_logic import Lingo2PlayerLogic | 12 | from .player_logic import Lingo2PlayerLogic |
| 13 | from .regions import create_regions, shuffle_entrances, connect_ports_from_ut | 13 | from .regions import create_regions, shuffle_entrances, connect_ports_from_ut |
| @@ -70,6 +70,9 @@ class Lingo2World(World): | |||
| 70 | self.player_logic = Lingo2PlayerLogic(self) | 70 | self.player_logic = Lingo2PlayerLogic(self) |
| 71 | self.port_pairings = {} | 71 | self.port_pairings = {} |
| 72 | 72 | ||
| 73 | if self.options.restrict_letter_placements: | ||
| 74 | self.options.local_items.value |= set(ALL_LETTERS_UPPER) | ||
| 75 | |||
| 73 | def create_regions(self): | 76 | def create_regions(self): |
| 74 | if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough: | 77 | if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough: |
| 75 | self.player_logic.rte_mapping = [self.world.static_logic.map_id_by_name[map_name] | 78 | self.player_logic.rte_mapping = [self.world.static_logic.map_id_by_name[map_name] |
| @@ -128,11 +131,14 @@ class Lingo2World(World): | |||
| 128 | self.push_precollected(self.create_item(name)) | 131 | self.push_precollected(self.create_item(name)) |
| 129 | 132 | ||
| 130 | def create_item(self, name: str) -> Item: | 133 | def create_item(self, name: str) -> Item: |
| 131 | return Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else | 134 | item = Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else |
| 132 | ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else | 135 | ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else |
| 133 | ItemClassification.progression, | 136 | ItemClassification.progression, |
| 134 | self.item_name_to_id.get(name), self.player) | 137 | self.item_name_to_id.get(name), self.player) |
| 135 | 138 | ||
| 139 | item.is_letter = (name in ALL_LETTERS_UPPER) | ||
| 140 | return item | ||
| 141 | |||
| 136 | def set_rules(self): | 142 | def set_rules(self): |
| 137 | self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) | 143 | self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) |
| 138 | 144 | ||
| diff --git a/apworld/items.py b/apworld/items.py index 28158c3..143ccb1 100644 --- a/apworld/items.py +++ b/apworld/items.py | |||
| @@ -5,6 +5,8 @@ from BaseClasses import Item | |||
| 5 | class Lingo2Item(Item): | 5 | class Lingo2Item(Item): |
| 6 | game: str = "Lingo 2" | 6 | game: str = "Lingo 2" |
| 7 | 7 | ||
| 8 | is_letter: bool | ||
| 9 | |||
| 8 | 10 | ||
| 9 | SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { | 11 | SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { |
| 10 | data_pb2.PuzzleSymbol.SUN: "Sun Symbol", | 12 | data_pb2.PuzzleSymbol.SUN: "Sun Symbol", |
| @@ -28,4 +30,5 @@ SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { | |||
| 28 | data_pb2.PuzzleSymbol.QUESTION: "Question Symbol", | 30 | data_pb2.PuzzleSymbol.QUESTION: "Question Symbol", |
| 29 | } | 31 | } |
| 30 | 32 | ||
| 31 | ANTI_COLLECTABLE_TRAPS: list[str] = [f"Anti {letter}" for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"] | 33 | ALL_LETTERS_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| 34 | ANTI_COLLECTABLE_TRAPS: list[str] = [f"Anti {letter}" for letter in ALL_LETTERS_UPPER] | ||
| diff --git a/apworld/locations.py b/apworld/locations.py index 3d619dc..174a0dd 100644 --- a/apworld/locations.py +++ b/apworld/locations.py | |||
| @@ -1,4 +1,13 @@ | |||
| 1 | from BaseClasses import Location | 1 | from enum import Enum |
| 2 | |||
| 3 | from BaseClasses import Location, Item | ||
| 4 | from .items import Lingo2Item | ||
| 5 | |||
| 6 | |||
| 7 | class LetterPlacementType(Enum): | ||
| 8 | ANY = 0 | ||
| 9 | DISALLOW = 1 | ||
| 10 | FORCE = 2 | ||
| 2 | 11 | ||
| 3 | 12 | ||
| 4 | class Lingo2Location(Location): | 13 | class Lingo2Location(Location): |
| @@ -6,3 +15,19 @@ class Lingo2Location(Location): | |||
| 6 | 15 | ||
| 7 | port_id: int | 16 | port_id: int |
| 8 | goal: bool | 17 | goal: bool |
| 18 | letter_placement_type: LetterPlacementType | ||
| 19 | |||
| 20 | def set_up_letter_rule(self, lpt: LetterPlacementType): | ||
| 21 | self.letter_placement_type = lpt | ||
| 22 | self.item_rule = self._l2_item_rule | ||
| 23 | |||
| 24 | def _l2_item_rule(self, item: Item) -> bool: | ||
| 25 | if not isinstance(item, Lingo2Item): | ||
| 26 | return True | ||
| 27 | |||
| 28 | if self.letter_placement_type == LetterPlacementType.FORCE: | ||
| 29 | return item.is_letter | ||
| 30 | elif self.letter_placement_type == LetterPlacementType.DISALLOW: | ||
| 31 | return not item.is_letter | ||
| 32 | |||
| 33 | return True | ||
| diff --git a/apworld/options.py b/apworld/options.py index 063af21..6fe6d8d 100644 --- a/apworld/options.py +++ b/apworld/options.py | |||
| @@ -44,6 +44,17 @@ class ShuffleLetters(Choice): | |||
| 44 | option_item_cyan = 4 | 44 | option_item_cyan = 4 |
| 45 | 45 | ||
| 46 | 46 | ||
| 47 | class RestrictLetterPlacements(Toggle): | ||
| 48 | """ | ||
| 49 | If enabled, letter items will be shuffled among letter locations in your local world. Shuffle Letters must be set to | ||
| 50 | Progressive or Item Cyan for this to be useful. | ||
| 51 | |||
| 52 | WARNING: This option may slow down generation. Additionally, it is only reliable with Shuffle Letters set to Item | ||
| 53 | Cyan. When set to Progressive, Shuffle Doors and Shuffle Symbols must be turned off. | ||
| 54 | """ | ||
| 55 | display_name = "Restrict Letter Placements" | ||
| 56 | |||
| 57 | |||
| 47 | class ShuffleSymbols(Toggle): | 58 | class ShuffleSymbols(Toggle): |
| 48 | """ | 59 | """ |
| 49 | If enabled, 19 items will be added to the pool, representing the different symbols that can appear on a panel. | 60 | If enabled, 19 items will be added to the pool, representing the different symbols that can appear on a panel. |
| @@ -251,6 +262,7 @@ class Lingo2Options(PerGameCommonOptions): | |||
| 251 | shuffle_control_center_colors: ShuffleControlCenterColors | 262 | shuffle_control_center_colors: ShuffleControlCenterColors |
| 252 | shuffle_gallery_paintings: ShuffleGalleryPaintings | 263 | shuffle_gallery_paintings: ShuffleGalleryPaintings |
| 253 | shuffle_letters: ShuffleLetters | 264 | shuffle_letters: ShuffleLetters |
| 265 | restrict_letter_placements: RestrictLetterPlacements | ||
| 254 | shuffle_symbols: ShuffleSymbols | 266 | shuffle_symbols: ShuffleSymbols |
| 255 | shuffle_worldports: ShuffleWorldports | 267 | shuffle_worldports: ShuffleWorldports |
| 256 | keyholder_sanity: KeyholderSanity | 268 | keyholder_sanity: KeyholderSanity |
| diff --git a/apworld/player_logic.py b/apworld/player_logic.py index a02856e..7bfd49f 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py | |||
| @@ -192,6 +192,7 @@ class AccessRequirements: | |||
| 192 | class PlayerLocation(NamedTuple): | 192 | class PlayerLocation(NamedTuple): |
| 193 | code: int | None | 193 | code: int | None |
| 194 | reqs: AccessRequirements | 194 | reqs: AccessRequirements |
| 195 | is_letter: bool = False | ||
| 195 | 196 | ||
| 196 | 197 | ||
| 197 | class LetterBehavior(IntEnum): | 198 | class LetterBehavior(IntEnum): |
| @@ -295,6 +296,12 @@ class Lingo2PlayerLogic: | |||
| 295 | self.shuffled_doors.update(set(door.id for door in world.static_logic.objects.doors | 296 | self.shuffled_doors.update(set(door.id for door in world.static_logic.objects.doors |
| 296 | if door.map_id == game_map.id and door.daedalus_only_allow)) | 297 | if door.map_id == game_map.id and door.daedalus_only_allow)) |
| 297 | 298 | ||
| 299 | if (world.options.restrict_letter_placements | ||
| 300 | and world.options.shuffle_letters == ShuffleLetters.option_progressive | ||
| 301 | and (world.options.shuffle_doors or world.options.shuffle_symbols)): | ||
| 302 | raise OptionError(f"When Restrict Letter Placements is enabled and Shuffle Letters is set to Progressive, " | ||
| 303 | f"both Shuffle Doors and Shuffle Symbols must be disabled (Player {world.player}).") | ||
| 304 | |||
| 298 | maximum_masteries = 13 + len(world.options.enable_gift_maps.value) | 305 | maximum_masteries = 13 + len(world.options.enable_gift_maps.value) |
| 299 | if world.options.enable_icarus: | 306 | if world.options.enable_icarus: |
| 300 | maximum_masteries += 1 | 307 | maximum_masteries += 1 |
| @@ -406,9 +413,11 @@ class Lingo2PlayerLogic: | |||
| 406 | if not self.should_shuffle_room(letter.room_id): | 413 | if not self.should_shuffle_room(letter.room_id): |
| 407 | continue | 414 | continue |
| 408 | 415 | ||
| 409 | self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id, | ||
| 410 | AccessRequirements())) | ||
| 411 | behavior = self.get_letter_behavior(letter.key, letter.level2) | 416 | behavior = self.get_letter_behavior(letter.key, letter.level2) |
| 417 | |||
| 418 | self.locations_by_room.setdefault(letter.room_id, []).append( | ||
| 419 | PlayerLocation(letter.ap_id, AccessRequirements(), behavior == LetterBehavior.ITEM)) | ||
| 420 | |||
| 412 | if behavior == LetterBehavior.VANILLA: | 421 | if behavior == LetterBehavior.VANILLA: |
| 413 | if not world.for_tracker: | 422 | if not world.for_tracker: |
| 414 | letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}" | 423 | letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}" |
| diff --git a/apworld/regions.py b/apworld/regions.py index 500139f..076c143 100644 --- a/apworld/regions.py +++ b/apworld/regions.py | |||
| @@ -4,7 +4,7 @@ import BaseClasses | |||
| 4 | from BaseClasses import Region, ItemClassification, Entrance | 4 | from BaseClasses import Region, ItemClassification, Entrance |
| 5 | from entrance_rando import randomize_entrances | 5 | from entrance_rando import randomize_entrances |
| 6 | from .items import Lingo2Item | 6 | from .items import Lingo2Item |
| 7 | from .locations import Lingo2Location | 7 | from .locations import Lingo2Location, LetterPlacementType |
| 8 | from .options import FastTravelAccess | 8 | from .options import FastTravelAccess |
| 9 | from .player_logic import AccessRequirements | 9 | from .player_logic import AccessRequirements |
| 10 | from .rules import make_location_lambda | 10 | from .rules import make_location_lambda |
| @@ -25,6 +25,11 @@ def create_locations(room, new_region: Region, world: "Lingo2World", regions: di | |||
| 25 | new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code], | 25 | new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code], |
| 26 | location.code, new_region) | 26 | location.code, new_region) |
| 27 | new_location.access_rule = make_location_lambda(reqs, world, regions) | 27 | new_location.access_rule = make_location_lambda(reqs, world, regions) |
| 28 | if world.options.restrict_letter_placements: | ||
| 29 | if location.is_letter: | ||
| 30 | new_location.set_up_letter_rule(LetterPlacementType.FORCE) | ||
| 31 | else: | ||
| 32 | new_location.set_up_letter_rule(LetterPlacementType.DISALLOW) | ||
| 28 | new_region.locations.append(new_location) | 33 | new_region.locations.append(new_location) |
| 29 | 34 | ||
| 30 | for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): | 35 | for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): |
