From 77bad39ce4d0e11f16a361ebda0760b41eb1c8cb Mon Sep 17 00:00:00 2001 From: Jouramie Date: Sat, 23 Nov 2024 13:54:11 -0500 Subject: [PATCH 1/6] count received progression in an event directly in collect instead of recounting everytime rules are evaluated # Conflicts: # worlds/stardew_valley/__init__.py # worlds/stardew_valley/stardew_rule/state.py # worlds/stardew_valley/test/rules/TestShipping.py --- worlds/stardew_valley/__init__.py | 26 ++++++++++++++----- worlds/stardew_valley/stardew_rule/state.py | 12 +-------- .../strings/ap_names/event_names.py | 1 + .../stardew_valley/test/rules/TestShipping.py | 11 +++++--- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index f9df8c292e37..a2b80af22f02 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -441,15 +441,29 @@ def fill_slot_data(self) -> Dict[str, Any]: def collect(self, state: CollectionState, item: StardewItem) -> bool: change = super().collect(state, item) - if change: - state.prog_items[self.player][Event.received_walnuts] += self.get_walnut_amount(item.name) - return change + if not change: + return False + + state.prog_items[self.player][Event.received_progression] += 1 + + walnut_amount = self.get_walnut_amount(item.name) + if walnut_amount: + state.prog_items[self.player][Event.received_walnuts] += walnut_amount + + return True def remove(self, state: CollectionState, item: StardewItem) -> bool: change = super().remove(state, item) - if change: - state.prog_items[self.player][Event.received_walnuts] -= self.get_walnut_amount(item.name) - return change + if not change: + return False + + state.prog_items[self.player][Event.received_progression] -= 1 + + walnut_amount = self.get_walnut_amount(item.name) + if walnut_amount: + state.prog_items[self.player][Event.received_walnuts] -= walnut_amount + + return True @staticmethod def get_walnut_amount(item_name: str) -> int: diff --git a/worlds/stardew_valley/stardew_rule/state.py b/worlds/stardew_valley/stardew_rule/state.py index 5f5e61b3d4e5..f5c7c146263c 100644 --- a/worlds/stardew_valley/stardew_rule/state.py +++ b/worlds/stardew_valley/stardew_rule/state.py @@ -105,18 +105,8 @@ def __call__(self, state: CollectionState) -> bool: stardew_world = state.multiworld.worlds[self.player] total_count = stardew_world.total_progression_items needed_count = (total_count * self.percent) // 100 - player_state = state.prog_items[self.player] - if needed_count <= len(player_state): - return True - - total_count = 0 - for item, item_count in player_state.items(): - total_count += item_count - if total_count >= needed_count: - return True - - return False + return state.has("Received Progression Item", self.player, needed_count) def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]: return self, self(state) diff --git a/worlds/stardew_valley/strings/ap_names/event_names.py b/worlds/stardew_valley/strings/ap_names/event_names.py index 88f9715abc65..61d49e7093ce 100644 --- a/worlds/stardew_valley/strings/ap_names/event_names.py +++ b/worlds/stardew_valley/strings/ap_names/event_names.py @@ -18,3 +18,4 @@ class Event: winter_farming = event("Winter Farming") received_walnuts = event("Received Walnuts") + received_progression = event("Received Progression Item") diff --git a/worlds/stardew_valley/test/rules/TestShipping.py b/worlds/stardew_valley/test/rules/TestShipping.py index 973d8d3ada7d..125b7f31d0d9 100644 --- a/worlds/stardew_valley/test/rules/TestShipping.py +++ b/worlds/stardew_valley/test/rules/TestShipping.py @@ -69,14 +69,17 @@ class TestShipsanityEverything(SVTestBase): def test_all_shipsanity_locations_require_shipping_bin(self): bin_name = "Shipping Bin" self.collect_all_except(bin_name) - shipsanity_locations = [location for location in self.get_real_locations() if - LocationTags.SHIPSANITY in location_table[location.name].tags] + shipsanity_locations = [location + for location in self.get_real_locations() + if LocationTags.SHIPSANITY in location_table[location.name].tags] bin_item = self.create_item(bin_name) + for location in shipsanity_locations: with self.subTest(location.name): - self.remove(bin_item) self.assertFalse(self.world.logic.region.can_reach_location(location.name)(self.multiworld.state)) - self.multiworld.state.collect(bin_item, prevent_sweep=False) + + self.collect(bin_item) shipsanity_rule = self.world.logic.region.can_reach_location(location.name) self.assert_rule_true(shipsanity_rule, self.multiworld.state) + self.remove(bin_item) From 1ee43324a43d563fc0f04c3894b8055b7eafe38c Mon Sep 17 00:00:00 2001 From: Jouramie Date: Sat, 23 Nov 2024 14:39:18 -0500 Subject: [PATCH 2/6] calculate percent directly in collect --- worlds/stardew_valley/__init__.py | 22 ++++++++++--- worlds/stardew_valley/stardew_rule/state.py | 32 ++++--------------- .../strings/ap_names/event_names.py | 3 +- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index a2b80af22f02..292ee69fdf9a 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -444,11 +444,18 @@ def collect(self, state: CollectionState, item: StardewItem) -> bool: if not change: return False - state.prog_items[self.player][Event.received_progression] += 1 + player_state = state.prog_items[self.player] + + received_progression_count = player_state[Event.received_progression_item] + received_progression_count += 1 + if self.total_progression_items: + # We can't update the percentage if we don't know the total progression items, can't divide by 0. + player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items + player_state[Event.received_progression_item] = received_progression_count walnut_amount = self.get_walnut_amount(item.name) if walnut_amount: - state.prog_items[self.player][Event.received_walnuts] += walnut_amount + player_state[Event.received_walnuts] += walnut_amount return True @@ -457,11 +464,18 @@ def remove(self, state: CollectionState, item: StardewItem) -> bool: if not change: return False - state.prog_items[self.player][Event.received_progression] -= 1 + player_state = state.prog_items[self.player] + + received_progression_count = player_state[Event.received_progression_item] + received_progression_count -= 1 + if self.total_progression_items: + # We can't update the percentage if we don't know the total progression items, can't divide by 0. + player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items + player_state[Event.received_progression_item] = received_progression_count walnut_amount = self.get_walnut_amount(item.name) if walnut_amount: - state.prog_items[self.player][Event.received_walnuts] -= walnut_amount + player_state[Event.received_walnuts] -= walnut_amount return True diff --git a/worlds/stardew_valley/stardew_rule/state.py b/worlds/stardew_valley/stardew_rule/state.py index f5c7c146263c..a5d936d57db8 100644 --- a/worlds/stardew_valley/stardew_rule/state.py +++ b/worlds/stardew_valley/stardew_rule/state.py @@ -4,6 +4,7 @@ from BaseClasses import CollectionState from .base import BaseStardewRule, CombinableStardewRule from .protocol import StardewRule +from ..strings.ap_names.event_names import Event class TotalReceived(BaseStardewRule): @@ -84,32 +85,13 @@ def __repr__(self): return f"Reach {self.resolution_hint} {self.spot}" -@dataclass(frozen=True) -class HasProgressionPercent(CombinableStardewRule): - player: int - percent: int +class HasProgressionPercent(Received): + def __init__(self, player: int, percent: int): + super().__init__(Event.received_progression_percent, player, percent, event=True) def __post_init__(self): - assert self.percent > 0, "HasProgressionPercent rule must be above 0%" - assert self.percent <= 100, "HasProgressionPercent rule can't require more than 100% of items" - - @property - def combination_key(self) -> Hashable: - return HasProgressionPercent.__name__ - - @property - def value(self): - return self.percent - - def __call__(self, state: CollectionState) -> bool: - stardew_world = state.multiworld.worlds[self.player] - total_count = stardew_world.total_progression_items - needed_count = (total_count * self.percent) // 100 - - return state.has("Received Progression Item", self.player, needed_count) - - def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]: - return self, self(state) + assert self.count > 0, "HasProgressionPercent rule must be above 0%" + assert self.count <= 100, "HasProgressionPercent rule can't require more than 100% of items" def __repr__(self): - return f"Received {self.percent}% progression items" + return f"Received {self.count}% progression items" diff --git a/worlds/stardew_valley/strings/ap_names/event_names.py b/worlds/stardew_valley/strings/ap_names/event_names.py index 61d49e7093ce..28d9a76d54ad 100644 --- a/worlds/stardew_valley/strings/ap_names/event_names.py +++ b/worlds/stardew_valley/strings/ap_names/event_names.py @@ -18,4 +18,5 @@ class Event: winter_farming = event("Winter Farming") received_walnuts = event("Received Walnuts") - received_progression = event("Received Progression Item") + received_progression_item = event("Received Progression Item") + received_progression_percent = event("Received Progression Percent") From a1d72f6546207668a43b7c6efd3ae3ce2980b836 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Sat, 23 Nov 2024 17:31:20 -0500 Subject: [PATCH 3/6] remove weird bytes from stability tests here as well --- worlds/stardew_valley/test/stability/TestStability.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/worlds/stardew_valley/test/stability/TestStability.py b/worlds/stardew_valley/test/stability/TestStability.py index 8bb904a56ea2..2beb3102cc02 100644 --- a/worlds/stardew_valley/test/stability/TestStability.py +++ b/worlds/stardew_valley/test/stability/TestStability.py @@ -7,9 +7,6 @@ from BaseClasses import get_seed from .. import SVTestCase -# There seems to be 4 bytes that appear at random at the end of the output, breaking the json... I don't know where they came from. -BYTES_TO_REMOVE = 4 - # at 0x102ca98a0> lambda_regex = re.compile(r"^ at (.*)>$") # Python 3.10.2\r\n @@ -29,8 +26,8 @@ def test_all_locations_and_items_are_the_same_between_two_generations(self): output_a = subprocess.check_output([sys.executable, '-m', 'worlds.stardew_valley.test.stability.StabilityOutputScript', '--seed', str(seed)]) output_b = subprocess.check_output([sys.executable, '-m', 'worlds.stardew_valley.test.stability.StabilityOutputScript', '--seed', str(seed)]) - result_a = json.loads(output_a[:-BYTES_TO_REMOVE]) - result_b = json.loads(output_b[:-BYTES_TO_REMOVE]) + result_a = json.loads(output_a) + result_b = json.loads(output_b) for i, ((room_a, bundles_a), (room_b, bundles_b)) in enumerate(zip(result_a["bundles"].items(), result_b["bundles"].items())): self.assertEqual(room_a, room_b, f"Bundle rooms at index {i} is different between both executions. Seed={seed}") From 5f8cac2aa9dd7895d046e3d89588f6069fea9e77 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Fri, 29 Nov 2024 20:19:48 -0500 Subject: [PATCH 4/6] remove create_starting_item because it now does the exact same thing as create_item --- worlds/stardew_valley/__init__.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index f35804707a1a..92791dec4e81 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,6 +1,6 @@ import logging from random import Random -from typing import Dict, Any, Iterable, Optional, Union, List, TextIO +from typing import Dict, Any, Iterable, Optional, List, TextIO from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions, Accessibility @@ -93,7 +93,6 @@ class StardewValleyWorld(World): randomized_entrances: Dict[str, str] total_progression_items: int - excluded_from_total_progression_items: List[str] = [Event.received_walnuts] def __init__(self, multiworld: MultiWorld, player: int): super().__init__(multiworld, player) @@ -208,7 +207,7 @@ def precollect_starting_season(self): if self.options.season_randomization == SeasonRandomization.option_disabled: for season in season_pool: - self.multiworld.push_precollected(self.create_starting_item(season)) + self.multiworld.push_precollected(self.create_item(season)) return if [item for item in self.multiworld.precollected_items[self.player] @@ -218,12 +217,13 @@ def precollect_starting_season(self): if self.options.season_randomization == SeasonRandomization.option_randomized_not_winter: season_pool = [season for season in season_pool if season.name != "Winter"] - starting_season = self.create_starting_item(self.random.choice(season_pool)) + item1 = self.random.choice(season_pool) + starting_season = self.create_item(item1) self.multiworld.push_precollected(starting_season) def precollect_farm_type_items(self): if self.options.farm_type == FarmType.option_meadowlands and self.options.building_progression & BuildingProgression.option_progressive: - self.multiworld.push_precollected(self.create_starting_item("Progressive Coop")) + self.multiworld.push_precollected(self.create_item("Progressive Coop")) def setup_player_events(self): self.setup_action_events() @@ -317,7 +317,7 @@ def setup_victory(self): def get_all_location_names(self) -> List[str]: return list(location.name for location in self.multiworld.get_locations(self.player)) - def create_item(self, item: Union[str, ItemData], override_classification: ItemClassification = None) -> StardewItem: + def create_item(self, item: str | ItemData, override_classification: ItemClassification = None) -> StardewItem: if isinstance(item, str): item = item_table[item] @@ -326,12 +326,6 @@ def create_item(self, item: Union[str, ItemData], override_classification: ItemC return StardewItem(item.name, override_classification, item.code, self.player) - def create_starting_item(self, item: Union[str, ItemData]) -> StardewItem: - if isinstance(item, str): - item = item_table[item] - - return StardewItem(item.name, item.classification, item.code, self.player) - def create_event_location(self, location_data: LocationData, rule: StardewRule = None, item: Optional[str] = None): if rule is None: rule = True_() From 2f62c57d158e2d57159b11f77b37d763a66544e4 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Sun, 8 Dec 2024 11:21:11 -0500 Subject: [PATCH 5/6] add comment explaining when total progression items is not set --- worlds/stardew_valley/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index ec6783bb77fa..963a84e32aa8 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -438,6 +438,7 @@ def collect(self, state: CollectionState, item: StardewItem) -> bool: received_progression_count = player_state[Event.received_progression_item] received_progression_count += 1 if self.total_progression_items: + # Total progression items is not set until all items are created, but collect will be called during the item creation when an item is precollected. # We can't update the percentage if we don't know the total progression items, can't divide by 0. player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items player_state[Event.received_progression_item] = received_progression_count From 8e55c79e73e261f923e1099490728cf488fdb871 Mon Sep 17 00:00:00 2001 From: Jouramie Date: Sun, 8 Dec 2024 11:30:01 -0500 Subject: [PATCH 6/6] th happened here --- worlds/stardew_valley/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 963a84e32aa8..69cf6e2dcd53 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -217,8 +217,7 @@ def precollect_starting_season(self): if self.options.season_randomization == SeasonRandomization.option_randomized_not_winter: season_pool = [season for season in season_pool if season.name != "Winter"] - item1 = self.random.choice(season_pool) - starting_season = self.create_item(item1) + starting_season = self.create_item(self.random.choice(season_pool)) self.multiworld.push_precollected(starting_season) def precollect_farm_type_items(self):