about summary refs log tree commit diff stats
path: root/apworld/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'apworld/__init__.py')
-rw-r--r--apworld/__init__.py155
1 files changed, 148 insertions, 7 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index 14bb4bc..6b5338e 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -1,18 +1,40 @@
1""" 1"""
2Archipelago init file for Lingo 2 2Archipelago init file for Lingo 2
3""" 3"""
4from BaseClasses import ItemClassification, Item 4from typing import ClassVar
5
6from BaseClasses import ItemClassification, Item, Tutorial
7from Options import OptionError
8from settings import Group, UserFilePath
5from worlds.AutoWorld import WebWorld, World 9from worlds.AutoWorld import WebWorld, World
6from .items import Lingo2Item 10from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS, ALL_LETTERS_UPPER
7from .options import Lingo2Options 11from .options import Lingo2Options
8from .player_logic import Lingo2PlayerLogic 12from .player_logic import Lingo2PlayerLogic
9from .regions import create_regions 13from .regions import create_regions, shuffle_entrances, connect_ports_from_ut
10from .static_logic import Lingo2StaticLogic 14from .static_logic import Lingo2StaticLogic
15from worlds.LauncherComponents import Component, Type, components, launch as launch_component, icon_paths
11 16
12 17
13class Lingo2WebWorld(WebWorld): 18class Lingo2WebWorld(WebWorld):
14 rich_text_options_doc = True 19 rich_text_options_doc = True
15 theme = "grass" 20 theme = "grass"
21 tutorials = [Tutorial(
22 "Multiworld Setup Guide",
23 "A guide to playing Lingo 2 with Archipelago.",
24 "English",
25 "en_Lingo_2.md",
26 "setup/en",
27 ["hatkirby"]
28 )]
29
30
31class Lingo2Settings(Group):
32 class ExecutableFile(UserFilePath):
33 """Path to the Lingo 2 executable"""
34 is_exe = True
35
36 exe_file: ExecutableFile = ExecutableFile()
37 start_game: bool = True
16 38
17 39
18class Lingo2World(World): 40class Lingo2World(World):
@@ -24,6 +46,9 @@ class Lingo2World(World):
24 game = "Lingo 2" 46 game = "Lingo 2"
25 web = Lingo2WebWorld() 47 web = Lingo2WebWorld()
26 48
49 settings: ClassVar[Lingo2Settings]
50 settings_key = "lingo2_options"
51
27 topology_present = True 52 topology_present = True
28 53
29 options_dataclass = Lingo2Options 54 options_dataclass = Lingo2Options
@@ -32,18 +57,45 @@ class Lingo2World(World):
32 static_logic = Lingo2StaticLogic() 57 static_logic = Lingo2StaticLogic()
33 item_name_to_id = static_logic.item_name_to_id 58 item_name_to_id = static_logic.item_name_to_id
34 location_name_to_id = static_logic.location_name_to_id 59 location_name_to_id = static_logic.location_name_to_id
60 item_name_groups = static_logic.item_name_groups
61 location_name_groups = static_logic.location_name_groups
62
63 for_tracker: ClassVar[bool] = False
35 64
36 player_logic: Lingo2PlayerLogic 65 player_logic: Lingo2PlayerLogic
37 66
67 port_pairings: dict[int, int]
68
38 def generate_early(self): 69 def generate_early(self):
39 self.player_logic = Lingo2PlayerLogic(self) 70 self.player_logic = Lingo2PlayerLogic(self)
71 self.port_pairings = {}
72
73 if self.options.restrict_letter_placements:
74 self.options.local_items.value |= set(ALL_LETTERS_UPPER)
40 75
41 def create_regions(self): 76 def create_regions(self):
77 if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough:
78 self.player_logic.rte_mapping = [self.world.static_logic.map_id_by_name[map_name]
79 for map_name in self.multiworld.re_gen_passthrough["Lingo 2"]["rte"]]
80
42 create_regions(self) 81 create_regions(self)
43 82
44 from Utils import visualize_regions 83 def connect_entrances(self):
84 if self.options.shuffle_worldports:
85 if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough:
86 slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"]
87 self.port_pairings = {
88 self.static_logic.port_id_by_ap_id[int(fp)]: self.static_logic.port_id_by_ap_id[int(tp)]
89 for fp, tp in slot_value.items()
90 }
91
92 connect_ports_from_ut(self.port_pairings, self)
93 else:
94 shuffle_entrances(self)
45 95
46 visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml") 96 #from Utils import visualize_regions
97
98 #visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
47 99
48 def create_items(self): 100 def create_items(self):
49 pool = [self.create_item(name) for name in self.player_logic.real_items] 101 pool = [self.create_item(name) for name in self.player_logic.real_items]
@@ -51,14 +103,103 @@ class Lingo2World(World):
51 total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values()) 103 total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values())
52 104
53 item_difference = total_locations - len(pool) 105 item_difference = total_locations - len(pool)
106
107 if self.options.trap_percentage > 0:
108 num_traps = int(item_difference * self.options.trap_percentage / 100)
109 item_difference = item_difference - num_traps
110
111 trap_names = []
112 trap_weights = []
113 for letter_name, weight in self.static_logic.letter_weights.items():
114 trap_names.append(f"Anti {letter_name}")
115 trap_weights.append(weight)
116
117 bad_letters = self.random.choices(trap_names, weights=trap_weights, k=num_traps)
118 pool += [self.create_item(trap_name) for trap_name in bad_letters]
119
54 for i in range(0, item_difference): 120 for i in range(0, item_difference):
55 pool.append(self.create_item("Nothing")) 121 pool.append(self.create_item(self.get_filler_item_name()))
122
123 if not any(ItemClassification.progression in item.classification for item in pool):
124 raise OptionError(f"Lingo 2 player {self.player} has no progression items. Please enable at least one "
125 f"option that would add progression gating to your world, such as Shuffle Doors or "
126 f"Shuffle Letters.")
56 127
57 self.multiworld.itempool += pool 128 self.multiworld.itempool += pool
58 129
130 for name in self.player_logic.starting_items:
131 self.push_precollected(self.create_item(name))
132
59 def create_item(self, name: str) -> Item: 133 def create_item(self, name: str) -> Item:
60 return Lingo2Item(name, ItemClassification.filler if name == "Nothing" else ItemClassification.progression, 134 item = Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else
135 ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else
136 ItemClassification.progression,
61 self.item_name_to_id.get(name), self.player) 137 self.item_name_to_id.get(name), self.player)
62 138
139 item.is_letter = (name in ALL_LETTERS_UPPER)
140 return item
141
63 def set_rules(self): 142 def set_rules(self):
64 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)
144
145 def fill_slot_data(self):
146 slot_options = [
147 "cyan_door_behavior",
148 "daedalus_only",
149 "daedalus_roof_access",
150 "enable_gift_maps",
151 "enable_icarus",
152 "endings_requirement",
153 "fast_travel_access",
154 "keyholder_sanity",
155 "masteries_requirement",
156 "shuffle_control_center_colors",
157 "shuffle_doors",
158 "shuffle_gallery_paintings",
159 "shuffle_letters",
160 "shuffle_music",
161 "shuffle_symbols",
162 "shuffle_worldports",
163 "strict_cyan_ending",
164 "strict_purple_ending",
165 "victory_condition",
166 ]
167
168 slot_data: dict[str, object] = {
169 **self.options.as_dict(*slot_options),
170 "custom_mint_ending": self.player_logic.custom_mint_ending or "",
171 "rte": [self.static_logic.objects.maps[map_id].name for map_id in self.player_logic.rte_mapping],
172 "seed": self.random.randint(0, 1000000),
173 "version": self.static_logic.get_data_version(),
174 }
175
176 if self.options.shuffle_worldports:
177 def get_port_ap_id(port_id):
178 return self.static_logic.objects.ports[port_id].ap_id
179
180 slot_data["port_pairings"] = {get_port_ap_id(from_id): get_port_ap_id(to_id)
181 for from_id, to_id in self.port_pairings.items()}
182
183 return slot_data
184
185 def get_filler_item_name(self) -> str:
186 return "A Job Well Done"
187
188 # for the universal tracker, doesn't get called in standard gen
189 # docs: https://github.com/FarisTheAncient/Archipelago/blob/tracker/worlds/tracker/docs/re-gen-passthrough.md
190 @staticmethod
191 def interpret_slot_data(slot_data: dict[str, object]) -> dict[str, object]:
192 # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough
193 # we are using re_gen_passthrough over modifying the world here due to complexities with ER
194 return slot_data
195
196
197def launch_client(*args):
198 from .context import client_main
199 launch_component(client_main, name="Lingo2Client", args=args)
200
201
202icon_paths["lingo2_ico"] = f"ap:{__name__}/logo.png"
203component = Component("Lingo 2 Client", component_type=Type.CLIENT, func=launch_client,
204 description="Open Lingo 2.", supports_uri=True, game_name="Lingo 2", icon="lingo2_ico")
205components.append(component)