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__.py125
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"""
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
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,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
167def launch_client(*args):
168 from .context import client_main
169 launch_component(client_main, name="Lingo2Client", args=args)
170
171
172icon_paths["lingo2_ico"] = f"ap:{__name__}/logo.png"
173component = 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")
175components.append(component)