about summary refs log tree commit diff stats
path: root/apworld
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2026-02-07 13:24:58 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2026-02-07 13:24:58 -0500
commit3d03fcd82991d201f32a8313d4b44a4b17de4526 (patch)
treea489ab0fd1143c5734e5e7234279661a8992e38d /apworld
parent1ff5dd9bb2199967bad531518a2d31e650ce107c (diff)
downloadlingo2-archipelago-3d03fcd82991d201f32a8313d4b44a4b17de4526.tar.gz
lingo2-archipelago-3d03fcd82991d201f32a8313d4b44a4b17de4526.tar.bz2
lingo2-archipelago-3d03fcd82991d201f32a8313d4b44a4b17de4526.zip
Add restrict_letter_placements option
Diffstat (limited to 'apworld')
-rw-r--r--apworld/__init__.py10
-rw-r--r--apworld/items.py5
-rw-r--r--apworld/locations.py27
-rw-r--r--apworld/options.py12
-rw-r--r--apworld/player_logic.py13
-rw-r--r--apworld/regions.py7
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
7from Options import OptionError 7from Options import OptionError
8from settings import Group, UserFilePath 8from settings import Group, UserFilePath
9from worlds.AutoWorld import WebWorld, World 9from worlds.AutoWorld import WebWorld, World
10from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS 10from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS, ALL_LETTERS_UPPER
11from .options import Lingo2Options 11from .options import Lingo2Options
12from .player_logic import Lingo2PlayerLogic 12from .player_logic import Lingo2PlayerLogic
13from .regions import create_regions, shuffle_entrances, connect_ports_from_ut 13from .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
5class Lingo2Item(Item): 5class Lingo2Item(Item):
6 game: str = "Lingo 2" 6 game: str = "Lingo 2"
7 7
8 is_letter: bool
9
8 10
9SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { 11SYMBOL_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
31ANTI_COLLECTABLE_TRAPS: list[str] = [f"Anti {letter}" for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"] 33ALL_LETTERS_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
34ANTI_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 @@
1from BaseClasses import Location 1from enum import Enum
2
3from BaseClasses import Location, Item
4from .items import Lingo2Item
5
6
7class LetterPlacementType(Enum):
8 ANY = 0
9 DISALLOW = 1
10 FORCE = 2
2 11
3 12
4class Lingo2Location(Location): 13class 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
47class 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
47class ShuffleSymbols(Toggle): 58class 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:
192class PlayerLocation(NamedTuple): 192class 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
197class LetterBehavior(IntEnum): 198class 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
4from BaseClasses import Region, ItemClassification, Entrance 4from BaseClasses import Region, ItemClassification, Entrance
5from entrance_rando import randomize_entrances 5from entrance_rando import randomize_entrances
6from .items import Lingo2Item 6from .items import Lingo2Item
7from .locations import Lingo2Location 7from .locations import Lingo2Location, LetterPlacementType
8from .options import FastTravelAccess 8from .options import FastTravelAccess
9from .player_logic import AccessRequirements 9from .player_logic import AccessRequirements
10from .rules import make_location_lambda 10from .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():