from typing import TYPE_CHECKING, Iterator from BaseClasses import MultiWorld, CollectionState, ItemClassification, Region, Entrance from NetUtils import NetworkItem from . import Lingo2World, Lingo2Item from .regions import connect_ports_from_ut from .options import Lingo2Options, ShuffleLetters if TYPE_CHECKING: from .context import Lingo2Manager PLAYER_NUM = 1 class Tracker: manager: "Lingo2Manager" multiworld: MultiWorld world: Lingo2World collected_items: dict[int, int] checked_locations: set[int] accessible_locations: set[int] accessible_worldports: set[int] goal_accessible: bool state: CollectionState def __init__(self, manager: "Lingo2Manager"): self.manager = manager self.collected_items = {} self.checked_locations = set() self.accessible_locations = set() self.accessible_worldports = set() self.goal_accessible = False def setup_slot(self, slot_data): Lingo2World.for_tracker = True self.multiworld = MultiWorld(players=PLAYER_NUM) self.world = Lingo2World(self.multiworld, PLAYER_NUM) self.multiworld.worlds[1] = self.world self.world.options = Lingo2Options(**{k: t(slot_data.get(k, t.default)) for k, t in Lingo2Options.type_hints.items()}) self.world.generate_early() self.world.create_regions() if self.world.options.shuffle_worldports: port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()} connect_ports_from_ut(port_pairings, self.world) self.refresh_state() def set_checked_locations(self, checked_locations: set[int]): self.checked_locations = checked_locations.copy() def set_collected_items(self, network_items: list[NetworkItem]): self.collected_items = {} for item in network_items: self.collected_items[item.item] = self.collected_items.get(item.item, 0) + 1 self.refresh_state() def refresh_state(self): self.state = CollectionState(self.multiworld) for item_id, item_amount in self.collected_items.items(): for i in range(item_amount): self.state.collect(Lingo2Item(Lingo2World.static_logic.item_id_to_name.get(item_id), ItemClassification.progression, item_id, PLAYER_NUM), prevent_sweep=True) for k, v in self.manager.keyboard.items(): # Unless all level 1 letters are pre-unlocked, H1 I1 N1 and T1 act differently between the generator and # game. The generator considers them to be unlocked, which means they are not included in logic # requirements, and only one item/event is needed to unlock their level 2 forms. The game considers them to # be vanilla, which means you still have to pick them up in the Starting Room in order for them to appear on # your keyboard. This also means that whether or not you have the level 1 forms should be synced to the # multiworld. The tracker specifically should collect one fewer item for these letters in this scenario. tv = v if k in "hint" and self.world.options.shuffle_letters in [ShuffleLetters.option_vanilla, ShuffleLetters.option_progressive]: tv = max(0, v - 1) if tv > 0: for i in range(tv): self.state.collect(Lingo2Item(k.upper(), ItemClassification.progression, None, PLAYER_NUM), prevent_sweep=True) for port_id in self.manager.worldports: self.state.collect(Lingo2Item(f"Worldport {port_id} Entered", ItemClassification.progression, None, PLAYER_NUM), prevent_sweep=True) self.state.sweep_for_advancements() self.accessible_locations = set() self.accessible_worldports = set() self.goal_accessible = False for region in self.state.reachable_regions[PLAYER_NUM]: for location in region.locations: if location.access_rule(self.state): if location.address is not None: if location.address not in self.checked_locations: self.accessible_locations.add(location.address) elif hasattr(location, "port_id"): if location.port_id not in self.manager.worldports: self.accessible_worldports.add(location.port_id) elif hasattr(location, "goal") and location.goal: if not self.manager.goaled: self.goal_accessible = True def get_path_to_location(self, location_id: int) -> list[str] | None: location_name = self.world.location_id_to_name.get(location_id) location = self.multiworld.get_location(location_name, PLAYER_NUM) return self.get_logical_path(location.parent_region) def get_path_to_port(self, port_id: int) -> list[str] | None: port = self.world.static_logic.objects.ports[port_id] region_name = self.world.static_logic.get_room_region_name(port.room_id) region = self.multiworld.get_region(region_name, PLAYER_NUM) return self.get_logical_path(region) def get_path_to_goal(self): room_id = self.world.player_logic.goal_room_id region_name = self.world.static_logic.get_room_region_name(room_id) region = self.multiworld.get_region(region_name, PLAYER_NUM) return self.get_logical_path(region) def get_logical_path(self, region: Region) -> list[str] | None: if region not in self.state.path: return None def flist_to_iter(path_value) -> Iterator[str]: while path_value: region_or_entrance, path_value = path_value yield region_or_entrance reversed_path = self.state.path.get(region) flat_path = reversed(list(map(str, flist_to_iter(reversed_path)))) return list(flat_path)[1::2] bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
from .generated import data_pb2 as data_pb2
from BaseClasses import Item
class Lingo2Item(Item):
game: str = "Lingo 2"
SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = {
data_pb2.PuzzleSymbol.SUN: "Sun Symbol",
data_pb2.PuzzleSymbol.SPARKLES: "Sparkles Symbol",
data_pb2.PuzzleSymbol.ZERO: "Zero Symbol",
data_pb2.PuzzleSymbol.EXAMPLE: "Example Symbol",
data_pb2.PuzzleSymbol.BOXES: "Boxes Symbol",
data_pb2.PuzzleSymbol.PLANET: "Planet Symbol",
data_pb2.PuzzleSymbol.PYRAMID: "Pyramid Symbol",
data_pb2.PuzzleSymbol.CROSS: "Cross Symbol",
data_pb2.PuzzleSymbol.SWEET: "Sweet Symbol",
data_pb2.PuzzleSymbol.GENDER: "Gender Symbol",
data_pb2.PuzzleSymbol.AGE: "Age Symbol",
data_pb2.PuzzleSymbol.SOUND: "Sound Symbol",
data_pb2.PuzzleSymbol.ANAGRAM: "Anagram Symbol",
data_pb2.PuzzleSymbol.JOB: "Job Symbol",
data_pb2.PuzzleSymbol.STARS: "Stars Symbol",
data_pb2.PuzzleSymbol.NULL: "Null Symbol",
data_pb2.PuzzleSymbol.EVAL: "Eval Symbol",
data_pb2.PuzzleSymbol.LINGO: "Lingo Symbol",
data_pb2.PuzzleSymbol.QUESTION: "Question Symbol",
}
ANTI_COLLECTABLE_TRAPS: list[str] = [f"Anti {letter}" for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]