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