1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<"""
Archipelago init file for Lingo 2
"""
from typing import ClassVar
from BaseClasses import ItemClassification, Item, Tutorial
from settings import Group, UserFilePath
from worlds.AutoWorld import WebWorld, World
from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS
from .options import Lingo2Options
from .player_logic import Lingo2PlayerLogic
from .regions import create_regions, shuffle_entrances, connect_ports_from_ut
from .static_logic import Lingo2StaticLogic
from ..LauncherComponents import Component, Type, components, launch as launch_component, icon_paths
class Lingo2WebWorld(WebWorld):
rich_text_options_doc = True
theme = "grass"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to playing Lingo 2 with Archipelago.",
"English",
"en_Lingo_2.md",
"setup/en",
["hatkirby"]
)]
class Lingo2Settings(Group):
class ExecutableFile(UserFilePath):
"""Path to the Lingo 2 executable"""
is_exe = True
exe_file: ExecutableFile = ExecutableFile()
class Lingo2World(World):
"""
Lingo 2 is a first person indie puzzle game where you solve word puzzles in a labyrinthe world. Compared to its
predecessor, Lingo 2 has new mechanics, more areas, and a unique progression system where you have to unlock letters
before using them in puzzle solutions.
"""
game = "Lingo 2"
web = Lingo2WebWorld()
settings: ClassVar[Lingo2Settings]
settings_key = "lingo2_options"
topology_present = True
options_dataclass = Lingo2Options
options: Lingo2Options
static_logic = Lingo2StaticLogic()
item_name_to_id = static_logic.item_name_to_id
location_name_to_id = static_logic.location_name_to_id
item_name_groups = static_logic.item_name_groups
location_name_groups = static_logic.location_name_groups
for_tracker: ClassVar[bool] = False
player_logic: Lingo2PlayerLogic
port_pairings: dict[int, int]
def generate_early(self):
self.player_logic = Lingo2PlayerLogic(self)
self.port_pairings = {}
def create_regions(self):
create_regions(self)
def connect_entrances(self):
if self.options.shuffle_worldports:
if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough:
slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"]
self.port_pairings = {int(fp): int(tp) for fp, tp in slot_value.items()}
connect_ports_from_ut(self.port_pairings, self)
else:
shuffle_entrances(self)
from Utils import visualize_regions
visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
def create_items(self):
pool = [self.create_item(name) for name in self.player_logic.real_items]
total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values())
item_difference = total_locations - len(pool)
if self.options.trap_percentage > 0:
num_traps = int(item_difference * self.options.trap_percentage / 100)
item_difference = item_difference - num_traps
trap_names = []
trap_weights = []
for letter_name, weight in self.static_logic.letter_weights.items():
trap_names.append(f"Anti {letter_name}")
trap_weights.append(weight)
bad_letters = self.random.choices(trap_names, weights=trap_weights, k=num_traps)
pool += [self.create_item(trap_name) for trap_name in bad_letters]
for i in range(0, item_difference):
pool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += pool
def create_item(self, name: str) -> Item:
return Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else
ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else
ItemClassification.progression,
self.item_name_to_id.get(name), self.player)
def set_rules(self):
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
def fill_slot_data(self):
slot_options = [
"cyan_door_behavior",
"daedalus_roof_access",
"keyholder_sanity",
"shuffle_control_center_colors",
"shuffle_doors",
"shuffle_gallery_paintings",
"shuffle_letters",
"shuffle_symbols",
"shuffle_worldports",
"strict_cyan_ending",
"strict_purple_ending",
"victory_condition",
]
slot_data: dict[str, object] = {
**self.options.as_dict(*slot_options),
"version": self.static_logic.get_data_version(),
}
if self.options.shuffle_worldports:
slot_data["port_pairings"] = self.port_pairings
return slot_data
def get_filler_item_name(self) -> str:
return "A Job Well Done"
# for the universal tracker, doesn't get called in standard gen
# docs: https://github.com/FarisTheAncient/Archipelago/blob/tracker/worlds/tracker/docs/re-gen-passthrough.md
@staticmethod
def interpret_slot_data(slot_data: dict[str, object]) -> dict[str, object]:
# returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough
# we are using re_gen_passthrough over modifying the world here due to complexities with ER
return slot_data
def launch_client(*args):
from .context import client_main
launch_component(client_main, name="Lingo2Client", args=args)
icon_paths["lingo2_ico"] = f"ap:{__name__}/logo.png"
component = Component("Lingo 2 Client", component_type=Type.CLIENT, func=launch_client,
description="Open Lingo 2.", supports_uri=True, game_name="Lingo 2", icon="lingo2_ico")
components.append(component)
|