From 7eae4f7cae51e9fceb49e0c6f25fe767bd5789ba Mon Sep 17 00:00:00 2001 From: Jouramie Date: Fri, 28 Jun 2024 00:22:18 -0400 Subject: [PATCH 01/25] add basic text client with UT integration --- worlds/stardew_valley/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 61c866631690..6fbda140ceb4 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -3,7 +3,9 @@ from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld from Options import PerGameCommonOptions +from Utils import local_path from worlds.AutoWorld import World, WebWorld +from worlds.LauncherComponents import launch_subprocess, components, Component, icon_paths, Type from . import rules from .bundles.bundle_room import BundleRoom from .bundles.bundles import get_all_bundles @@ -53,6 +55,21 @@ class StardewWebWorld(WebWorld): )] +def launch_client(): + from .client import launch + launch_subprocess(launch, name="Stardew Valley Tracker") + + +components.append(Component( + "Stardew Valley Tracker", + func=launch_client, + component_type=Type.CLIENT, + icon='stardew' +)) + +icon_paths['stardew'] = local_path('data', 'stardew.png') + + class StardewValleyWorld(World): """ Stardew Valley is an open-ended country-life RPG. You can farm, fish, mine, fight, complete quests, From d2eee3559adea1f7e6a2536605c078650d8045c1 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Fri, 28 Jun 2024 00:22:27 -0400 Subject: [PATCH 02/25] add basic text client with UT integration --- data/stardew.ico | Bin 0 -> 67646 bytes data/stardew.png | Bin 0 -> 14573 bytes worlds/stardew_valley/client.py | 105 ++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 data/stardew.ico create mode 100644 data/stardew.png create mode 100644 worlds/stardew_valley/client.py diff --git a/data/stardew.ico b/data/stardew.ico new file mode 100644 index 0000000000000000000000000000000000000000..2a6c6a3beb605b9ff2b33c1fe9a16a0bea3db4a9 GIT binary patch literal 67646 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 L#koDO_xb+^12|aM literal 0 HcmV?d00001 diff --git a/data/stardew.png b/data/stardew.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec396836f05aff86bad18459ccce67addb3aee2 GIT binary patch literal 14573 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�%v2wOT@i zM354s<^sZTKmX3RJU02Gigc6S1wca$9cdCx9~PzoqI2(tm0JZOh&6IS`u5G;&x)iD frkajAIsot8GxW~>yZ#uY|5s?L>Z(*KAtL? Date: Fri, 28 Jun 2024 00:34:16 -0400 Subject: [PATCH 03/25] self review --- worlds/stardew_valley/client.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 3c8315025be3..7d54fd14455f 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -1,6 +1,5 @@ import asyncio -import Utils from CommonClient import logger, get_base_parser, gui_enabled try: @@ -96,9 +95,6 @@ async def main(): await ctx.exit_event.wait() await ctx.shutdown() - Utils.init_logging("StardewClient") - # options = Utils.get_options() - import colorama colorama.init() asyncio.run(main()) From 77e818b0861fe58a675bbc9dbbf57829bc86077d Mon Sep 17 00:00:00 2001 From: Jouramie Date: Fri, 28 Jun 2024 16:08:55 -0400 Subject: [PATCH 04/25] disable menu entry if tracker feature flag if False (which it is) --- worlds/stardew_valley/__init__.py | 22 ++++++++++++---------- worlds/stardew_valley/client.py | 6 ++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 6fbda140ceb4..4d1c9e3aef76 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -28,6 +28,7 @@ from .strings.region_names import Region as RegionName client_version = 0 +TRACKER_ENABLED = False class StardewLocation(Location): @@ -55,19 +56,20 @@ class StardewWebWorld(WebWorld): )] -def launch_client(): - from .client import launch - launch_subprocess(launch, name="Stardew Valley Tracker") +if TRACKER_ENABLED: + def launch_client(): + from .client import launch + launch_subprocess(launch, name="Stardew Valley Tracker") -components.append(Component( - "Stardew Valley Tracker", - func=launch_client, - component_type=Type.CLIENT, - icon='stardew' -)) + components.append(Component( + "Stardew Valley Tracker", + func=launch_client, + component_type=Type.CLIENT, + icon='stardew' + )) -icon_paths['stardew'] = local_path('data', 'stardew.png') + icon_paths['stardew'] = local_path('data', 'stardew.png') class StardewValleyWorld(World): diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 7d54fd14455f..2ba166257ac2 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -29,8 +29,6 @@ class BaseContext(CommonContext, TrackerGameContextMixin): tracker_loaded = False -DEBUG = False - class StardewCommandProcessor(ClientCommandProcessor): @@ -50,7 +48,7 @@ class StardewManager(GameManager): logging_pairs = [ ("Client", "Archipelago") ] - base_title = "Stardew Valley Archipelago Tracking Client" + base_title = "Stardew Valley Archipelago Tracker" ctx: StardewClientContext def build(self): @@ -78,7 +76,7 @@ async def server_auth(self, password_requested: bool = False): def launch(): async def main(): - parser = get_base_parser(description="Stardew Valley Archipelago Tracking Client") + parser = get_base_parser(description="Stardew Valley Archipelago Tracker") args = parser.parse_args() ctx = StardewClientContext(args.connect, args.password) From 79c0ba531d3bb8412edcabecdfdc0297395e9d84 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Tue, 2 Jul 2024 21:33:02 -0400 Subject: [PATCH 05/25] except ImportError --- worlds/stardew_valley/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 2ba166257ac2..13dc66c59161 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -6,7 +6,7 @@ from worlds.tracker.TrackerClient import TrackerGameContext as BaseContext, TrackerCommandProcessor as ClientCommandProcessor # noqa tracker_loaded = True -except ModuleNotFoundError: +except ImportError: from CommonClient import CommonContext, ClientCommandProcessor From 21bd402feea22a7a8b7f6c4483374798dc438a4b Mon Sep 17 00:00:00 2001 From: Jouramie Date: Sun, 30 Jun 2024 00:27:04 -0400 Subject: [PATCH 06/25] prove that's it's possible to log explain --- worlds/stardew_valley/__init__.py | 2 +- worlds/stardew_valley/client.py | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 7ae86ad66b2b..801ffde005b4 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -37,7 +37,7 @@ UNIVERSAL_TRACKER_SEED_PROPERTY = "ut_seed" client_version = 0 -TRACKER_ENABLED = False +TRACKER_ENABLED = True class StardewLocation(Location): diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 13dc66c59161..f2400adb7f49 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -1,6 +1,10 @@ +from __future__ import annotations + import asyncio from CommonClient import logger, get_base_parser, gui_enabled +from . import StardewLogic +from .stardew_rule.rule_explain import explain try: from worlds.tracker.TrackerClient import TrackerGameContext as BaseContext, TrackerCommandProcessor as ClientCommandProcessor # noqa @@ -31,15 +35,26 @@ class BaseContext(CommonContext, TrackerGameContextMixin): class StardewCommandProcessor(ClientCommandProcessor): + ctx: StardewClientContext - def _cmd_explain(self): + def _cmd_explain(self, item): """Coming soon.™""" - logger.info("Coming soon.™") + if self.ctx.logic is None: + logger.warning("Internal logic was not able to load, check your yamls and relaunch.") + return + + rule = self.ctx.logic.has(item) + expl = explain(rule, self.ctx.multiworld.state) + logger.info(expl) + + if not tracker_loaded: + del _cmd_explain class StardewClientContext(BaseContext): game = "Stardew Valley" command_processor = StardewCommandProcessor + logic: StardewLogic = None def run_gui(self): from kvui import GameManager @@ -66,6 +81,10 @@ def build(self): self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + def setup_logic(self): + if self.multiworld is not None: + self.logic = self.multiworld.worlds[self.player_id].logic + async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: await super(StardewClientContext, self).server_auth(password_requested) @@ -83,6 +102,10 @@ async def main(): if tracker_loaded: ctx.run_generator() + # FIXME that's probably not legit + if ctx.player_id is None: + ctx.player_id = 1 + ctx.setup_logic() else: logger.warning("Could not find Universal Tracker.") From 9f2011f0d14520467106ff85c6e9c3c6bcd38a5d Mon Sep 17 00:00:00 2001 From: Jouramie Date: Mon, 1 Jul 2024 17:06:30 -0400 Subject: [PATCH 07/25] add missing command --- worlds/stardew_valley/client.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index f2400adb7f49..88dafaf371ff 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -47,8 +47,21 @@ def _cmd_explain(self, item): expl = explain(rule, self.ctx.multiworld.state) logger.info(expl) + def _cmd_explain_missing(self, item): + """Coming soon.™""" + if self.ctx.logic is None: + logger.warning("Internal logic was not able to load, check your yamls and relaunch.") + return + + rule = self.ctx.logic.has(item) + state = self.ctx.multiworld.state + simplified, _ = rule.evaluate_while_simplifying(state) + expl = explain(simplified, state) + logger.info(expl) + if not tracker_loaded: del _cmd_explain + del _cmd_explain_missing class StardewClientContext(BaseContext): @@ -102,7 +115,7 @@ async def main(): if tracker_loaded: ctx.run_generator() - # FIXME that's probably not legit + # FIXME that's probably not legit, but it works when there is only one player if ctx.player_id is None: ctx.player_id = 1 ctx.setup_logic() From c86dc7b2f05be5a0bde0b6dfeb74af5734b9fbe0 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Mon, 1 Jul 2024 19:40:03 -0400 Subject: [PATCH 08/25] implement commands --- worlds/stardew_valley/client.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 88dafaf371ff..668993bf6919 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -2,7 +2,9 @@ import asyncio +from BaseClasses import MultiWorld from CommonClient import logger, get_base_parser, gui_enabled +from MultiServer import mark_raw from . import StardewLogic from .stardew_rule.rule_explain import explain @@ -16,6 +18,8 @@ class TrackerGameContextMixin: """Expecting the TrackerGameContext to have these methods.""" + multiworld: MultiWorld + player_id: int def build_gui(self, manager): ... @@ -37,23 +41,36 @@ class BaseContext(CommonContext, TrackerGameContextMixin): class StardewCommandProcessor(ClientCommandProcessor): ctx: StardewClientContext - def _cmd_explain(self, item): - """Coming soon.™""" + @mark_raw + def _cmd_explain(self, location: str = ""): + """Explain the logic behind a location.""" if self.ctx.logic is None: logger.warning("Internal logic was not able to load, check your yamls and relaunch.") return - rule = self.ctx.logic.has(item) + rule = self.ctx.logic.region.can_reach_location(location) expl = explain(rule, self.ctx.multiworld.state) logger.info(expl) - def _cmd_explain_missing(self, item): - """Coming soon.™""" + @mark_raw + def _cmd_explain_item(self, item: str = ""): + """Explain the logic behind a game item.""" if self.ctx.logic is None: logger.warning("Internal logic was not able to load, check your yamls and relaunch.") return rule = self.ctx.logic.has(item) + expl = explain(rule, self.ctx.multiworld.state) + logger.info(expl) + + @mark_raw + def _cmd_explain_missing(self, location: str = ""): + """Will tell you what's missing to consider a location in logic.""" + if self.ctx.logic is None: + logger.warning("Internal logic was not able to load, check your yamls and relaunch.") + return + + rule = self.ctx.logic.region.can_reach_location(location) state = self.ctx.multiworld.state simplified, _ = rule.evaluate_while_simplifying(state) expl = explain(simplified, state) From e24c338c1507746858812aafbf14960ad6707ac5 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sat, 14 Dec 2024 21:16:56 -0500 Subject: [PATCH 09/25] add command to explain more --- worlds/stardew_valley/client.py | 48 ++++++-- .../stardew_rule/rule_explain.py | 103 ++++++++++++++---- 2 files changed, 120 insertions(+), 31 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 668993bf6919..d6629a2d3e8f 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -5,8 +5,8 @@ from BaseClasses import MultiWorld from CommonClient import logger, get_base_parser, gui_enabled from MultiServer import mark_raw -from . import StardewLogic -from .stardew_rule.rule_explain import explain +from .logic.logic import StardewLogic +from .stardew_rule.rule_explain import explain, ExplainMode, RuleExplanation try: from worlds.tracker.TrackerClient import TrackerGameContext as BaseContext, TrackerCommandProcessor as ClientCommandProcessor # noqa @@ -49,8 +49,11 @@ def _cmd_explain(self, location: str = ""): return rule = self.ctx.logic.region.can_reach_location(location) - expl = explain(rule, self.ctx.multiworld.state) - logger.info(expl) + + expl = explain(rule, self.ctx.multiworld.state, mode=ExplainMode.CLIENT) + self.ctx.previous_explanation = expl + + logger.info(str(expl).strip()) @mark_raw def _cmd_explain_item(self, item: str = ""): @@ -60,8 +63,11 @@ def _cmd_explain_item(self, item: str = ""): return rule = self.ctx.logic.has(item) - expl = explain(rule, self.ctx.multiworld.state) - logger.info(expl) + + expl = explain(rule, self.ctx.multiworld.state, mode=ExplainMode.CLIENT) + self.ctx.previous_explanation = expl + + logger.info(str(expl).strip()) @mark_raw def _cmd_explain_missing(self, location: str = ""): @@ -73,8 +79,31 @@ def _cmd_explain_missing(self, location: str = ""): rule = self.ctx.logic.region.can_reach_location(location) state = self.ctx.multiworld.state simplified, _ = rule.evaluate_while_simplifying(state) - expl = explain(simplified, state) - logger.info(expl) + + expl = explain(simplified, state, mode=ExplainMode.CLIENT) + self.ctx.previous_explanation = expl + + logger.info(str(expl).strip()) + + @mark_raw + def _cmd_more(self, index: str = ""): + """Will tell you what's missing to consider a location in logic.""" + if self.ctx.logic is None: + logger.warning("Internal logic was not able to load, check your yamls and relaunch.") + return + + if self.ctx.previous_explanation is None: + logger.warning("No previous explanation found.") + return + + if not index or not index.isdigit(): + logger.warning("Which previous rule do you want to explained?") + return + + expl = self.ctx.previous_explanation.more(int(index)) + self.ctx.previous_explanation = expl + + logger.info(str(expl).strip()) if not tracker_loaded: del _cmd_explain @@ -84,7 +113,8 @@ def _cmd_explain_missing(self, location: str = ""): class StardewClientContext(BaseContext): game = "Stardew Valley" command_processor = StardewCommandProcessor - logic: StardewLogic = None + logic: StardewLogic | None = None + previous_explanation: RuleExplanation | None = None def run_gui(self): from kvui import GameManager diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index 2e2b9c959d7f..ea12d25b0bb3 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -1,5 +1,6 @@ from __future__ import annotations +import enum from dataclasses import dataclass, field from functools import cached_property, singledispatch from typing import Iterable, Set, Tuple, List, Optional @@ -9,12 +10,38 @@ from . import StardewRule, AggregatingStardewRule, Count, Has, TotalReceived, Received, Reach, true_ +class ExplainMode(enum.Enum): + VERBOSE = enum.auto() + CLIENT = enum.auto() + + +@dataclass +class MoreExplanation: + rule: StardewRule + state: CollectionState + more_index: int + + @cached_property + def result(self) -> bool: + try: + return self.rule(self.state) + except KeyError: + return False + + def __str__(self, depth=0): + summary = " " * depth + f"{str(self.rule)} -> {self.result}" + summary += f" [ use `/more {self.more_index}` to explain ]" + return summary + + @dataclass class RuleExplanation: rule: StardewRule state: CollectionState = field(repr=False, hash=False) expected: bool + mode: ExplainMode sub_rules: Iterable[StardewRule] = field(default_factory=list) + more_explanations: List[StardewRule] = field(default_factory=list, repr=False, hash=False) explored_rules_key: Set[Tuple[str, str]] = field(default_factory=set, repr=False, hash=False) current_rule_explored: bool = False @@ -38,6 +65,12 @@ def __str__(self, depth=0): if i.result is not self.expected else i.summary(depth + 1) for i in sorted(self.explained_sub_rules, key=lambda x: x.result)) + def more(self, more_index: int) -> RuleExplanation: + if more_index >= len(self.more_explanations): + return self + + return explain(self.more_explanations[more_index], self.state, self.expected, self.mode) + @cached_property def result(self) -> bool: try: @@ -51,7 +84,18 @@ def explained_sub_rules(self) -> List[RuleExplanation]: if rule_key is not None: self.explored_rules_key.add(rule_key) - return [_explain(i, self.state, self.expected, self.explored_rules_key) for i in self.sub_rules] + if self.mode == ExplainMode.CLIENT: + sub_explanations = [] + for sub_rule in self.sub_rules: + if isinstance(sub_rule, Reach) and sub_rule.resolution_hint == 'Entrance': + sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations))) + self.more_explanations.append(sub_rule) + else: + sub_explanations.append(_explain(sub_rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key)) + + return sub_explanations + + return [_explain(sub_rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key) for sub_rule in self.sub_rules] @dataclass @@ -60,7 +104,8 @@ class CountSubRuleExplanation(RuleExplanation): @staticmethod def from_explanation(expl: RuleExplanation, count: int) -> CountSubRuleExplanation: - return CountSubRuleExplanation(expl.rule, expl.state, expl.expected, expl.sub_rules, expl.explored_rules_key, expl.current_rule_explored, count) + return CountSubRuleExplanation(expl.rule, expl.state, expl.expected, expl.mode, expl.sub_rules, more_explanations=expl.more_explanations, + explored_rules_key=expl.explored_rules_key, current_rule_explored=expl.current_rule_explored, count=count) def summary(self, depth=0) -> str: summary = " " * depth + f"{self.count}x {str(self.rule)} -> {self.result}" @@ -76,48 +121,57 @@ class CountExplanation(RuleExplanation): @cached_property def explained_sub_rules(self) -> List[RuleExplanation]: return [ - CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.explored_rules_key), count) + CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key), + count) for rule, count in self.rule.counter.items() ] -def explain(rule: CollectionRule, state: CollectionState, expected: bool = True) -> RuleExplanation: +def explain(rule: CollectionRule, state: CollectionState, expected: bool = True, mode: ExplainMode = ExplainMode.VERBOSE) -> RuleExplanation: if isinstance(rule, StardewRule): - return _explain(rule, state, expected, explored_spots=set()) + return _explain(rule, state, expected, mode, more_explanations=list(), explored_spots=set()) else: return f"Value of rule {str(rule)} was not {str(expected)} in {str(state)}" # noqa @singledispatch -def _explain(rule: StardewRule, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) +def _explain(rule: StardewRule, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, rule.original_rules, explored_rules_key=explored_spots) +def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return RuleExplanation(rule, state, expected, mode, rule.original_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Count, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return CountExplanation(rule, state, expected, rule.rules, explored_rules_key=explored_spots) +def _(rule: Count, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return CountExplanation(rule, state, expected, mode, rule.rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Has, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: +def _(rule: Has, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: try: - return RuleExplanation(rule, state, expected, [rule.other_rules[rule.item]], explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, [rule.other_rules[rule.item]], more_explanations=more_explanations, + explored_rules_key=explored_spots) except KeyError: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: TotalReceived, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, [Received(i, rule.player, 1) for i in rule.items], explored_rules_key=explored_spots) +def _(rule: TotalReceived, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return RuleExplanation(rule, state, expected, mode, [Received(i, rule.player, 1) for i in rule.items], more_explanations=more_explanations, + explored_rules_key=explored_spots) @_explain.register -def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: +def _(rule: Reach, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: access_rules = None if rule.resolution_hint == 'Location': spot = state.multiworld.get_location(rule.spot, rule.player) @@ -135,6 +189,10 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T elif rule.resolution_hint == 'Entrance': spot = state.multiworld.get_entrance(rule.spot, rule.player) + if isinstance(spot.access_rule, StardewRule): + if spot.access_rule is not true_: + access_rules = [spot.access_rule] + if isinstance(spot.access_rule, StardewRule): if spot.access_rule is true_: access_rules = [Reach(spot.parent_region.name, "Region", rule.player)] @@ -149,13 +207,14 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T access_rules = [*(Reach(e.name, "Entrance", rule.player) for e in spot.entrances)] if not access_rules: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) - return RuleExplanation(rule, state, expected, access_rules, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, access_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Received, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: +def _(rule: Received, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: access_rules = None if rule.event: try: @@ -168,9 +227,9 @@ def _(rule: Received, state: CollectionState, expected: bool, explored_spots: Se pass if not access_rules: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) - return RuleExplanation(rule, state, expected, access_rules, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, access_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @singledispatch From f2204a8687d6e7fe246bb0c9ed294ef15c3ac9f9 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sat, 14 Dec 2024 21:43:28 -0500 Subject: [PATCH 10/25] allow no expectation, and add more space in client mode --- worlds/stardew_valley/client.py | 15 +++++--- .../stardew_rule/rule_explain.py | 37 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index d6629a2d3e8f..cc0928c5fdb3 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -50,7 +50,7 @@ def _cmd_explain(self, location: str = ""): rule = self.ctx.logic.region.can_reach_location(location) - expl = explain(rule, self.ctx.multiworld.state, mode=ExplainMode.CLIENT) + expl = explain(rule, self.ctx.multiworld.state, expected=None, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl logger.info(str(expl).strip()) @@ -64,14 +64,14 @@ def _cmd_explain_item(self, item: str = ""): rule = self.ctx.logic.has(item) - expl = explain(rule, self.ctx.multiworld.state, mode=ExplainMode.CLIENT) + expl = explain(rule, self.ctx.multiworld.state, expected=None, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl logger.info(str(expl).strip()) @mark_raw def _cmd_explain_missing(self, location: str = ""): - """Will tell you what's missing to consider a location in logic.""" + """Explain the logic behind a location, while skipping the rules that are already satisfied.""" if self.ctx.logic is None: logger.warning("Internal logic was not able to load, check your yamls and relaunch.") return @@ -96,11 +96,14 @@ def _cmd_more(self, index: str = ""): logger.warning("No previous explanation found.") return - if not index or not index.isdigit(): - logger.warning("Which previous rule do you want to explained?") + try: + expl = self.ctx.previous_explanation.more(int(index)) + except (ValueError, IndexError): + logger.info("Which previous rule do you want to explained?") + for i, rule in enumerate(self.ctx.previous_explanation.more_explanations): + logger.info(f"/more {i} -> {str(rule)})") return - expl = self.ctx.previous_explanation.more(int(index)) self.ctx.previous_explanation = expl logger.info(str(expl).strip()) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index ea12d25b0bb3..0bc9f8a1727a 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -20,6 +20,7 @@ class MoreExplanation: rule: StardewRule state: CollectionState more_index: int + mode: ExplainMode @cached_property def result(self) -> bool: @@ -29,8 +30,12 @@ def result(self) -> bool: return False def __str__(self, depth=0): + if self.mode is ExplainMode.CLIENT: + depth *= 2 + summary = " " * depth + f"{str(self.rule)} -> {self.result}" - summary += f" [ use `/more {self.more_index}` to explain ]" + summary += f" [use `/more {self.more_index}` to explain]" + return summary @@ -38,7 +43,7 @@ def __str__(self, depth=0): class RuleExplanation: rule: StardewRule state: CollectionState = field(repr=False, hash=False) - expected: bool + expected: bool | None mode: ExplainMode sub_rules: Iterable[StardewRule] = field(default_factory=list) more_explanations: List[StardewRule] = field(default_factory=list, repr=False, hash=False) @@ -52,23 +57,23 @@ def __post_init__(self): self.sub_rules = [] def summary(self, depth=0) -> str: + if self.mode is ExplainMode.CLIENT: + depth *= 2 + summary = " " * depth + f"{str(self.rule)} -> {self.result}" if self.current_rule_explored: summary += " [Already explained]" + return summary def __str__(self, depth=0): if not self.sub_rules: return self.summary(depth) - return self.summary(depth) + "\n" + "\n".join(i.__str__(depth + 1) - if i.result is not self.expected else i.summary(depth + 1) + return self.summary(depth) + "\n" + "\n".join(i.__str__(depth + 1) if self.expected is None or i.result is not self.expected else i.summary(depth + 1) for i in sorted(self.explained_sub_rules, key=lambda x: x.result)) def more(self, more_index: int) -> RuleExplanation: - if more_index >= len(self.more_explanations): - return self - return explain(self.more_explanations[more_index], self.state, self.expected, self.mode) @cached_property @@ -88,7 +93,7 @@ def explained_sub_rules(self) -> List[RuleExplanation]: sub_explanations = [] for sub_rule in self.sub_rules: if isinstance(sub_rule, Reach) and sub_rule.resolution_hint == 'Entrance': - sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations))) + sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations), self.mode)) self.more_explanations.append(sub_rule) else: sub_explanations.append(_explain(sub_rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key)) @@ -127,7 +132,7 @@ def explained_sub_rules(self) -> List[RuleExplanation]: ] -def explain(rule: CollectionRule, state: CollectionState, expected: bool = True, mode: ExplainMode = ExplainMode.VERBOSE) -> RuleExplanation: +def explain(rule: CollectionRule, state: CollectionState, expected: bool | None = True, mode: ExplainMode = ExplainMode.VERBOSE) -> RuleExplanation: if isinstance(rule, StardewRule): return _explain(rule, state, expected, mode, more_explanations=list(), explored_spots=set()) else: @@ -135,25 +140,25 @@ def explain(rule: CollectionRule, state: CollectionState, expected: bool = True, @singledispatch -def _explain(rule: StardewRule, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _explain(rule: StardewRule, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: return RuleExplanation(rule, state, expected, mode, rule.original_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Count, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _(rule: Count, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: return CountExplanation(rule, state, expected, mode, rule.rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Has, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _(rule: Has, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: try: return RuleExplanation(rule, state, expected, mode, [rule.other_rules[rule.item]], more_explanations=more_explanations, @@ -163,14 +168,14 @@ def _(rule: Has, state: CollectionState, expected: bool, mode: ExplainMode, more @_explain.register -def _(rule: TotalReceived, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _(rule: TotalReceived, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: return RuleExplanation(rule, state, expected, mode, [Received(i, rule.player, 1) for i in rule.items], more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Reach, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _(rule: Reach, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: access_rules = None if rule.resolution_hint == 'Location': @@ -213,7 +218,7 @@ def _(rule: Reach, state: CollectionState, expected: bool, mode: ExplainMode, mo @_explain.register -def _(rule: Received, state: CollectionState, expected: bool, mode: ExplainMode, more_explanations: list[StardewRule], +def _(rule: Received, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: access_rules = None if rule.event: From ca894ab05380058e79120ce209e92fff5c648cde Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sat, 14 Dec 2024 22:23:50 -0500 Subject: [PATCH 11/25] copied stuff from UT to get a working state --- worlds/stardew_valley/client.py | 49 +++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index cc0928c5fdb3..a45968c829a9 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -1,8 +1,9 @@ from __future__ import annotations import asyncio +from collections import Counter -from BaseClasses import MultiWorld +from BaseClasses import MultiWorld, CollectionState, ItemClassification from CommonClient import logger, get_base_parser, gui_enabled from MultiServer import mark_raw from .logic.logic import StardewLogic @@ -50,7 +51,7 @@ def _cmd_explain(self, location: str = ""): rule = self.ctx.logic.region.can_reach_location(location) - expl = explain(rule, self.ctx.multiworld.state, expected=None, mode=ExplainMode.CLIENT) + expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl logger.info(str(expl).strip()) @@ -64,7 +65,7 @@ def _cmd_explain_item(self, item: str = ""): rule = self.ctx.logic.has(item) - expl = explain(rule, self.ctx.multiworld.state, expected=None, mode=ExplainMode.CLIENT) + expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl logger.info(str(expl).strip()) @@ -77,7 +78,7 @@ def _cmd_explain_missing(self, location: str = ""): return rule = self.ctx.logic.region.can_reach_location(location) - state = self.ctx.multiworld.state + state = get_updated_state(self.ctx) simplified, _ = rule.evaluate_while_simplifying(state) expl = explain(simplified, state, mode=ExplainMode.CLIENT) @@ -88,10 +89,6 @@ def _cmd_explain_missing(self, location: str = ""): @mark_raw def _cmd_more(self, index: str = ""): """Will tell you what's missing to consider a location in logic.""" - if self.ctx.logic is None: - logger.warning("Internal logic was not able to load, check your yamls and relaunch.") - return - if self.ctx.previous_explanation is None: logger.warning("No previous explanation found.") return @@ -146,7 +143,7 @@ def build(self): def setup_logic(self): if self.multiworld is not None: - self.logic = self.multiworld.worlds[self.player_id].logic + self.logic = self.multiworld.worlds[1].logic async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: @@ -165,9 +162,6 @@ async def main(): if tracker_loaded: ctx.run_generator() - # FIXME that's probably not legit, but it works when there is only one player - if ctx.player_id is None: - ctx.player_id = 1 ctx.setup_logic() else: logger.warning("Could not find Universal Tracker.") @@ -183,3 +177,34 @@ async def main(): colorama.init() asyncio.run(main()) colorama.deinit() + + +# Don't mind me I just copy-pasted that from UT because it was too complicated to access their updated state. +def get_updated_state(ctx: StardewClientContext) -> CollectionState: + if ctx.player_id is None or ctx.multiworld is None: + logger.error("Player YAML not installed or Generator failed") + ctx.log_to_tab("Check Player YAMLs for error", False) + ctx.tracker_failed = True + raise ValueError("Player YAML not installed or Generator failed") + + state = CollectionState(ctx.multiworld) + state.sweep_for_advancements( + locations=(location for location in ctx.multiworld.get_locations() if (not location.address))) + prog_items = Counter() + all_items = Counter() + + item_id_to_name = ctx.multiworld.worlds[ctx.player_id].item_id_to_name + for item_name in [item_id_to_name[item[0]] for item in ctx.items_received] + ctx.manual_items: + try: + world_item = ctx.multiworld.create_item(item_name, ctx.player_id) + state.collect(world_item, True) + if world_item.classification == ItemClassification.progression or world_item.classification == ItemClassification.progression_skip_balancing: + prog_items[world_item.name] += 1 + if world_item.code is not None: + all_items[world_item.name] += 1 + except: + ctx.log_to_tab("Item id " + str(item_name) + " not able to be created", False) + state.sweep_for_advancements( + locations=(location for location in ctx.multiworld.get_locations() if (not location.address))) + + return state From 485d2548fc2285c6b8e7038e0f72a7baa6a58ad8 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sat, 14 Dec 2024 22:38:16 -0500 Subject: [PATCH 12/25] use ut context as base tracker --- worlds/stardew_valley/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index a45968c829a9..3a732916fdb9 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -10,7 +10,7 @@ from .stardew_rule.rule_explain import explain, ExplainMode, RuleExplanation try: - from worlds.tracker.TrackerClient import TrackerGameContext as BaseContext, TrackerCommandProcessor as ClientCommandProcessor # noqa + from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor # noqa tracker_loaded = True except ImportError: @@ -32,7 +32,7 @@ def load_kv(self): ... - class BaseContext(CommonContext, TrackerGameContextMixin): + class TrackerGameContext(CommonContext, TrackerGameContextMixin): pass @@ -110,7 +110,7 @@ def _cmd_more(self, index: str = ""): del _cmd_explain_missing -class StardewClientContext(BaseContext): +class StardewClientContext(TrackerGameContext): game = "Stardew Valley" command_processor = StardewCommandProcessor logic: StardewLogic | None = None @@ -180,7 +180,7 @@ async def main(): # Don't mind me I just copy-pasted that from UT because it was too complicated to access their updated state. -def get_updated_state(ctx: StardewClientContext) -> CollectionState: +def get_updated_state(ctx: TrackerGameContext) -> CollectionState: if ctx.player_id is None or ctx.multiworld is None: logger.error("Player YAML not installed or Generator failed") ctx.log_to_tab("Check Player YAMLs for error", False) From 45d3d04b90ffab0c4f91f39989131b971df87502 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:25:12 -0500 Subject: [PATCH 13/25] add fuzzy matching on names --- worlds/stardew_valley/client.py | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 3a732916fdb9..eed1c2ac2054 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -3,6 +3,7 @@ import asyncio from collections import Counter +import Utils from BaseClasses import MultiWorld, CollectionState, ItemClassification from CommonClient import logger, get_base_parser, gui_enabled from MultiServer import mark_raw @@ -49,11 +50,20 @@ def _cmd_explain(self, location: str = ""): logger.warning("Internal logic was not able to load, check your yamls and relaunch.") return - rule = self.ctx.logic.region.can_reach_location(location) + try: + rule = self.ctx.logic.region.can_reach_location(location) + expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) + except KeyError: + + result, usable, response = Utils.get_intended_text(location, [loc.name for loc in self.ctx.multiworld.get_locations(1)]) + if usable: + rule = self.ctx.logic.region.can_reach_location(result) + expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) + else: + logger.warning(response) + return - expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl - logger.info(str(expl).strip()) @mark_raw @@ -63,11 +73,15 @@ def _cmd_explain_item(self, item: str = ""): logger.warning("Internal logic was not able to load, check your yamls and relaunch.") return - rule = self.ctx.logic.has(item) + result, usable, response = Utils.get_intended_text(item, self.ctx.logic.registry.item_rules.keys()) + if usable: + rule = self.ctx.logic.has(result) + expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) + else: + logger.warning(response) + return - expl = explain(rule, get_updated_state(self.ctx), expected=None, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl - logger.info(str(expl).strip()) @mark_raw @@ -77,13 +91,24 @@ def _cmd_explain_missing(self, location: str = ""): logger.warning("Internal logic was not able to load, check your yamls and relaunch.") return - rule = self.ctx.logic.region.can_reach_location(location) - state = get_updated_state(self.ctx) - simplified, _ = rule.evaluate_while_simplifying(state) + try: + rule = self.ctx.logic.region.can_reach_location(location) + state = get_updated_state(self.ctx) + simplified, _ = rule.evaluate_while_simplifying(state) + expl = explain(simplified, state, mode=ExplainMode.CLIENT) + except KeyError: + + result, usable, response = Utils.get_intended_text(location, [loc.name for loc in self.ctx.multiworld.get_locations(1)]) + if usable: + rule = self.ctx.logic.region.can_reach_location(result) + state = get_updated_state(self.ctx) + simplified, _ = rule.evaluate_while_simplifying(state) + expl = explain(simplified, state, mode=ExplainMode.CLIENT) + else: + logger.warning(response) + return - expl = explain(simplified, state, mode=ExplainMode.CLIENT) self.ctx.previous_explanation = expl - logger.info(str(expl).strip()) @mark_raw From f67c31a2709bbbd122b73d2dbde6143488675af2 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 00:32:41 -0500 Subject: [PATCH 14/25] best effet to find out if ut is installed --- worlds/stardew_valley/__init__.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 801ffde005b4..34a84f96c5d6 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -66,19 +66,24 @@ class StardewWebWorld(WebWorld): if TRACKER_ENABLED: - def launch_client(): - from .client import launch - launch_subprocess(launch, name="Stardew Valley Tracker") + from .. import user_folder + import os + # Best effort to detect if universal tracker is installed + if any("tracker" in f.name for f in os.scandir(user_folder)): + def launch_client(): + from .client import launch + launch_subprocess(launch, name="Stardew Valley Tracker") - components.append(Component( - "Stardew Valley Tracker", - func=launch_client, - component_type=Type.CLIENT, - icon='stardew' - )) - icon_paths['stardew'] = local_path('data', 'stardew.png') + components.append(Component( + "Stardew Valley Tracker", + func=launch_client, + component_type=Type.CLIENT, + icon='stardew' + )) + + icon_paths['stardew'] = local_path('data', 'stardew.png') class StardewValleyWorld(World): From bd984609bd1ede5a754caa753e319353e5b94cba Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 00:54:38 -0500 Subject: [PATCH 15/25] best effet to find out if ut is installed --- worlds/stardew_valley/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 34a84f96c5d6..107c52b15e5c 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -70,7 +70,7 @@ class StardewWebWorld(WebWorld): import os # Best effort to detect if universal tracker is installed - if any("tracker" in f.name for f in os.scandir(user_folder)): + if any("tracker.apworld" in f.name for f in os.scandir(user_folder)): def launch_client(): from .client import launch launch_subprocess(launch, name="Stardew Valley Tracker") From bf443ebfb11a316e593e97c8a88ac7ba10ce1b4a Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 01:31:22 -0500 Subject: [PATCH 16/25] move icon in stardew apworld --- worlds/stardew_valley/__init__.py | 3 +-- {data => worlds/stardew_valley}/stardew.ico | Bin {data => worlds/stardew_valley}/stardew.png | Bin 3 files changed, 1 insertion(+), 2 deletions(-) rename {data => worlds/stardew_valley}/stardew.ico (100%) rename {data => worlds/stardew_valley}/stardew.png (100%) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 107c52b15e5c..c5223e346720 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -4,7 +4,6 @@ from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions -from Utils import local_path from worlds.AutoWorld import World, WebWorld from worlds.LauncherComponents import launch_subprocess, components, Component, icon_paths, Type from . import rules @@ -83,7 +82,7 @@ def launch_client(): icon='stardew' )) - icon_paths['stardew'] = local_path('data', 'stardew.png') + icon_paths['stardew'] = f"ap:{__name__}/stardew.png" class StardewValleyWorld(World): diff --git a/data/stardew.ico b/worlds/stardew_valley/stardew.ico similarity index 100% rename from data/stardew.ico rename to worlds/stardew_valley/stardew.ico diff --git a/data/stardew.png b/worlds/stardew_valley/stardew.png similarity index 100% rename from data/stardew.png rename to worlds/stardew_valley/stardew.png From 71f3f5f88e358769ee204df8b3c6bd42bd9f60ba Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 12:24:49 -0500 Subject: [PATCH 17/25] adapt to new ut versions --- worlds/stardew_valley/client.py | 98 ++++++++++++++++----------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index eed1c2ac2054..ba5d4979bede 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -1,17 +1,19 @@ from __future__ import annotations import asyncio +# webserver imports +import urllib.parse from collections import Counter import Utils from BaseClasses import MultiWorld, CollectionState, ItemClassification -from CommonClient import logger, get_base_parser, gui_enabled +from CommonClient import logger, get_base_parser, gui_enabled, server_loop from MultiServer import mark_raw from .logic.logic import StardewLogic from .stardew_rule.rule_explain import explain, ExplainMode, RuleExplanation try: - from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor # noqa + from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor, UT_VERSION # noqa tracker_loaded = True except ImportError: @@ -38,6 +40,7 @@ class TrackerGameContext(CommonContext, TrackerGameContextMixin): tracker_loaded = False + UT_VERSION = "Not found" class StardewCommandProcessor(ClientCommandProcessor): @@ -141,68 +144,26 @@ class StardewClientContext(TrackerGameContext): logic: StardewLogic | None = None previous_explanation: RuleExplanation | None = None - def run_gui(self): - from kvui import GameManager + def make_gui(self): + ui = super().make_gui() # before the kivy imports so kvui gets loaded first - class StardewManager(GameManager): - logging_pairs = [ - ("Client", "Archipelago") - ] - base_title = "Stardew Valley Archipelago Tracker" + class StardewManager(ui): + base_title = f"Stardew Valley Tracker with UT {UT_VERSION} for AP version" # core appends ap version so this works ctx: StardewClientContext def build(self): container = super().build() - if tracker_loaded: - self.ctx.build_gui(self) - else: + if not tracker_loaded: logger.info("To enable the tracker page, install Universal Tracker.") return container - self.ui = StardewManager(self) - if tracker_loaded: - self.load_kv() - - self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + return StardewManager def setup_logic(self): if self.multiworld is not None: self.logic = self.multiworld.worlds[1].logic - async def server_auth(self, password_requested: bool = False): - if password_requested and not self.password: - await super(StardewClientContext, self).server_auth(password_requested) - - await self.get_username() - await self.send_connect() - - -def launch(): - async def main(): - parser = get_base_parser(description="Stardew Valley Archipelago Tracker") - args = parser.parse_args() - - ctx = StardewClientContext(args.connect, args.password) - - if tracker_loaded: - ctx.run_generator() - ctx.setup_logic() - else: - logger.warning("Could not find Universal Tracker.") - - if gui_enabled: - ctx.run_gui() - ctx.run_cli() - - await ctx.exit_event.wait() - await ctx.shutdown() - - import colorama - colorama.init() - asyncio.run(main()) - colorama.deinit() - # Don't mind me I just copy-pasted that from UT because it was too complicated to access their updated state. def get_updated_state(ctx: TrackerGameContext) -> CollectionState: @@ -233,3 +194,40 @@ def get_updated_state(ctx: TrackerGameContext) -> CollectionState: locations=(location for location in ctx.multiworld.get_locations() if (not location.address))) return state + + +async def main(args): + ctx = StardewClientContext(args.connect, args.password) + + ctx.auth = args.name + ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop") + + if tracker_loaded: + ctx.run_generator() + ctx.setup_logic() + else: + logger.warning("Could not find Universal Tracker.") + + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + + await ctx.exit_event.wait() + await ctx.shutdown() + + +def launch(*args): + parser = get_base_parser(description="Gameless Archipelago Client, for text interfacing.") + parser.add_argument('--name', default=None, help="Slot Name to connect as.") + parser.add_argument("url", nargs="?", help="Archipelago connection url") + args = parser.parse_args(args) + + if args.url: + url = urllib.parse.urlparse(args.url) + args.connect = url.netloc + if url.username: + args.name = urllib.parse.unquote(url.username) + if url.password: + args.password = urllib.parse.unquote(url.password) + + asyncio.run(main(args)) From 2a50bd41e2aa2ffc375442ff1cc55fbf4faa3b25 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 13:05:24 -0500 Subject: [PATCH 18/25] color true and false for better readability --- worlds/stardew_valley/client.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index ba5d4979bede..e7b4acbff89f 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import re # webserver imports import urllib.parse from collections import Counter @@ -9,6 +10,7 @@ from BaseClasses import MultiWorld, CollectionState, ItemClassification from CommonClient import logger, get_base_parser, gui_enabled, server_loop from MultiServer import mark_raw +from NetUtils import JSONMessagePart from .logic.logic import StardewLogic from .stardew_rule.rule_explain import explain, ExplainMode, RuleExplanation @@ -112,7 +114,7 @@ def _cmd_explain_missing(self, location: str = ""): return self.ctx.previous_explanation = expl - logger.info(str(expl).strip()) + self.ctx.ui.print_json(parse_explanation(expl)) @mark_raw def _cmd_more(self, index: str = ""): @@ -165,6 +167,22 @@ def setup_logic(self): self.logic = self.multiworld.worlds[1].logic +def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: + result_regex = f"(True|False)" + splits = re.split(result_regex, str(explanation)) + + messages = [] + for s in splits: + if s == "True": + messages.append({"text": s, "type": "color", "color": "green"}) + elif s == "False": + messages.append({"text": s, "type": "color", "color": "salmon"}) + else: + messages.append({"text": s, "type": "text"}) + + return messages + + # Don't mind me I just copy-pasted that from UT because it was too complicated to access their updated state. def get_updated_state(ctx: TrackerGameContext) -> CollectionState: if ctx.player_id is None or ctx.multiworld is None: From b53c982f38589fc726cc714a58497ab591f056e3 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:05:42 -0500 Subject: [PATCH 19/25] add color to location, region, entrances and items --- worlds/stardew_valley/client.py | 29 +++++++++++++++++-- .../stardew_rule/rule_explain.py | 3 ++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index e7b4acbff89f..b8b3ec65bb75 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -168,15 +168,38 @@ def setup_logic(self): def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: - result_regex = f"(True|False)" + result_regex = r"(\(|\)| & | -> | \| | \[.*\]\s*| \(\w+\)|\n\s*)" splits = re.split(result_regex, str(explanation)) messages = [] for s in splits: + if len(s) == 0: + continue + if s == "True": - messages.append({"text": s, "type": "color", "color": "green"}) + messages.append({"type": "color", "color": "green", "text": s}) elif s == "False": - messages.append({"text": s, "type": "color", "color": "salmon"}) + messages.append({"type": "color", "color": "salmon", "text": s}) + elif s.startswith("Reach Location "): + messages.append({"type": "text", "text": "Reach Location "}) + messages.append({"type": "location_name", "text": s[15:]}) + elif s.startswith("Reach Entrance "): + messages.append({"type": "text", "text": "Reach Entrance "}) + messages.append({"type": "entrance_name", "text": s[15:]}) + elif s.startswith("Reach Region "): + messages.append({"type": "text", "text": "Reach Region "}) + messages.append({"type": "color", "color": "yellow", "text": s[13:]}) + elif s.startswith("Received event "): + messages.append({"type": "text", "text": "Received event "}) + messages.append({"type": "item_name", "text": s[15:]}) + elif s.startswith("Received "): + messages.append({"type": "text", "text": "Received "}) + messages.append({"type": "item_name", "text": s[9:]}) + elif s.startswith(" ["): + if len(s) <= 50: + messages.append({"type": "text", "text": s}) + else: + messages.append({"type": "text", "text": " [ ... ] "}) else: messages.append({"text": s, "type": "text"}) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index 0bc9f8a1727a..a28ab675d2e0 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -113,6 +113,9 @@ def from_explanation(expl: RuleExplanation, count: int) -> CountSubRuleExplanati explored_rules_key=expl.explored_rules_key, current_rule_explored=expl.current_rule_explored, count=count) def summary(self, depth=0) -> str: + if self.mode is ExplainMode.CLIENT: + depth *= 2 + summary = " " * depth + f"{self.count}x {str(self.rule)} -> {self.result}" if self.current_rule_explored: summary += " [Already explained]" From 72a2685ec395352d8e5bde5d7df194273f9e1593 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:37:52 -0500 Subject: [PATCH 20/25] fix count explain being way to big --- worlds/stardew_valley/client.py | 17 +++++++++++------ worlds/stardew_valley/stardew_rule/base.py | 11 ++++++++++- .../stardew_valley/stardew_rule/rule_explain.py | 3 +++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index b8b3ec65bb75..1f66cc58e79d 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -168,7 +168,7 @@ def setup_logic(self): def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: - result_regex = r"(\(|\)| & | -> | \| | \[.*\]\s*| \(\w+\)|\n\s*)" + result_regex = r"(\(|\)| & | -> | \| | \[.*\](?: ->)?\s*| \(\w+\)|\n\s*)" splits = re.split(result_regex, str(explanation)) messages = [] @@ -194,12 +194,17 @@ def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: messages.append({"type": "item_name", "text": s[15:]}) elif s.startswith("Received "): messages.append({"type": "text", "text": "Received "}) - messages.append({"type": "item_name", "text": s[9:]}) - elif s.startswith(" ["): - if len(s) <= 50: - messages.append({"type": "text", "text": s}) + messages.append({"type": "item_name", "flags": 0b001, "text": s[9:]}) + elif s.startswith("Has "): + if s[4].isdigit(): + messages.append({"type": "text", "text": "Has "}) + digit_end = re.search(r"\D", s[4:]) + digit = s[4:4 + digit_end.start()] + messages.append({"type": "color", "color": "cyan", "text": digit}) + messages.append({"type": "text", "text": s[4 + digit_end.start():]}) + else: - messages.append({"type": "text", "text": " [ ... ] "}) + messages.append({"text": s, "type": "text"}) else: messages.append({"text": s, "type": "text"}) diff --git a/worlds/stardew_valley/stardew_rule/base.py b/worlds/stardew_valley/stardew_rule/base.py index af4c3c35330d..dad294da15c3 100644 --- a/worlds/stardew_valley/stardew_rule/base.py +++ b/worlds/stardew_valley/stardew_rule/base.py @@ -430,8 +430,17 @@ def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRul def rules_count(self): return len(self.rules) + def __str__(self): + if all(value == 1 for value in self.counter.values()): + return f"Has {self.count} of [{', '.join(str(rule) for rule in self.counter.keys())}]" + + return f"Has {self.count} of [{', '.join(f'{value}x {str(rule)}' for rule, value in self.counter.items())}]" + def __repr__(self): - return f"Received {self.count} [{', '.join(f'{value}x {repr(rule)}' for rule, value in self.counter.items())}]" + if all(value == 1 for value in self.counter.values()): + return f"Has {self.count} of [{', '.join(repr(rule) for rule in self.counter.keys())}]" + + return f"Has {self.count} of [{', '.join(f'{value}x {repr(rule)}' for rule, value in self.counter.items())}]" @dataclass(frozen=True) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index a28ab675d2e0..dc6d5ee98df1 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -128,6 +128,9 @@ class CountExplanation(RuleExplanation): @cached_property def explained_sub_rules(self) -> List[RuleExplanation]: + if all(value == 1 for value in self.rule.counter.values()): + return super().explained_sub_rules + return [ CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key), count) From 4738ce7441e0ef7436a922a855a3f738940a0736 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:46:03 -0500 Subject: [PATCH 21/25] add colors to other commands --- worlds/stardew_valley/client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 1f66cc58e79d..7ac9456b0755 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -69,7 +69,7 @@ def _cmd_explain(self, location: str = ""): return self.ctx.previous_explanation = expl - logger.info(str(expl).strip()) + self.ctx.ui.print_json(parse_explanation(expl)) @mark_raw def _cmd_explain_item(self, item: str = ""): @@ -87,7 +87,7 @@ def _cmd_explain_item(self, item: str = ""): return self.ctx.previous_explanation = expl - logger.info(str(expl).strip()) + self.ctx.ui.print_json(parse_explanation(expl)) @mark_raw def _cmd_explain_missing(self, location: str = ""): @@ -132,8 +132,7 @@ def _cmd_more(self, index: str = ""): return self.ctx.previous_explanation = expl - - logger.info(str(expl).strip()) + self.ctx.ui.print_json(parse_explanation(expl)) if not tracker_loaded: del _cmd_explain @@ -169,7 +168,7 @@ def setup_logic(self): def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: result_regex = r"(\(|\)| & | -> | \| | \[.*\](?: ->)?\s*| \(\w+\)|\n\s*)" - splits = re.split(result_regex, str(explanation)) + splits = re.split(result_regex, str(explanation).strip()) messages = [] for s in splits: From 9260891982303c3f8cca8c2d11d35dc8d1f2322b Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:53:06 -0500 Subject: [PATCH 22/25] add color in count explanation --- worlds/stardew_valley/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 7ac9456b0755..1144634d1e76 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -167,7 +167,7 @@ def setup_logic(self): def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: - result_regex = r"(\(|\)| & | -> | \| | \[.*\](?: ->)?\s*| \(\w+\)|\n\s*)" + result_regex = r"(\(|\)| & | -> | \| | \[|\](?: ->)?\s*| \(\w+\)|\n\s*)" splits = re.split(result_regex, str(explanation).strip()) messages = [] From 2227fc7a1a6f4d178f222ab3d6f208c295f679a5 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 15:26:44 -0500 Subject: [PATCH 23/25] Pushback location explains in more explain --- worlds/stardew_valley/stardew_rule/rule_explain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index dc6d5ee98df1..2b6d60d2b13b 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -95,6 +95,9 @@ def explained_sub_rules(self) -> List[RuleExplanation]: if isinstance(sub_rule, Reach) and sub_rule.resolution_hint == 'Entrance': sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations), self.mode)) self.more_explanations.append(sub_rule) + elif isinstance(sub_rule, Reach) and sub_rule.resolution_hint == 'Location': + sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations), self.mode)) + self.more_explanations.append(sub_rule) else: sub_explanations.append(_explain(sub_rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key)) From 770dae0de52e19e70e2102f581a245be59b96a4e Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:43:18 -0500 Subject: [PATCH 24/25] properly color count subrules --- worlds/stardew_valley/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 1144634d1e76..2a15070e5640 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -167,7 +167,7 @@ def setup_logic(self): def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: - result_regex = r"(\(|\)| & | -> | \| | \[|\](?: ->)?\s*| \(\w+\)|\n\s*)" + result_regex = r"(\(|\)| & | -> | \| |\d+x | \[|\](?: ->)?\s*| \(\w+\)|\n\s*)" splits = re.split(result_regex, str(explanation).strip()) messages = [] From f1d7f7bc630d797259421e1fec088b9ab1a20394 Mon Sep 17 00:00:00 2001 From: Jouramie <16137441+Jouramie@users.noreply.github.com> Date: Sun, 15 Dec 2024 22:09:17 -0500 Subject: [PATCH 25/25] change get_updated_state to call directly updateTracker --- worlds/stardew_valley/client.py | 34 +++------------------------------ 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py index 2a15070e5640..eece4d9b2ee8 100644 --- a/worlds/stardew_valley/client.py +++ b/worlds/stardew_valley/client.py @@ -4,10 +4,9 @@ import re # webserver imports import urllib.parse -from collections import Counter import Utils -from BaseClasses import MultiWorld, CollectionState, ItemClassification +from BaseClasses import MultiWorld, CollectionState from CommonClient import logger, get_base_parser, gui_enabled, server_loop from MultiServer import mark_raw from NetUtils import JSONMessagePart @@ -15,7 +14,7 @@ from .stardew_rule.rule_explain import explain, ExplainMode, RuleExplanation try: - from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor, UT_VERSION # noqa + from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor, UT_VERSION, updateTracker # noqa tracker_loaded = True except ImportError: @@ -210,35 +209,8 @@ def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: return messages -# Don't mind me I just copy-pasted that from UT because it was too complicated to access their updated state. def get_updated_state(ctx: TrackerGameContext) -> CollectionState: - if ctx.player_id is None or ctx.multiworld is None: - logger.error("Player YAML not installed or Generator failed") - ctx.log_to_tab("Check Player YAMLs for error", False) - ctx.tracker_failed = True - raise ValueError("Player YAML not installed or Generator failed") - - state = CollectionState(ctx.multiworld) - state.sweep_for_advancements( - locations=(location for location in ctx.multiworld.get_locations() if (not location.address))) - prog_items = Counter() - all_items = Counter() - - item_id_to_name = ctx.multiworld.worlds[ctx.player_id].item_id_to_name - for item_name in [item_id_to_name[item[0]] for item in ctx.items_received] + ctx.manual_items: - try: - world_item = ctx.multiworld.create_item(item_name, ctx.player_id) - state.collect(world_item, True) - if world_item.classification == ItemClassification.progression or world_item.classification == ItemClassification.progression_skip_balancing: - prog_items[world_item.name] += 1 - if world_item.code is not None: - all_items[world_item.name] += 1 - except: - ctx.log_to_tab("Item id " + str(item_name) + " not able to be created", False) - state.sweep_for_advancements( - locations=(location for location in ctx.multiworld.get_locations() if (not location.address))) - - return state + return updateTracker(ctx)[3] async def main(args):