Skip to content

Commit

Permalink
Stardew Valley: Enable extended UT Tracker and add explain commands
Browse files Browse the repository at this point in the history
  • Loading branch information
Jouramie committed Dec 15, 2024
1 parent 8bdeeac commit a264093
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 46 deletions.
28 changes: 16 additions & 12 deletions worlds/stardew_valley/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,7 +35,7 @@
UNIVERSAL_TRACKER_SEED_PROPERTY = "ut_seed"

client_version = 0
TRACKER_ENABLED = False
TRACKER_ENABLED = True


class StardewLocation(Location):
Expand Down Expand Up @@ -65,19 +64,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.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")

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'] = f"ap:{__name__}/stardew.png"


class StardewValleyWorld(World):
Expand Down
154 changes: 145 additions & 9 deletions worlds/stardew_valley/client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
from __future__ import annotations

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
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
from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor # noqa

tracker_loaded = True
except ImportError:
Expand All @@ -12,6 +20,8 @@

class TrackerGameContextMixin:
"""Expecting the TrackerGameContext to have these methods."""
multiworld: MultiWorld
player_id: int

def build_gui(self, manager):
...
Expand All @@ -23,23 +33,113 @@ def load_kv(self):
...


class BaseContext(CommonContext, TrackerGameContextMixin):
class TrackerGameContext(CommonContext, TrackerGameContextMixin):
pass


tracker_loaded = False


class StardewCommandProcessor(ClientCommandProcessor):

def _cmd_explain(self):
"""Coming soon.™"""
logger.info("Coming soon.™")


class StardewClientContext(BaseContext):
ctx: StardewClientContext

@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

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

self.ctx.previous_explanation = expl
logger.info(str(expl).strip())

@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

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

self.ctx.previous_explanation = expl
logger.info(str(expl).strip())

@mark_raw
def _cmd_explain_missing(self, location: str = ""):
"""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

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

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.previous_explanation is None:
logger.warning("No previous explanation found.")
return

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

self.ctx.previous_explanation = expl

logger.info(str(expl).strip())

if not tracker_loaded:
del _cmd_explain
del _cmd_explain_missing


class StardewClientContext(TrackerGameContext):
game = "Stardew Valley"
command_processor = StardewCommandProcessor
logic: StardewLogic | None = None
previous_explanation: RuleExplanation | None = None

def run_gui(self):
from kvui import GameManager
Expand All @@ -66,6 +166,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[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)
Expand All @@ -83,6 +187,7 @@ async def main():

if tracker_loaded:
ctx.run_generator()
ctx.setup_logic()
else:
logger.warning("Could not find Universal Tracker.")

Expand All @@ -97,3 +202,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: 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
File renamed without changes.
File renamed without changes
Loading

0 comments on commit a264093

Please sign in to comment.