diff options
Diffstat (limited to 'apworld/__init__.py')
| -rw-r--r-- | apworld/__init__.py | 119 |
1 files changed, 114 insertions, 5 deletions
| diff --git a/apworld/__init__.py b/apworld/__init__.py index 07e3982..96f6804 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py | |||
| @@ -1,18 +1,38 @@ | |||
| 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 settings import Group, UserFilePath | ||
| 5 | from worlds.AutoWorld import WebWorld, World | 8 | from worlds.AutoWorld import WebWorld, World |
| 6 | from .items import Lingo2Item | 9 | from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS |
| 7 | from .options import Lingo2Options | 10 | from .options import Lingo2Options |
| 8 | from .player_logic import Lingo2PlayerLogic | 11 | from .player_logic import Lingo2PlayerLogic |
| 9 | from .regions import create_regions | 12 | from .regions import create_regions, shuffle_entrances, connect_ports_from_ut |
| 10 | from .static_logic import Lingo2StaticLogic | 13 | from .static_logic import Lingo2StaticLogic |
| 14 | from ..LauncherComponents import Component, Type, components, launch as launch_component, icon_paths | ||
| 11 | 15 | ||
| 12 | 16 | ||
| 13 | class Lingo2WebWorld(WebWorld): | 17 | class Lingo2WebWorld(WebWorld): |
| 14 | rich_text_options_doc = True | 18 | rich_text_options_doc = True |
| 15 | theme = "grass" | 19 | theme = "grass" |
| 20 | tutorials = [Tutorial( | ||
| 21 | "Multiworld Setup Guide", | ||
| 22 | "A guide to playing Lingo 2 with Archipelago.", | ||
| 23 | "English", | ||
| 24 | "en_Lingo_2.md", | ||
| 25 | "setup/en", | ||
| 26 | ["hatkirby"] | ||
| 27 | )] | ||
| 28 | |||
| 29 | |||
| 30 | class Lingo2Settings(Group): | ||
| 31 | class ExecutableFile(UserFilePath): | ||
| 32 | """Path to the Lingo 2 executable""" | ||
| 33 | is_exe = True | ||
| 34 | |||
| 35 | exe_file: ExecutableFile = ExecutableFile() | ||
| 16 | 36 | ||
| 17 | 37 | ||
| 18 | class Lingo2World(World): | 38 | class Lingo2World(World): |
| @@ -24,21 +44,43 @@ class Lingo2World(World): | |||
| 24 | game = "Lingo 2" | 44 | game = "Lingo 2" |
| 25 | web = Lingo2WebWorld() | 45 | web = Lingo2WebWorld() |
| 26 | 46 | ||
| 47 | settings: ClassVar[Lingo2Settings] | ||
| 48 | settings_key = "lingo2_options" | ||
| 49 | |||
| 50 | topology_present = True | ||
| 51 | |||
| 27 | options_dataclass = Lingo2Options | 52 | options_dataclass = Lingo2Options |
| 28 | options: Lingo2Options | 53 | options: Lingo2Options |
| 29 | 54 | ||
| 30 | static_logic = Lingo2StaticLogic() | 55 | static_logic = Lingo2StaticLogic() |
| 31 | item_name_to_id = static_logic.item_name_to_id | 56 | item_name_to_id = static_logic.item_name_to_id |
| 32 | location_name_to_id = static_logic.location_name_to_id | 57 | location_name_to_id = static_logic.location_name_to_id |
| 58 | item_name_groups = static_logic.item_name_groups | ||
| 59 | location_name_groups = static_logic.location_name_groups | ||
| 60 | |||
| 61 | for_tracker: ClassVar[bool] = False | ||
| 33 | 62 | ||
| 34 | player_logic: Lingo2PlayerLogic | 63 | player_logic: Lingo2PlayerLogic |
| 35 | 64 | ||
| 65 | port_pairings: dict[int, int] | ||
| 66 | |||
| 36 | def generate_early(self): | 67 | def generate_early(self): |
| 37 | self.player_logic = Lingo2PlayerLogic(self) | 68 | self.player_logic = Lingo2PlayerLogic(self) |
| 69 | self.port_pairings = {} | ||
| 38 | 70 | ||
| 39 | def create_regions(self): | 71 | def create_regions(self): |
| 40 | create_regions(self) | 72 | create_regions(self) |
| 41 | 73 | ||
| 74 | def connect_entrances(self): | ||
| 75 | if self.options.shuffle_worldports: | ||
| 76 | if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough: | ||
| 77 | slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"] | ||
| 78 | self.port_pairings = {int(fp): int(tp) for fp, tp in slot_value.items()} | ||
| 79 | |||
| 80 | connect_ports_from_ut(self.port_pairings, self) | ||
| 81 | else: | ||
| 82 | shuffle_entrances(self) | ||
| 83 | |||
| 42 | from Utils import visualize_regions | 84 | from Utils import visualize_regions |
| 43 | 85 | ||
| 44 | visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml") | 86 | visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml") |
| @@ -49,11 +91,78 @@ class Lingo2World(World): | |||
| 49 | total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values()) | 91 | total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values()) |
| 50 | 92 | ||
| 51 | item_difference = total_locations - len(pool) | 93 | item_difference = total_locations - len(pool) |
| 94 | |||
| 95 | if self.options.trap_percentage > 0: | ||
| 96 | num_traps = int(item_difference * self.options.trap_percentage / 100) | ||
| 97 | item_difference = item_difference - num_traps | ||
| 98 | |||
| 99 | trap_names = [] | ||
| 100 | trap_weights = [] | ||
| 101 | for letter_name, weight in self.static_logic.letter_weights.items(): | ||
| 102 | trap_names.append(f"Anti {letter_name}") | ||
| 103 | trap_weights.append(weight) | ||
| 104 | |||
| 105 | bad_letters = self.random.choices(trap_names, weights=trap_weights, k=num_traps) | ||
| 106 | pool += [self.create_item(trap_name) for trap_name in bad_letters] | ||
| 107 | |||
| 52 | for i in range(0, item_difference): | 108 | for i in range(0, item_difference): |
| 53 | pool.append(self.create_item("Nothing")) | 109 | pool.append(self.create_item(self.get_filler_item_name())) |
| 54 | 110 | ||
| 55 | self.multiworld.itempool += pool | 111 | self.multiworld.itempool += pool |
| 56 | 112 | ||
| 57 | def create_item(self, name: str) -> Item: | 113 | def create_item(self, name: str) -> Item: |
| 58 | return Lingo2Item(name, ItemClassification.filler if name == "Nothing" else ItemClassification.progression, | 114 | return Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else |
| 115 | ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else | ||
| 116 | ItemClassification.progression, | ||
| 59 | self.item_name_to_id.get(name), self.player) | 117 | self.item_name_to_id.get(name), self.player) |
| 118 | |||
| 119 | def set_rules(self): | ||
| 120 | self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) | ||
| 121 | |||
| 122 | def fill_slot_data(self): | ||
| 123 | slot_options = [ | ||
| 124 | "cyan_door_behavior", | ||
| 125 | "daedalus_roof_access", | ||
| 126 | "keyholder_sanity", | ||
| 127 | "shuffle_control_center_colors", | ||
| 128 | "shuffle_doors", | ||
| 129 | "shuffle_gallery_paintings", | ||
| 130 | "shuffle_letters", | ||
| 131 | "shuffle_symbols", | ||
| 132 | "shuffle_worldports", | ||
| 133 | "strict_cyan_ending", | ||
| 134 | "strict_purple_ending", | ||
| 135 | "victory_condition", | ||
| 136 | ] | ||
| 137 | |||
| 138 | slot_data: dict[str, object] = { | ||
| 139 | **self.options.as_dict(*slot_options), | ||
| 140 | "version": self.static_logic.get_data_version(), | ||
| 141 | } | ||
| 142 | |||
| 143 | if self.options.shuffle_worldports: | ||
| 144 | slot_data["port_pairings"] = self.port_pairings | ||
| 145 | |||
| 146 | return slot_data | ||
| 147 | |||
| 148 | def get_filler_item_name(self) -> str: | ||
| 149 | return "A Job Well Done" | ||
| 150 | |||
| 151 | # for the universal tracker, doesn't get called in standard gen | ||
| 152 | # docs: https://github.com/FarisTheAncient/Archipelago/blob/tracker/worlds/tracker/docs/re-gen-passthrough.md | ||
| 153 | @staticmethod | ||
| 154 | def interpret_slot_data(slot_data: dict[str, object]) -> dict[str, object]: | ||
| 155 | # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough | ||
| 156 | # we are using re_gen_passthrough over modifying the world here due to complexities with ER | ||
| 157 | return slot_data | ||
| 158 | |||
| 159 | |||
| 160 | def launch_client(*args): | ||
| 161 | from .context import client_main | ||
| 162 | launch_component(client_main, name="Lingo2Client", args=args) | ||
| 163 | |||
| 164 | |||
| 165 | icon_paths["lingo2_ico"] = f"ap:{__name__}/logo.png" | ||
| 166 | component = Component("Lingo 2 Client", component_type=Type.CLIENT, func=launch_client, | ||
| 167 | description="Open Lingo 2.", supports_uri=True, game_name="Lingo 2", icon="lingo2_ico") | ||
| 168 | components.append(component) | ||
