about summary refs log tree commit diff stats
path: root/data/maps/the_charismatic/rooms/Mastery.txtpb
diff options
context:
space:
mode:
Diffstat (limited to 'data/maps/the_charismatic/rooms/Mastery.txtpb')
0 files changed, 0 insertions, 0 deletions
5' href='#n95'>95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
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]