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