From 2aa51109e81cbcd1ad0fafaff113737d085ba06a Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:56:43 -0400 Subject: [PATCH 01/21] Add button press protection to Victory button --- src/ManualClient.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 19728689..ebe9b06a 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -1125,8 +1125,11 @@ def victory_button_callback(self, button): return - self.ctx.items_received.append("__Victory__") - self.ctx.syncing = True + if tracker_loaded and self.ctx.block_unreachable_location_press and "__Victory__" not in self.ctx.tracker_reachable_events: + logger.debug(f"button for location '{button.text}' was pressed while unreachable") + else: + self.ctx.items_received.append("__Victory__") + self.ctx.syncing = True return ManualManager From 709d9fb7ff476e626eb20450beac2e3f3b088160 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:39:58 -0400 Subject: [PATCH 02/21] Add option to sort categories via algorithms --- src/ManualClient.py | 89 +++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index ebe9b06a..9563c87d 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -65,6 +65,17 @@ class SortingOrderItem(IntEnum): SortingOrderItem.natural.__doc__ = "Sort like custom but makes sure that any number are read as integer and thus sorted naturally. EG. key2 < key12" SortingOrderItem.received.__doc__ = "Sort the item in the order they are received from the server" +class SortingOrderCategories(IntEnum): + alphabetical = 1 + inverted_alphabetical = -1 + natural = 2 + inverted_natural = -2 + default = 2 + +SortingOrderLoc.alphabetical.__doc__ = "Sort alphabetically using the name of item defined in locations.json." +SortingOrderLoc.natural.__doc__ = "Sort like alphabetically but makes sure that any number are read as integer and thus sorted naturally. EG. key2 < key12" + + @cache def strip_articles(title: str) -> str: lower = title.lower() @@ -76,6 +87,14 @@ def strip_articles(title: str) -> str: title = title[3:] return title +def natural_sort_key(key: str): + # Modified from https://stackoverflow.com/a/11150413 + def convert(text): + return int(text) if text.isdigit() else text.lower() + key = strip_articles(key) + + return [convert(c) for c in re.split('([0-9]+)', key)] + class ManualClientCommandProcessor(ClientCommandProcessor): def _cmd_resync(self) -> bool: """Manually trigger a resync.""" @@ -121,18 +140,20 @@ class ManualContext(SuperContext): region_table = {} category_table = {} - tracker_reachable_locations = [] - tracker_reachable_events = [] + tracker_reachable_locations: list[str] = [] + tracker_reachable_events: list[str] = [] set_deathlink = False last_death_link = 0 deathlink_out = False - visible_events = {} + visible_events: dict[str, dict[str, Any]] = {} search_term = "" items_sorting = SortingOrderItem.default.name + items_categories_sorting = SortingOrderCategories.default.name locations_sorting = SortingOrderLoc.default.name + locations_categories_sorting = SortingOrderCategories.default.name block_unreachable_location_press = True colors = { @@ -416,6 +437,8 @@ def build(self) -> Layout: self.ctx.items_sorting = self.config.get('manual', 'items_sorting_order') self.ctx.locations_sorting = self.config.get('manual', 'locations_sorting_order') + self.ctx.items_categories_sorting = self.config.get('manual', 'items_categories_sorting_order') + self.ctx.locations_categories_sorting = self.config.get('manual', 'locations_categories_sorting_order') self.ctx.block_unreachable_location_press = True if self.config.get('universal-tracker', 'block_unreachable_location_press') == "Yes" else False self.manual_game_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(30)) @@ -450,7 +473,9 @@ def build_config(self, config: ConfigParser): super().build_config(config) config.setdefaults("manual", { "items_sorting_order": SortingOrderItem.default.name, - "locations_sorting_order": SortingOrderLoc.default.name + "locations_sorting_order": SortingOrderLoc.default.name, + "items_categories_sorting_order": SortingOrderCategories.default.name, + "locations_categories_sorting_order": SortingOrderCategories.default.name }) config.setdefaults("universal-tracker", { "block_unreachable_location_press": "Yes" @@ -480,6 +505,22 @@ def build_settings(self, settings: Settings): "options": list(SortingOrderLoc._member_names_), "desc": "\n".join([f'[b]{i.name}/inverted_{i.name}[/b]: {i.__doc__}' for i in SortingOrderLoc if i.__doc__ is not None]) }, + { + "type": "options", + "title": "Items Category Sorting Order", + "section": "manual", + "key": "items_categories_sorting_order", + "options": list(SortingOrderCategories._member_names_), + "desc": '\n'.join([f'[b]{i.name}/inverted_{i.name}[/b]: {i.__doc__}' for i in SortingOrderCategories if i.__doc__ is not None]) + }, + { + "type": "options", + "title": "Locations Sorting Order", + "section": "manual", + "key": "locations_categories_sorting_order", + "options": list(SortingOrderCategories._member_names_), + "desc": "Same Options as Items Category Sorting Order." + }, ] if tracker_loaded: json_data.extend([ @@ -722,15 +763,8 @@ def build_tracker_and_locations_table(self): reverse=loc_sorting < 0) elif abs(loc_sorting) == SortingOrderLoc.natural: - # Modified from https://stackoverflow.com/a/11150413 - def convert(text): - return int(text) if text.isdigit() else text.lower() - - def alphanum_key(i): - name = strip_articles(self.ctx.get_location_by_id(i).get("name", "")) - - return [convert(c) for c in re.split('([0-9]+)', self.ctx.get_location_by_id(i).get("sort-key", name))] - + def alphanum_key(key: int) -> list[str|int]: + return natural_sort_key(self.ctx.get_location_by_id(key).get("sort-key",self.ctx.get_location_by_id(key).get("name", ""))) for category in self.listed_locations: self.listed_locations[category].sort(key=alphanum_key, reverse=loc_sorting < 0) @@ -740,8 +774,15 @@ def alphanum_key(i): tracker_panel = TreeView(root_options=dict(text="Items Received (%d)" % (items_length)), size_hint_y=None) tracker_panel.bind(minimum_height=tracker_panel.setter('height')) + # Sorting items categories + item_cat_sorting = SortingOrderCategories[self.ctx.items_categories_sorting] + if abs(item_cat_sorting) == SortingOrderCategories.natural: + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=natural_sort_key, reverse=item_cat_sorting < 0)} + else: + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), reverse=item_cat_sorting < 0)} + # Since items_received is not available on connect, don't bother building item labels here - for item_category in sorted(self.listed_items.keys()): + for item_category in self.listed_items.keys(): category_tree = tracker_panel.add_node( TreeViewLabel(text = "%s (%s)" % (item_category, len(self.listed_items[item_category]))) ) @@ -760,7 +801,14 @@ def alphanum_key(i): if not self.ctx.location_table and not hasattr(AutoWorldRegister.world_types[self.ctx.game], 'location_name_to_location'): raise Exception("The apworld for %s is too outdated for this client. Please update it." % (self.ctx.game)) - for location_category in sorted(self.listed_locations.keys()): + # Sorting location categories + loc_cat_sorting = SortingOrderCategories[self.ctx.locations_categories_sorting] + if abs(loc_cat_sorting) == SortingOrderCategories.natural: + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=natural_sort_key, reverse=loc_sorting < 0)} + else: + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), reverse=loc_sorting < 0)} + + for location_category in self.listed_locations.keys(): locations_in_category = len(self.listed_locations[location_category]) if (location_category in victory_categories) or \ @@ -910,14 +958,9 @@ def update_tracker_and_locations_table(self, update_highlights=False): reverse=item_sorting < 0) elif abs(item_sorting) == SortingOrderItem.natural: - def convert(text): - return int(text) if text.isdigit() else text.lower() - def alphanum_key(i): - name = self.ctx.get_item_by_id(i).get("name", "") - name = strip_articles(name) - - return [convert(c) for c in re.split('([0-9]+)',self.ctx.get_item_by_id(i).get("sort-key", name)) - ] + def alphanum_key(key: int) -> list[str|int]: + return natural_sort_key(self.ctx.get_item_by_id(key).get("sort-key", self.ctx.get_item_by_id(key).get("name", ""))) + sorted_items_received = sorted(sorted_items_received, key=alphanum_key, reverse=item_sorting < 0) elif abs(item_sorting) == SortingOrderItem.received: From 8d009035b16988b9867b4160143c3b59df175bda Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:07:17 -0400 Subject: [PATCH 03/21] make location buttons checks for the location name instead of the button.text --- src/ManualClient.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 9563c87d..bd2956ea 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -384,7 +384,8 @@ class LocationsLayoutScrollable(ScrollView): class TreeViewButton(Button, TreeViewNode): victory: bool = False - id: int = None + id: int|None = None + location_name: str = "" class TreeViewScrollView(ScrollView, TreeViewNode): pass @@ -825,9 +826,10 @@ def alphanum_key(key: int) -> list[str|int]: category_scroll.add_widget(category_layout) for location_id in self.listed_locations[location_category]: - location_button = TreeViewButton(text=self.ctx.location_names.lookup_in_game(location_id), size_hint=(None, None), height=30, width=400) + location_button = TreeViewButton(text=f"custom button text: {self.ctx.location_names.lookup_in_game(location_id)}", size_hint=(None, None), height=30, width=400) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) location_button.id = location_id + location_button.location_name = self.ctx.location_names.lookup_in_game(location_id) category_layout.add_widget(location_button) # if this is the category that Victory is in, display the Victory button @@ -835,9 +837,10 @@ def alphanum_key(key: int) -> list[str|int]: # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - victory_text = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] - location_button = TreeViewButton(text=victory_text, size_hint=(None, None), height=dp(30), width=dp(400)) + victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + location_button = TreeViewButton(text=f"custom victory button text: {victory_text}", size_hint=(None, None), height=dp(30), width=dp(400)) location_button.victory = True + location_button.location_name = victory_location["name"] location_button.bind(on_release=self.victory_button_callback) category_layout.add_widget(location_button) @@ -1061,7 +1064,7 @@ def hide_button_during_search(btn: TreeViewButton): for location_button in category_grid.children: if type(location_button) is TreeViewButton: # should only be true for the victory location button, which has different text - if location_button.text not in (self.ctx.location_table or AutoWorldRegister.world_types[self.ctx.game].location_name_to_location): + if location_button.victory: # if the player is searching for text and the location name doesn't contain it, hide and disable it if self.ctx.search_term and not self.ctx.search_term.lower() in location_button.text.lower(): hide_button_during_search(location_button) @@ -1076,15 +1079,13 @@ def hide_button_during_search(btn: TreeViewButton): continue if location_button.id and location_button.id not in self.ctx.missing_locations: - import logging - - logging.info("location button being removed: " + location_button.text) + logger.info("location button being removed: " + location_button.text) buttons_to_remove.append(location_button) continue was_reachable = False - if location_button.text in self.ctx.tracker_reachable_locations: + if location_button.location_name in self.ctx.tracker_reachable_locations: location_button.background_color = self.ctx.colors['location_in_logic'] was_reachable = True else: @@ -1130,8 +1131,8 @@ def hide_button_during_search(btn: TreeViewButton): category_scrollview.size=(Window.width / 2, scrollview_height) - def location_button_callback(self, location_id, button): - if button.text not in self.ctx.location_names_to_id: + def location_button_callback(self, location_id: int, button: TreeViewButton): + if button.location_name not in self.ctx.location_names_to_id: raise Exception("Locations were not loaded correctly. Please reconnect your client.") # if the mouse is currently hovering over any of the controls/tabs at the top of the client, ignore clicks for location buttons underneath @@ -1146,7 +1147,7 @@ def location_button_callback(self, location_id, button): return if location_id: - if tracker_loaded and self.ctx.block_unreachable_location_press and button.text not in self.ctx.tracker_reachable_locations: + if tracker_loaded and self.ctx.block_unreachable_location_press and button.location_name not in self.ctx.tracker_reachable_locations: logger.debug(f"button for location '{button.text}' was pressed while unreachable") else: self.ctx.locations_checked.append(location_id) @@ -1156,7 +1157,7 @@ def location_button_callback(self, location_id, button): # message = [{"cmd": 'LocationChecks', "locations": [location_id]}] # self.ctx.send_msgs(message) - def victory_button_callback(self, button): + def victory_button_callback(self, button: TreeViewButton): # if the mouse is currently hovering over any of the controls/tabs at the top of the client, ignore clicks for location buttons underneath if self.are_top_controls_at_mouse_pos(): # if there's an obj in the top controls/tab at the current mouse position, click it instead From c0d7d862d9a804361ce2e63a115763ff8e4b143b Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:28:29 -0400 Subject: [PATCH 04/21] fix copy paste errors and new options not doing anything until reload --- src/ManualClient.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index bd2956ea..266b1602 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -72,8 +72,8 @@ class SortingOrderCategories(IntEnum): inverted_natural = -2 default = 2 -SortingOrderLoc.alphabetical.__doc__ = "Sort alphabetically using the name of item defined in locations.json." -SortingOrderLoc.natural.__doc__ = "Sort like alphabetically but makes sure that any number are read as integer and thus sorted naturally. EG. key2 < key12" +SortingOrderCategories.alphabetical.__doc__ = "Sort alphabetically using the name of item defined in locations.json." +SortingOrderCategories.natural.__doc__ = "Sort like alphabetically but makes sure that any number are read as integer and thus sorted naturally. EG. key2 < key12" @cache @@ -552,6 +552,16 @@ def on_config_change(self, config, section, key, value): self.ctx.locations_sorting = value self.build_tracker_and_locations_table() self.request_update_tracker_and_locations_table() + elif key == "locations_categories_sorting_order": + if value in SortingOrderCategories._member_names_: + self.ctx.locations_categories_sorting = value + self.build_tracker_and_locations_table() + self.request_update_tracker_and_locations_table() + elif key == "items_categories_sorting_order": + if value in SortingOrderCategories._member_names_: + self.ctx.items_categories_sorting = value + self.build_tracker_and_locations_table() + self.request_update_tracker_and_locations_table() elif section == "universal-tracker": if key == "block_unreachable_location_press": self.ctx.block_unreachable_location_press = True if value == "Yes" else False @@ -778,9 +788,9 @@ def alphanum_key(key: int) -> list[str|int]: # Sorting items categories item_cat_sorting = SortingOrderCategories[self.ctx.items_categories_sorting] if abs(item_cat_sorting) == SortingOrderCategories.natural: - self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=natural_sort_key, reverse=item_cat_sorting < 0)} + self.listed_items = {key: self.listed_items[key] for key in sorted(self.listed_items.keys(), key=natural_sort_key, reverse=item_cat_sorting < 0)} else: - self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), reverse=item_cat_sorting < 0)} + self.listed_items = {key: self.listed_items[key] for key in sorted(self.listed_items.keys(), reverse=item_cat_sorting < 0)} # Since items_received is not available on connect, don't bother building item labels here for item_category in self.listed_items.keys(): @@ -805,9 +815,9 @@ def alphanum_key(key: int) -> list[str|int]: # Sorting location categories loc_cat_sorting = SortingOrderCategories[self.ctx.locations_categories_sorting] if abs(loc_cat_sorting) == SortingOrderCategories.natural: - self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=natural_sort_key, reverse=loc_sorting < 0)} + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=natural_sort_key, reverse=loc_cat_sorting < 0)} else: - self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), reverse=loc_sorting < 0)} + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), reverse=loc_cat_sorting < 0)} for location_category in self.listed_locations.keys(): locations_in_category = len(self.listed_locations[location_category]) From 78ad825b0f8f968d7414fe27b15e69fe944537db Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:29:35 -0400 Subject: [PATCH 05/21] client version bump --- src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__init__.py b/src/__init__.py index fd50ed83..5c703211 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -570,7 +570,7 @@ def __init__(self, display_name: str, script_name: Optional[str] = None, func: O self.version = version def add_client_to_launcher() -> None: - version = 2026_03_19 # YYYYMMDD + version = 2026_04_07 # YYYYMMDD found = False if "manual" not in icon_paths: From 4dcb9bc6b81466bcdbfb26042cd16a16f58e6cea Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:39:21 -0400 Subject: [PATCH 06/21] removed testing code oops --- src/ManualClient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 266b1602..fe03755e 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -836,7 +836,7 @@ def alphanum_key(key: int) -> list[str|int]: category_scroll.add_widget(category_layout) for location_id in self.listed_locations[location_category]: - location_button = TreeViewButton(text=f"custom button text: {self.ctx.location_names.lookup_in_game(location_id)}", size_hint=(None, None), height=30, width=400) + location_button = TreeViewButton(text=self.ctx.location_names.lookup_in_game(location_id), size_hint=(None, None), height=30, width=400) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) location_button.id = location_id location_button.location_name = self.ctx.location_names.lookup_in_game(location_id) @@ -848,7 +848,7 @@ def alphanum_key(key: int) -> list[str|int]: if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] - location_button = TreeViewButton(text=f"custom victory button text: {victory_text}", size_hint=(None, None), height=dp(30), width=dp(400)) + location_button = TreeViewButton(text=victory_text, size_hint=(None, None), height=dp(30), width=dp(400)) location_button.victory = True location_button.location_name = victory_location["name"] location_button.bind(on_release=self.victory_button_callback) From 96a944bb7d51444001328a592b91c82e552b118b Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:21:12 -0400 Subject: [PATCH 07/21] added basic support for UT aliases --- src/ManualClient.py | 11 +++++++++-- src/__init__.py | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index fe03755e..b74d258b 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -179,7 +179,7 @@ def __init__(self, server_address, password, game, player_name) -> None: self.send_index: int = 0 self.syncing = False - self.game = game + self.game: str = game self.username = player_name async def server_auth(self, password_requested: bool = False): @@ -219,6 +219,11 @@ def suggested_game(self) -> str: from .Game import game_name # This will at least give us the name of a manual they've installed return Utils.persistent_load().get("client", {}).get("last_manual_game", game_name) + def get_location_UT_alias_by_id(self, id) -> str|None: + if hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): + return AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) + return None + def get_location_by_name(self, name) -> dict[str, Any]: location = self.location_table.get(name) if not location: @@ -836,7 +841,9 @@ def alphanum_key(key: int) -> list[str|int]: category_scroll.add_widget(category_layout) for location_id in self.listed_locations[location_category]: - location_button = TreeViewButton(text=self.ctx.location_names.lookup_in_game(location_id), size_hint=(None, None), height=30, width=400) + extra = f' ({alias})' if (alias := self.ctx.get_location_UT_alias_by_id(location_id)) is not None else '' + text = f"{self.ctx.location_names.lookup_in_game(location_id)}{extra}" + location_button = TreeViewButton(text=text, size_hint=(None, None), height=30, width=400) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) location_button.id = location_id location_button.location_name = self.ctx.location_names.lookup_in_game(location_id) diff --git a/src/__init__.py b/src/__init__.py index 5c703211..ad17c073 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -71,6 +71,7 @@ class ManualWorld(World): # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True + location_id_to_alias: dict[int, str] = {} origin_region_name = "Manual" From bdd43de9b5493dc7806ff1d866c12868ebea32ed Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:54:59 -0400 Subject: [PATCH 08/21] moved where get_location_UT_alias_by_id get location_id_to_alias to slotdata if possible --- src/ManualClient.py | 10 +++++++--- src/__init__.py | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index b74d258b..f2b990fa 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -148,6 +148,7 @@ class ManualContext(SuperContext): deathlink_out = False visible_events: dict[str, dict[str, Any]] = {} + location_id_to_alias: dict[str, str] = {} search_term = "" items_sorting = SortingOrderItem.default.name @@ -220,9 +221,11 @@ def suggested_game(self) -> str: return Utils.persistent_load().get("client", {}).get("last_manual_game", game_name) def get_location_UT_alias_by_id(self, id) -> str|None: - if hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): - return AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) - return None + alias = self.location_id_to_alias.get(str(id), None) + # Kept a fallback if its not in slotdata + if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): + alias = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) + return alias def get_location_by_name(self, name) -> dict[str, Any]: location = self.location_table.get(name) @@ -286,6 +289,7 @@ def on_package(self, cmd: str, args: dict): self.set_deathlink = True self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) + self.location_id_to_alias = args['slot_data'].get('location_id_to_alias', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() diff --git a/src/__init__.py b/src/__init__.py index ad17c073..f39e5672 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -418,11 +418,14 @@ def fill_slot_data(self): # slot_data["DeathLink"] = bool(self.multiworld.death_link[self.player].value) common_options = set(PerGameCommonOptions.type_hints.keys()) + common_options |= set(["generate_region_diagram", "start_inventory_from_pool"]) for option_key, _ in self.options_dataclass.type_hints.items(): if option_key in common_options: continue slot_data[option_key] = get_option_value(self.multiworld, self.player, option_key) + slot_data["location_id_to_alias"] = self.location_id_to_alias + slot_data["visible_events"] = {} for _, event in self.event_name_to_event.items(): event_name = event["name"] From 7aa239a75a95c0e4734e35b7c763de4788ea83b6 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:08:59 -0400 Subject: [PATCH 09/21] update SortingOrderCategories description --- src/ManualClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index f2b990fa..95b1822d 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -72,7 +72,7 @@ class SortingOrderCategories(IntEnum): inverted_natural = -2 default = 2 -SortingOrderCategories.alphabetical.__doc__ = "Sort alphabetically using the name of item defined in locations.json." +SortingOrderCategories.alphabetical.__doc__ = "Sort alphabetically using the name of the category." SortingOrderCategories.natural.__doc__ = "Sort like alphabetically but makes sure that any number are read as integer and thus sorted naturally. EG. key2 < key12" From 0facb723be8c6f08467898e571d5ddf6a105b55c Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:52:45 -0400 Subject: [PATCH 10/21] add UT alias support for victory location --- src/ManualClient.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 95b1822d..6cc0de50 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -858,7 +858,8 @@ def alphanum_key(key: int) -> list[str|int]: # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra = f' ({alias})' if (alias := self.ctx.get_location_UT_alias_by_id(victory_location["id"])) is not None else '' + victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra location_button = TreeViewButton(text=victory_text, size_hint=(None, None), height=dp(30), width=dp(400)) location_button.victory = True location_button.location_name = victory_location["name"] From f962997653ff562b91a2d42a283c3ba19ae3e489 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:56:31 -0400 Subject: [PATCH 11/21] add Support for basic item aliases --- src/ManualClient.py | 47 +++++++++++++++++++++++++++++++++------------ src/__init__.py | 3 +-- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 6cc0de50..a7a4df57 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -149,6 +149,7 @@ class ManualContext(SuperContext): visible_events: dict[str, dict[str, Any]] = {} location_id_to_alias: dict[str, str] = {} + item_id_to_alias: dict[str, str] = {} search_term = "" items_sorting = SortingOrderItem.default.name @@ -220,13 +221,20 @@ def suggested_game(self) -> str: from .Game import game_name # This will at least give us the name of a manual they've installed return Utils.persistent_load().get("client", {}).get("last_manual_game", game_name) - def get_location_UT_alias_by_id(self, id) -> str|None: + def get_location_alias_by_id(self, id) -> str|None: alias = self.location_id_to_alias.get(str(id), None) # Kept a fallback if its not in slotdata if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): alias = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) return alias + def get_item_alias_by_id(self, id) -> str|None: + alias = self.item_id_to_alias.get(str(id), None) + # Kept a fallback if its not in slotdata + if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): + alias = AutoWorldRegister.world_types[self.game].item_id_to_alias.get(id, None) + return alias + def get_location_by_name(self, name) -> dict[str, Any]: location = self.location_table.get(name) if not location: @@ -290,6 +298,7 @@ def on_package(self, cmd: str, args: dict): self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) self.location_id_to_alias = args['slot_data'].get('location_id_to_alias', {}) + self.item_id_to_alias = args['slot_data'].get('item_id_to_alias', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() @@ -396,6 +405,12 @@ class TreeViewButton(Button, TreeViewNode): id: int|None = None location_name: str = "" + class ItemLabel(Label): + item_id: int|None = None + item_count: int = 1 + item_name: str = "" + item_alias: str = "" + class TreeViewScrollView(ScrollView, TreeViewNode): pass @@ -845,7 +860,7 @@ def alphanum_key(key: int) -> list[str|int]: category_scroll.add_widget(category_layout) for location_id in self.listed_locations[location_category]: - extra = f' ({alias})' if (alias := self.ctx.get_location_UT_alias_by_id(location_id)) is not None else '' + extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(location_id)) is not None else '' text = f"{self.ctx.location_names.lookup_in_game(location_id)}{extra}" location_button = TreeViewButton(text=text, size_hint=(None, None), height=30, width=400) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) @@ -858,7 +873,7 @@ def alphanum_key(key: int) -> list[str|int]: # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - extra = f' ({alias})' if (alias := self.ctx.get_location_UT_alias_by_id(victory_location["id"])) is not None else '' + extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(victory_location["id"])) is not None else '' victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra location_button = TreeViewButton(text=victory_text, size_hint=(None, None), height=dp(30), width=dp(400)) location_button.victory = True @@ -931,11 +946,11 @@ def update_tracker_and_locations_table(self, update_highlights=False): # for items that were already listed, determine if the qty changed. if it did, add them to the list to be bolded for item in category_grid.children: - if type(item) is Label: + if type(item) is ItemLabel: # Get the item name from the item Label, minus quantity, then do a lookup for count - old_item_text = item.text - item_name = re.sub(r"\s\(\d+\)$", "", item.text) - item_id = self.ctx.item_names_to_id.get(item_name, False) + old_count = item.item_count + item_name = item.item_name + item_id = item.item_id if item_id: item_count = len(list(i for i in self.ctx.items_received if i.item == item_id)) else: @@ -956,10 +971,11 @@ def update_tracker_and_locations_table(self, update_highlights=False): category_unique_name_count += 1 # Update the label quantity - item.text="%s (%s)" % (item_name, item_count) - - if update_highlights and (old_item_text != item.text): + if update_highlights and (old_count != item_count): bold_item_labels.append(item_name) + item.item_count = item_count + + item.text="%s %s(%s)" % (item_name, item.item_alias, item_count) existing_item_labels.append(item_name) @@ -1005,8 +1021,13 @@ def alphanum_key(key: int) -> list[str|int]: if category_name in item_data["category"] and network_item not in self.listed_items[category_name]: item_count = len(list(i for i in self.ctx.items_received if i.item == network_item)) - item_text = Label(text="%s (%s)" % (item_name, item_count), + alias = f'({alias}) ' if (alias := self.ctx.get_item_alias_by_id(network_item)) is not None else '' + item_text = ItemLabel(text="%s %s(%s)" % (item_name, alias, item_count), size_hint=(None, None), height=dp(30), width=dp(400), bold=True) + item_text.item_name = item_name + item_text.item_count = item_count + item_text.item_id = network_item + item_text.item_alias = alias # if the item was previously listed and was bold, or if it wasn't previously listed at all, make it bold item_text.bold = (update_highlights and (item_name in bold_item_labels or item_name not in existing_item_labels)) @@ -1020,8 +1041,10 @@ def alphanum_key(key: int) -> list[str|int]: for event in sorted(self.ctx.tracker_reachable_events): if self.ctx.is_event_visible(event, category_name) and event not in self.listed_items[category_name]: item_count = len(list(i for i in self.ctx.tracker_reachable_events if i == event)) - item_text = Label(text="%s (%s)" % (event, item_count), + item_text = ItemLabel(text="%s (%s)" % (event, item_count), size_hint=(None, None), height=dp(30), width=dp(400), bold=True) + item_text.item_name = event + item_text.item_count = item_count category_grid.add_widget(item_text) self.listed_items[category_name].append(event) category_count += item_count diff --git a/src/__init__.py b/src/__init__.py index f39e5672..62409c94 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -72,6 +72,7 @@ class ManualWorld(World): # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True location_id_to_alias: dict[int, str] = {} + item_id_to_alias: dict[int, str] = {} origin_region_name = "Manual" @@ -424,8 +425,6 @@ def fill_slot_data(self): continue slot_data[option_key] = get_option_value(self.multiworld, self.player, option_key) - slot_data["location_id_to_alias"] = self.location_id_to_alias - slot_data["visible_events"] = {} for _, event in self.event_name_to_event.items(): event_name = event["name"] From 5cea50093356ad596b093d46fcfab57dfe4f8fa3 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:37:24 -0400 Subject: [PATCH 12/21] keep categories that starts with parentheses to the top of natural sort --- src/ManualClient.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index a7a4df57..02cf5dcf 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -809,10 +809,13 @@ def alphanum_key(key: int) -> list[str|int]: tracker_panel = TreeView(root_options=dict(text="Items Received (%d)" % (items_length)), size_hint_y=None) tracker_panel.bind(minimum_height=tracker_panel.setter('height')) + def category_sort_key(key: str): + result = natural_sort_key(key) + return [0 if key.lstrip().startswith("(") else 1] + result # Sorting items categories item_cat_sorting = SortingOrderCategories[self.ctx.items_categories_sorting] if abs(item_cat_sorting) == SortingOrderCategories.natural: - self.listed_items = {key: self.listed_items[key] for key in sorted(self.listed_items.keys(), key=natural_sort_key, reverse=item_cat_sorting < 0)} + self.listed_items = {key: self.listed_items[key] for key in sorted(self.listed_items.keys(), key=category_sort_key, reverse=item_cat_sorting < 0)} else: self.listed_items = {key: self.listed_items[key] for key in sorted(self.listed_items.keys(), reverse=item_cat_sorting < 0)} @@ -839,7 +842,7 @@ def alphanum_key(key: int) -> list[str|int]: # Sorting location categories loc_cat_sorting = SortingOrderCategories[self.ctx.locations_categories_sorting] if abs(loc_cat_sorting) == SortingOrderCategories.natural: - self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=natural_sort_key, reverse=loc_cat_sorting < 0)} + self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=category_sort_key, reverse=loc_cat_sorting < 0)} else: self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), reverse=loc_cat_sorting < 0)} From ea9c92860a7012632ee60de1f6975bd5e84442ac Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:29:07 -0400 Subject: [PATCH 13/21] rephrase the get_location_alias_by_id comments --- src/ManualClient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index a4751a22..373a545d 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -222,15 +222,15 @@ def suggested_game(self) -> str: return Utils.persistent_load().get("client", {}).get("last_manual_game", None) or game_name def get_location_alias_by_id(self, id) -> str|None: + # First we try to get it from slotdata for dynamic aliases alias = self.location_id_to_alias.get(str(id), None) - # Kept a fallback if its not in slotdata + # Secondly we try to get it from the world itself for a more static alias if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): alias = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) return alias def get_item_alias_by_id(self, id) -> str|None: alias = self.item_id_to_alias.get(str(id), None) - # Kept a fallback if its not in slotdata if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): alias = AutoWorldRegister.world_types[self.game].item_id_to_alias.get(id, None) return alias From 2e13b877b6bcafd4dfa1ebd803659424527012e9 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Thu, 28 May 2026 23:29:17 -0400 Subject: [PATCH 14/21] renamed the categories sorting settings keys --- src/ManualClient.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 21ef1fe0..35ec1464 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -153,9 +153,9 @@ class ManualContext(SuperContext): search_term = "" items_sorting = SortingOrderItem.default.name - items_categories_sorting = SortingOrderCategories.default.name + item_categories_sorting = SortingOrderCategories.default.name locations_sorting = SortingOrderLoc.default.name - locations_categories_sorting = SortingOrderCategories.default.name + location_categories_sorting = SortingOrderCategories.default.name block_unreachable_location_press = True colors = { @@ -462,8 +462,8 @@ def build(self) -> Layout: self.ctx.items_sorting = self.config.get('manual', 'items_sorting_order') self.ctx.locations_sorting = self.config.get('manual', 'locations_sorting_order') - self.ctx.items_categories_sorting = self.config.get('manual', 'items_categories_sorting_order') - self.ctx.locations_categories_sorting = self.config.get('manual', 'locations_categories_sorting_order') + self.ctx.item_categories_sorting = self.config.get('manual', 'item_categories_sorting_order') + self.ctx.location_categories_sorting = self.config.get('manual', 'location_categories_sorting_order') self.ctx.block_unreachable_location_press = True if self.config.get('universal-tracker', 'block_unreachable_location_press') == "Yes" else False self.manual_game_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(30)) @@ -499,8 +499,8 @@ def build_config(self, config: ConfigParser): config.setdefaults("manual", { "items_sorting_order": SortingOrderItem.default.name, "locations_sorting_order": SortingOrderLoc.default.name, - "items_categories_sorting_order": SortingOrderCategories.default.name, - "locations_categories_sorting_order": SortingOrderCategories.default.name + "item_categories_sorting_order": SortingOrderCategories.default.name, + "location_categories_sorting_order": SortingOrderCategories.default.name }) config.setdefaults("universal-tracker", { "block_unreachable_location_press": "Yes" @@ -532,17 +532,17 @@ def build_settings(self, settings: Settings): }, { "type": "options", - "title": "Items Category Sorting Order", + "title": "Item Categories Sorting Order", "section": "manual", - "key": "items_categories_sorting_order", + "key": "item_categories_sorting_order", "options": list(SortingOrderCategories._member_names_), "desc": '\n'.join([f'[b]{i.name}/inverted_{i.name}[/b]: {i.__doc__}' for i in SortingOrderCategories if i.__doc__ is not None]) }, { "type": "options", - "title": "Locations Sorting Order", + "title": "Location Categories Sorting Order", "section": "manual", - "key": "locations_categories_sorting_order", + "key": "location_categories_sorting_order", "options": list(SortingOrderCategories._member_names_), "desc": "Same Options as Items Category Sorting Order." }, @@ -576,14 +576,14 @@ def on_config_change(self, config, section, key, value): self.ctx.locations_sorting = value self.build_tracker_and_locations_table() self.request_update_tracker_and_locations_table() - elif key == "locations_categories_sorting_order": + elif key == "location_categories_sorting_order": if value in SortingOrderCategories._member_names_: - self.ctx.locations_categories_sorting = value + self.ctx.location_categories_sorting = value self.build_tracker_and_locations_table() self.request_update_tracker_and_locations_table() - elif key == "items_categories_sorting_order": + elif key == "item_categories_sorting_order": if value in SortingOrderCategories._member_names_: - self.ctx.items_categories_sorting = value + self.ctx.item_categories_sorting = value self.build_tracker_and_locations_table() self.request_update_tracker_and_locations_table() elif section == "universal-tracker": @@ -831,7 +831,7 @@ def category_sort_key(key: str): result = natural_sort_key(key) return [0 if key.lstrip().startswith("(") else 1] + result # Sorting items categories - item_cat_sorting = SortingOrderCategories[self.ctx.items_categories_sorting] + item_cat_sorting = SortingOrderCategories[self.ctx.item_categories_sorting] if abs(item_cat_sorting) == SortingOrderCategories.natural: self.listed_items = {key: self.listed_items[key] for key in sorted(self.listed_items.keys(), key=category_sort_key, reverse=item_cat_sorting < 0)} else: @@ -858,7 +858,7 @@ def category_sort_key(key: str): raise Exception("The apworld for %s is too outdated for this client. Please update it." % (self.ctx.game)) # Sorting location categories - loc_cat_sorting = SortingOrderCategories[self.ctx.locations_categories_sorting] + loc_cat_sorting = SortingOrderCategories[self.ctx.location_categories_sorting] if abs(loc_cat_sorting) == SortingOrderCategories.natural: self.listed_locations = {key: self.listed_locations[key] for key in sorted(self.listed_locations.keys(), key=category_sort_key, reverse=loc_cat_sorting < 0)} else: From 1f3dc567ef60de1dfbeda71eab7a5aacb956ccf9 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 30 May 2026 17:09:16 -0400 Subject: [PATCH 15/21] moved alias stuff to other branch --- src/ManualClient.py | 31 ++++--------------------------- src/__init__.py | 3 --- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 35ec1464..e1d072f2 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -148,8 +148,6 @@ class ManualContext(SuperContext): deathlink_out = False visible_events: dict[str, dict[str, Any]] = {} - location_id_to_alias: dict[str, str] = {} - item_id_to_alias: dict[str, str] = {} search_term = "" items_sorting = SortingOrderItem.default.name @@ -221,20 +219,6 @@ def suggested_game(self) -> str: from .Game import game_name # This will at least give us the name of a manual they've installed return Utils.persistent_load().get("client", {}).get("last_manual_game", None) or game_name - def get_location_alias_by_id(self, id) -> str|None: - # First we try to get it from slotdata for dynamic aliases - alias = self.location_id_to_alias.get(str(id), None) - # Secondly we try to get it from the world itself for a more static alias - if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): - alias = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) - return alias - - def get_item_alias_by_id(self, id) -> str|None: - alias = self.item_id_to_alias.get(str(id), None) - if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): - alias = AutoWorldRegister.world_types[self.game].item_id_to_alias.get(id, None) - return alias - def get_location_by_name(self, name) -> dict[str, Any]: location = self.location_table.get(name) if not location: @@ -297,8 +281,6 @@ def on_package(self, cmd: str, args: dict): self.set_deathlink = True self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) - self.location_id_to_alias = args['slot_data'].get('location_id_to_alias', {}) - self.item_id_to_alias = args['slot_data'].get('item_id_to_alias', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() @@ -409,7 +391,6 @@ class ItemLabel(Label): item_id: int|None = None item_count: int = 1 item_name: str = "" - item_alias: str = "" class TreeViewScrollView(ScrollView, TreeViewNode): pass @@ -883,8 +864,7 @@ def category_sort_key(key: str): for location_id in self.listed_locations[location_category]: has_hint = location_id in hinted_locations or location_id in scoutable_locations - extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(location_id)) is not None else '' - text = f"{self.ctx.location_names.lookup_in_game(location_id)}{extra}" + text = f"{self.ctx.location_names.lookup_in_game(location_id)}" location_button = TreeViewButton(text=text, size_hint=(.75 if has_hint else 1, None), height=30) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) location_button.id = location_id @@ -907,8 +887,7 @@ def category_sort_key(key: str): # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(victory_location["id"])) is not None else '' - victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra + victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] location_button = TreeViewButton(text=victory_text, size_hint=(1, None), height=dp(30), width=dp(400)) location_button.victory = True location_button.location_name = victory_location["name"] @@ -1009,7 +988,7 @@ def update_tracker_and_locations_table(self, update_highlights=False): bold_item_labels.append(item_name) item.item_count = item_count - item.text="%s %s(%s)" % (item_name, item.item_alias, item_count) + item.text="%s (%s)" % (item_name, item_count) existing_item_labels.append(item_name) @@ -1055,13 +1034,11 @@ def alphanum_key(key: int) -> list[str|int]: if category_name in item_data["category"] and network_item not in self.listed_items[category_name]: item_count = len(list(i for i in self.ctx.items_received if i.item == network_item)) - alias = f'({alias}) ' if (alias := self.ctx.get_item_alias_by_id(network_item)) is not None else '' - item_text = ItemLabel(text="%s %s(%s)" % (item_name, alias, item_count), + item_text = ItemLabel(text="%s (%s)" % (item_name, item_count), size_hint=(None, None), height=dp(30), width=dp(400), bold=True) item_text.item_name = item_name item_text.item_count = item_count item_text.item_id = network_item - item_text.item_alias = alias # if the item was previously listed and was bold, or if it wasn't previously listed at all, make it bold item_text.bold = (update_highlights and (item_name in bold_item_labels or item_name not in existing_item_labels)) diff --git a/src/__init__.py b/src/__init__.py index 62409c94..f942fc5f 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -71,9 +71,6 @@ class ManualWorld(World): # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True - location_id_to_alias: dict[int, str] = {} - item_id_to_alias: dict[int, str] = {} - origin_region_name = "Manual" def get_filler_item_name(self) -> str: From 75e2b566128fb9e2f75c00e4810b87d223f2b519 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 30 May 2026 17:14:15 -0400 Subject: [PATCH 16/21] Add alias code that was in PR #214 --- src/ManualClient.py | 31 +++++++++++++++++++++++++++---- src/__init__.py | 3 +++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index e1d072f2..35ec1464 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -148,6 +148,8 @@ class ManualContext(SuperContext): deathlink_out = False visible_events: dict[str, dict[str, Any]] = {} + location_id_to_alias: dict[str, str] = {} + item_id_to_alias: dict[str, str] = {} search_term = "" items_sorting = SortingOrderItem.default.name @@ -219,6 +221,20 @@ def suggested_game(self) -> str: from .Game import game_name # This will at least give us the name of a manual they've installed return Utils.persistent_load().get("client", {}).get("last_manual_game", None) or game_name + def get_location_alias_by_id(self, id) -> str|None: + # First we try to get it from slotdata for dynamic aliases + alias = self.location_id_to_alias.get(str(id), None) + # Secondly we try to get it from the world itself for a more static alias + if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): + alias = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) + return alias + + def get_item_alias_by_id(self, id) -> str|None: + alias = self.item_id_to_alias.get(str(id), None) + if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): + alias = AutoWorldRegister.world_types[self.game].item_id_to_alias.get(id, None) + return alias + def get_location_by_name(self, name) -> dict[str, Any]: location = self.location_table.get(name) if not location: @@ -281,6 +297,8 @@ def on_package(self, cmd: str, args: dict): self.set_deathlink = True self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) + self.location_id_to_alias = args['slot_data'].get('location_id_to_alias', {}) + self.item_id_to_alias = args['slot_data'].get('item_id_to_alias', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() @@ -391,6 +409,7 @@ class ItemLabel(Label): item_id: int|None = None item_count: int = 1 item_name: str = "" + item_alias: str = "" class TreeViewScrollView(ScrollView, TreeViewNode): pass @@ -864,7 +883,8 @@ def category_sort_key(key: str): for location_id in self.listed_locations[location_category]: has_hint = location_id in hinted_locations or location_id in scoutable_locations - text = f"{self.ctx.location_names.lookup_in_game(location_id)}" + extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(location_id)) is not None else '' + text = f"{self.ctx.location_names.lookup_in_game(location_id)}{extra}" location_button = TreeViewButton(text=text, size_hint=(.75 if has_hint else 1, None), height=30) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) location_button.id = location_id @@ -887,7 +907,8 @@ def category_sort_key(key: str): # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(victory_location["id"])) is not None else '' + victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra location_button = TreeViewButton(text=victory_text, size_hint=(1, None), height=dp(30), width=dp(400)) location_button.victory = True location_button.location_name = victory_location["name"] @@ -988,7 +1009,7 @@ def update_tracker_and_locations_table(self, update_highlights=False): bold_item_labels.append(item_name) item.item_count = item_count - item.text="%s (%s)" % (item_name, item_count) + item.text="%s %s(%s)" % (item_name, item.item_alias, item_count) existing_item_labels.append(item_name) @@ -1034,11 +1055,13 @@ def alphanum_key(key: int) -> list[str|int]: if category_name in item_data["category"] and network_item not in self.listed_items[category_name]: item_count = len(list(i for i in self.ctx.items_received if i.item == network_item)) - item_text = ItemLabel(text="%s (%s)" % (item_name, item_count), + alias = f'({alias}) ' if (alias := self.ctx.get_item_alias_by_id(network_item)) is not None else '' + item_text = ItemLabel(text="%s %s(%s)" % (item_name, alias, item_count), size_hint=(None, None), height=dp(30), width=dp(400), bold=True) item_text.item_name = item_name item_text.item_count = item_count item_text.item_id = network_item + item_text.item_alias = alias # if the item was previously listed and was bold, or if it wasn't previously listed at all, make it bold item_text.bold = (update_highlights and (item_name in bold_item_labels or item_name not in existing_item_labels)) diff --git a/src/__init__.py b/src/__init__.py index f942fc5f..62409c94 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -71,6 +71,9 @@ class ManualWorld(World): # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True + location_id_to_alias: dict[int, str] = {} + item_id_to_alias: dict[int, str] = {} + origin_region_name = "Manual" def get_filler_item_name(self) -> str: From 99367090001ff3f428de43348c60a4501877b490 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 30 May 2026 17:45:38 -0400 Subject: [PATCH 17/21] rename alias to description in the client --- src/ManualClient.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 35ec1464..71a60655 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -148,8 +148,8 @@ class ManualContext(SuperContext): deathlink_out = False visible_events: dict[str, dict[str, Any]] = {} - location_id_to_alias: dict[str, str] = {} - item_id_to_alias: dict[str, str] = {} + location_id_to_description: dict[str, str] = {} + item_id_to_description: dict[str, str] = {} search_term = "" items_sorting = SortingOrderItem.default.name @@ -221,19 +221,19 @@ def suggested_game(self) -> str: from .Game import game_name # This will at least give us the name of a manual they've installed return Utils.persistent_load().get("client", {}).get("last_manual_game", None) or game_name - def get_location_alias_by_id(self, id) -> str|None: - # First we try to get it from slotdata for dynamic aliases - alias = self.location_id_to_alias.get(str(id), None) + def get_location_description_by_id(self, id) -> str|None: + # First we try to get it from slotdata for dynamic descriptions + description = self.location_id_to_description.get(str(id), None) # Secondly we try to get it from the world itself for a more static alias - if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): - alias = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) - return alias + if description is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): + description = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) + return description - def get_item_alias_by_id(self, id) -> str|None: - alias = self.item_id_to_alias.get(str(id), None) - if alias is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): - alias = AutoWorldRegister.world_types[self.game].item_id_to_alias.get(id, None) - return alias + def get_item_description_by_id(self, id) -> str|None: + description = self.item_id_to_description.get(str(id), None) + if description is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): + description = AutoWorldRegister.world_types[self.game].item_id_to_description.get(id, None) + return description def get_location_by_name(self, name) -> dict[str, Any]: location = self.location_table.get(name) @@ -297,8 +297,8 @@ def on_package(self, cmd: str, args: dict): self.set_deathlink = True self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) - self.location_id_to_alias = args['slot_data'].get('location_id_to_alias', {}) - self.item_id_to_alias = args['slot_data'].get('item_id_to_alias', {}) + self.location_id_to_description = args['slot_data'].get('location_id_to_alias', {}) + self.item_id_to_description = args['slot_data'].get('item_id_to_alias', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() @@ -409,7 +409,7 @@ class ItemLabel(Label): item_id: int|None = None item_count: int = 1 item_name: str = "" - item_alias: str = "" + item_description: str = "" class TreeViewScrollView(ScrollView, TreeViewNode): pass @@ -883,7 +883,7 @@ def category_sort_key(key: str): for location_id in self.listed_locations[location_category]: has_hint = location_id in hinted_locations or location_id in scoutable_locations - extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(location_id)) is not None else '' + extra = f' ({description})' if (description := self.ctx.get_location_description_by_id(location_id)) is not None else '' text = f"{self.ctx.location_names.lookup_in_game(location_id)}{extra}" location_button = TreeViewButton(text=text, size_hint=(.75 if has_hint else 1, None), height=30) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) @@ -907,7 +907,7 @@ def category_sort_key(key: str): # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - extra = f' ({alias})' if (alias := self.ctx.get_location_alias_by_id(victory_location["id"])) is not None else '' + extra = f' ({description})' if (description := self.ctx.get_location_description_by_id(victory_location["id"])) is not None else '' victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra location_button = TreeViewButton(text=victory_text, size_hint=(1, None), height=dp(30), width=dp(400)) location_button.victory = True @@ -1009,7 +1009,7 @@ def update_tracker_and_locations_table(self, update_highlights=False): bold_item_labels.append(item_name) item.item_count = item_count - item.text="%s %s(%s)" % (item_name, item.item_alias, item_count) + item.text="%s %s(%s)" % (item_name, item.item_description, item_count) existing_item_labels.append(item_name) @@ -1055,13 +1055,13 @@ def alphanum_key(key: int) -> list[str|int]: if category_name in item_data["category"] and network_item not in self.listed_items[category_name]: item_count = len(list(i for i in self.ctx.items_received if i.item == network_item)) - alias = f'({alias}) ' if (alias := self.ctx.get_item_alias_by_id(network_item)) is not None else '' - item_text = ItemLabel(text="%s %s(%s)" % (item_name, alias, item_count), + description = f'({description}) ' if (description := self.ctx.get_item_description_by_id(network_item)) is not None else '' + item_text = ItemLabel(text="%s %s(%s)" % (item_name, description, item_count), size_hint=(None, None), height=dp(30), width=dp(400), bold=True) item_text.item_name = item_name item_text.item_count = item_count item_text.item_id = network_item - item_text.item_alias = alias + item_text.item_description = description # if the item was previously listed and was bold, or if it wasn't previously listed at all, make it bold item_text.bold = (update_highlights and (item_name in bold_item_labels or item_name not in existing_item_labels)) From 3cea3e562c7402d8cad9a617c6f3c07973b240d3 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 30 May 2026 17:52:23 -0400 Subject: [PATCH 18/21] Add full support for the item/loc descriptions --- src/Items.py | 4 ++++ src/Locations.py | 3 +++ src/__init__.py | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Items.py b/src/Items.py index 68c76776..c2783068 100644 --- a/src/Items.py +++ b/src/Items.py @@ -8,6 +8,7 @@ ###################### item_id_to_name: dict[int, str] = {} +item_id_to_description: dict[int, str] = {} item_name_to_item: dict[str, dict] = {} item_name_groups: dict[str, str] = {} advancement_item_names: set[str] = set() @@ -60,6 +61,9 @@ item_name_groups[group_name] = [] item_name_groups[group_name].append(item_name) + if item.get("description"): + item_id_to_description[item["id"]] = item["description"] + item_id_to_name[None] = "__Victory__" item_name_to_id = {name: id for id, name in item_id_to_name.items()} diff --git a/src/Locations.py b/src/Locations.py index d2ee209d..968aad10 100644 --- a/src/Locations.py +++ b/src/Locations.py @@ -45,6 +45,7 @@ victory_names.append("__Manual Game Complete__") location_id_to_name: dict[int, str] = {} +location_id_to_description: dict[int, str] = {} location_name_to_location: dict[str, dict[str, Any]] = {} location_name_groups: dict[str, list[str]] = {} event_name_to_event: dict[str, dict[str, Any]] = {} @@ -59,6 +60,8 @@ location_name_groups[c] = [] location_name_groups[c].append(loc_name) + if loc.get("description"): + location_id_to_description[loc["id"]] = loc["description"] # location_id_to_name[None] = "__Manual Game Complete__" location_name_to_id = {name: id for id, name in location_id_to_name.items()} diff --git a/src/__init__.py b/src/__init__.py index 62409c94..26322ede 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -10,8 +10,8 @@ from .Data import item_table, location_table, event_table, region_table, category_table from .Game import game_name, filler_item_name, starting_items from .Meta import world_description, world_webworld -from .Locations import location_id_to_name, location_name_to_id, location_name_to_location, location_name_groups, victory_names, event_name_to_event -from .Items import item_id_to_name, item_name_to_id, item_name_to_item, item_name_groups +from .Locations import location_id_to_name, location_name_to_id, location_name_to_location, location_name_groups, victory_names, event_name_to_event, location_id_to_description +from .Items import item_id_to_name, item_name_to_id, item_name_to_item, item_name_groups, item_id_to_description from .DataValidation import runGenerationDataValidation, runPreFillDataValidation from .Regions import create_regions, create_events @@ -54,6 +54,7 @@ class ManualWorld(World): item_name_to_id = item_name_to_id item_name_to_item = item_name_to_item item_name_groups = item_name_groups + item_id_to_alias = item_id_to_description # check location_id_to_alias for why its named this way filler_item_name = filler_item_name @@ -65,14 +66,13 @@ class ManualWorld(World): location_name_to_id = location_name_to_id location_name_to_location = location_name_to_location location_name_groups = location_name_groups + location_id_to_alias = location_id_to_description # by naming it this way, the description will also display in the UT Tracker tab victory_names = victory_names event_name_to_event = event_name_to_event # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True - location_id_to_alias: dict[int, str] = {} - item_id_to_alias: dict[int, str] = {} origin_region_name = "Manual" From 89c637e85541b6e3c13b92a8dc65f35ff5b1048f Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 30 May 2026 17:52:52 -0400 Subject: [PATCH 19/21] added item/location description to schema --- schemas/Manual.items.schema.json | 4 ++++ schemas/Manual.locations.schema.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/schemas/Manual.items.schema.json b/schemas/Manual.items.schema.json index 0298c05f..7fa4745c 100644 --- a/schemas/Manual.items.schema.json +++ b/schemas/Manual.items.schema.json @@ -116,6 +116,10 @@ "description": "(Optional) A string to sort the items by. If not provided, items will always be sorted by name.", "type": "string" }, + "description": { + "description": "(Optional) A short description of the item to be displayed in the client", + "type": "string" + }, "_comment": {"$ref": "#/definitions/comment"} }, "required": ["name"] diff --git a/schemas/Manual.locations.schema.json b/schemas/Manual.locations.schema.json index 829539df..52a7f6e6 100644 --- a/schemas/Manual.locations.schema.json +++ b/schemas/Manual.locations.schema.json @@ -104,6 +104,10 @@ "description": "(Optional) A string to sort the locations by. If not provided, locations will always be sorted by name.", "type": "string" }, + "description": { + "description": "(Optional) A short description of the location to be displayed in the client in the Manual and Tracker tabs", + "type": "string" + }, "_comment": {"$ref": "#/definitions/comment"} }, "required": ["name"] From b0da087c9c8fddfee1dba360f3cc956ecba4a1d2 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 30 May 2026 21:23:42 -0400 Subject: [PATCH 20/21] removed all reference to alias except for the 1 needed for UT --- src/ManualClient.py | 12 ++++++------ src/__init__.py | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ManualClient.py b/src/ManualClient.py index 71a60655..5d169433 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -224,14 +224,14 @@ def suggested_game(self) -> str: def get_location_description_by_id(self, id) -> str|None: # First we try to get it from slotdata for dynamic descriptions description = self.location_id_to_description.get(str(id), None) - # Secondly we try to get it from the world itself for a more static alias - if description is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_alias"): - description = AutoWorldRegister.world_types[self.game].location_id_to_alias.get(id, None) + # Secondly we try to get it from the world itself for a more static description + if description is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_description"): + description = AutoWorldRegister.world_types[self.game].location_id_to_description.get(id, None) return description def get_item_description_by_id(self, id) -> str|None: description = self.item_id_to_description.get(str(id), None) - if description is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_alias"): + if description is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_description"): description = AutoWorldRegister.world_types[self.game].item_id_to_description.get(id, None) return description @@ -297,8 +297,8 @@ def on_package(self, cmd: str, args: dict): self.set_deathlink = True self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) - self.location_id_to_description = args['slot_data'].get('location_id_to_alias', {}) - self.item_id_to_description = args['slot_data'].get('item_id_to_alias', {}) + self.location_id_to_description = args['slot_data'].get('location_id_to_description', {}) + self.item_id_to_description = args['slot_data'].get('item_id_to_description', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() diff --git a/src/__init__.py b/src/__init__.py index 26322ede..89f650e4 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -51,10 +51,10 @@ class ManualWorld(World): category_table = category_table item_id_to_name = item_id_to_name + item_id_to_description = item_id_to_description item_name_to_id = item_name_to_id item_name_to_item = item_name_to_item item_name_groups = item_name_groups - item_id_to_alias = item_id_to_description # check location_id_to_alias for why its named this way filler_item_name = filler_item_name @@ -63,16 +63,17 @@ class ManualWorld(World): start_inventory = {} location_id_to_name = location_id_to_name + location_id_to_description = location_id_to_description location_name_to_id = location_name_to_id location_name_to_location = location_name_to_location location_name_groups = location_name_groups - location_id_to_alias = location_id_to_description # by naming it this way, the description will also display in the UT Tracker tab victory_names = victory_names event_name_to_event = event_name_to_event # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True + location_id_to_alias = location_id_to_description origin_region_name = "Manual" From b2af75f478a69e98a8b689641d841376ca2a78c7 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:49:54 -0400 Subject: [PATCH 21/21] use the official webworld.item/loc_descriptions instead --- src/Items.py | 6 +++--- src/Locations.py | 6 +++--- src/ManualClient.py | 41 +++++++++++++++++++++-------------------- src/Meta.py | 8 +++++++- src/__init__.py | 8 +++----- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/Items.py b/src/Items.py index c2783068..ad5ce21a 100644 --- a/src/Items.py +++ b/src/Items.py @@ -8,8 +8,8 @@ ###################### item_id_to_name: dict[int, str] = {} -item_id_to_description: dict[int, str] = {} item_name_to_item: dict[str, dict] = {} +item_name_to_description: dict[str, str] = {} item_name_groups: dict[str, str] = {} advancement_item_names: set[str] = set() lastItemId = -1 @@ -39,7 +39,7 @@ count += 1 for item in item_table: - item_name = item.get("name", f"Unnamed Item {item['id']}") + item_name: str = item.get("name", f"Unnamed Item {item['id']}") item_id_to_name[item["id"]] = item_name item_name_to_item[item_name] = item @@ -62,7 +62,7 @@ item_name_groups[group_name].append(item_name) if item.get("description"): - item_id_to_description[item["id"]] = item["description"] + item_name_to_description[item_name] = item["description"] item_id_to_name[None] = "__Victory__" item_name_to_id = {name: id for id, name in item_id_to_name.items()} diff --git a/src/Locations.py b/src/Locations.py index 968aad10..75692e09 100644 --- a/src/Locations.py +++ b/src/Locations.py @@ -45,13 +45,13 @@ victory_names.append("__Manual Game Complete__") location_id_to_name: dict[int, str] = {} -location_id_to_description: dict[int, str] = {} location_name_to_location: dict[str, dict[str, Any]] = {} +location_name_to_description: dict[str, str] = {} location_name_groups: dict[str, list[str]] = {} event_name_to_event: dict[str, dict[str, Any]] = {} for loc in location_table: - loc_name = loc.get("name", f"Unnamed Location {loc['id']}") + loc_name: str = loc.get("name", f"Unnamed Location {loc['id']}") location_id_to_name[loc["id"]] = loc_name location_name_to_location[loc_name] = loc @@ -61,7 +61,7 @@ location_name_groups[c].append(loc_name) if loc.get("description"): - location_id_to_description[loc["id"]] = loc["description"] + location_name_to_description[loc_name] = loc["description"] # location_id_to_name[None] = "__Manual Game Complete__" location_name_to_id = {name: id for id, name in location_id_to_name.items()} diff --git a/src/ManualClient.py b/src/ManualClient.py index 5d169433..72c39da0 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -148,8 +148,8 @@ class ManualContext(SuperContext): deathlink_out = False visible_events: dict[str, dict[str, Any]] = {} - location_id_to_description: dict[str, str] = {} - item_id_to_description: dict[str, str] = {} + location_name_to_description: dict[str, str] = {} + item_name_to_description: dict[str, str] = {} search_term = "" items_sorting = SortingOrderItem.default.name @@ -221,18 +221,18 @@ def suggested_game(self) -> str: from .Game import game_name # This will at least give us the name of a manual they've installed return Utils.persistent_load().get("client", {}).get("last_manual_game", None) or game_name - def get_location_description_by_id(self, id) -> str|None: + def get_location_description_by_name(self, name: str) -> str|None: # First we try to get it from slotdata for dynamic descriptions - description = self.location_id_to_description.get(str(id), None) + description = self.location_name_to_description.get(name, None) # Secondly we try to get it from the world itself for a more static description - if description is None and hasattr(AutoWorldRegister.world_types[self.game], "location_id_to_description"): - description = AutoWorldRegister.world_types[self.game].location_id_to_description.get(id, None) + if description is None: + description = AutoWorldRegister.world_types[self.game].web.location_descriptions.get(name, None) return description - def get_item_description_by_id(self, id) -> str|None: - description = self.item_id_to_description.get(str(id), None) - if description is None and hasattr(AutoWorldRegister.world_types[self.game], "item_id_to_description"): - description = AutoWorldRegister.world_types[self.game].item_id_to_description.get(id, None) + def get_item_description_by_name(self, name: str) -> str|None: + description = self.item_name_to_description.get(name, None) + if description is None: + description = AutoWorldRegister.world_types[self.game].web.item_descriptions.get(name, None) return description def get_location_by_name(self, name) -> dict[str, Any]: @@ -297,8 +297,8 @@ def on_package(self, cmd: str, args: dict): self.set_deathlink = True self.last_death_link = 0 self.visible_events = args['slot_data'].get('visible_events', {}) - self.location_id_to_description = args['slot_data'].get('location_id_to_description', {}) - self.item_id_to_description = args['slot_data'].get('item_id_to_description', {}) + self.location_name_to_description = args['slot_data'].get('location_name_to_description', {}) + self.item_name_to_description = args['slot_data'].get('item_name_to_description', {}) logger.info(f"Slot data: {args['slot_data']}") self.ui.build_tracker_and_locations_table() @@ -882,13 +882,13 @@ def category_sort_key(key: str): for location_id in self.listed_locations[location_category]: has_hint = location_id in hinted_locations or location_id in scoutable_locations - - extra = f' ({description})' if (description := self.ctx.get_location_description_by_id(location_id)) is not None else '' - text = f"{self.ctx.location_names.lookup_in_game(location_id)}{extra}" + location_name = self.ctx.location_names.lookup_in_game(location_id) + extra = f' ({description})' if (description := self.ctx.get_location_description_by_name(location_name)) is not None else '' + text = f"{location_name}{extra}" location_button = TreeViewButton(text=text, size_hint=(.75 if has_hint else 1, None), height=30) location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args)) location_button.id = location_id - location_button.location_name = self.ctx.location_names.lookup_in_game(location_id) + location_button.location_name = location_name category_layout.add_widget(location_button) if location_id in hinted_locations: @@ -907,11 +907,12 @@ def category_sort_key(key: str): # ("category" not in victory_location_data and location_category == "(No Category)"): if location_category in victory_categories: # Add the Victory location to be marked at any point, which is why locations length has 1 added to it above - extra = f' ({description})' if (description := self.ctx.get_location_description_by_id(victory_location["id"])) is not None else '' - victory_text: str = "VICTORY! (seed finished)" if victory_location["name"] == "__Manual Game Complete__" else "GOAL: " + victory_location["name"] + extra + location_name = victory_location["name"] + extra = f' ({description})' if (description := self.ctx.get_location_description_by_name(location_name)) is not None else '' + victory_text: str = "VICTORY! (seed finished)" if location_name == "__Manual Game Complete__" else "GOAL: " + location_name + extra location_button = TreeViewButton(text=victory_text, size_hint=(1, None), height=dp(30), width=dp(400)) location_button.victory = True - location_button.location_name = victory_location["name"] + location_button.location_name = location_name location_button.bind(on_release=self.victory_button_callback) category_layout.add_widget(location_button) @@ -1055,7 +1056,7 @@ def alphanum_key(key: int) -> list[str|int]: if category_name in item_data["category"] and network_item not in self.listed_items[category_name]: item_count = len(list(i for i in self.ctx.items_received if i.item == network_item)) - description = f'({description}) ' if (description := self.ctx.get_item_description_by_id(network_item)) is not None else '' + description = f'({description}) ' if (description := self.ctx.get_item_description_by_name(item_name)) is not None else '' item_text = ItemLabel(text="%s %s(%s)" % (item_name, description, item_count), size_hint=(None, None), height=dp(30), width=dp(400), bold=True) item_text.item_name = item_name diff --git a/src/Meta.py b/src/Meta.py index 0056aff8..a9513a09 100644 --- a/src/Meta.py +++ b/src/Meta.py @@ -28,8 +28,14 @@ def set_world_description(base_doc: str) -> str: return base_doc -def set_world_webworld(web: WebWorld) -> WebWorld: +def set_world_webworld(web: ManualWeb) -> ManualWeb: from .Options import make_options_group + from .Locations import location_name_to_description + from .Items import item_name_to_description + + web.location_descriptions = location_name_to_description + web.item_descriptions = item_name_to_description + if meta_table.get("docs", {}).get("web", {}): Web_Config = meta_table["docs"]["web"] diff --git a/src/__init__.py b/src/__init__.py index 89f650e4..be101505 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -10,8 +10,8 @@ from .Data import item_table, location_table, event_table, region_table, category_table from .Game import game_name, filler_item_name, starting_items from .Meta import world_description, world_webworld -from .Locations import location_id_to_name, location_name_to_id, location_name_to_location, location_name_groups, victory_names, event_name_to_event, location_id_to_description -from .Items import item_id_to_name, item_name_to_id, item_name_to_item, item_name_groups, item_id_to_description +from .Locations import location_id_to_name, location_name_to_id, location_name_to_location, location_name_groups, victory_names, event_name_to_event, location_name_to_description +from .Items import item_id_to_name, item_name_to_id, item_name_to_item, item_name_groups from .DataValidation import runGenerationDataValidation, runPreFillDataValidation from .Regions import create_regions, create_events @@ -51,7 +51,6 @@ class ManualWorld(World): category_table = category_table item_id_to_name = item_id_to_name - item_id_to_description = item_id_to_description item_name_to_id = item_name_to_id item_name_to_item = item_name_to_item item_name_groups = item_name_groups @@ -63,7 +62,6 @@ class ManualWorld(World): start_inventory = {} location_id_to_name = location_id_to_name - location_id_to_description = location_id_to_description location_name_to_id = location_name_to_id location_name_to_location = location_name_to_location location_name_groups = location_name_groups @@ -73,7 +71,7 @@ class ManualWorld(World): # UT (the universal-est of trackers) can now generate without a YAML ut_can_gen_without_yaml = True - location_id_to_alias = location_id_to_description + location_id_to_alias: dict[int, str] = {location_name_to_id[name]: desc for name, desc in location_name_to_description.items()} origin_region_name = "Manual"