diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 44306011..01ca5653 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -206,25 +206,10 @@ class StardewValleyWorld(World): self.multiworld.push_precollected(self.create_starting_item("Progressive Coop")) def setup_player_events(self): - self.setup_construction_events() - self.setup_quest_events() self.setup_action_events() self.setup_logic_events() - def setup_construction_events(self): - can_construct_buildings = LocationData(None, RegionName.carpenter, Event.can_construct_buildings) - self.create_event_location(can_construct_buildings, True_(), Event.can_construct_buildings) - - def setup_quest_events(self): - start_dark_talisman_quest = LocationData(None, RegionName.railroad, Event.start_dark_talisman_quest) - self.create_event_location(start_dark_talisman_quest, self.logic.wallet.has_rusty_key(), Event.start_dark_talisman_quest) - def setup_action_events(self): - can_ship_event = LocationData(None, LogicRegion.shipping, Event.can_ship_items) - self.create_event_location(can_ship_event, true_, Event.can_ship_items) - can_shop_pierre_event = LocationData(None, RegionName.pierre_store, Event.can_shop_at_pierre) - self.create_event_location(can_shop_pierre_event, true_, Event.can_shop_at_pierre) - spring_farming = LocationData(None, LogicRegion.spring_farming, Event.spring_farming) self.create_event_location(spring_farming, true_, Event.spring_farming) summer_farming = LocationData(None, LogicRegion.summer_farming, Event.summer_farming) diff --git a/worlds/stardew_valley/logic/building_logic.py b/worlds/stardew_valley/logic/building_logic.py index 4611eba3..b4eff439 100644 --- a/worlds/stardew_valley/logic/building_logic.py +++ b/worlds/stardew_valley/logic/building_logic.py @@ -1,3 +1,4 @@ +from functools import cached_property from typing import Dict, Union from Utils import cache_self1 @@ -8,12 +9,12 @@ from .received_logic import ReceivedLogicMixin from .region_logic import RegionLogicMixin from ..options import BuildingProgression from ..stardew_rule import StardewRule, True_, False_, Has -from ..strings.ap_names.event_names import Event from ..strings.artisan_good_names import ArtisanGood from ..strings.building_names import Building from ..strings.fish_names import WaterItem from ..strings.material_names import Material from ..strings.metal_names import MetalBar +from ..strings.region_names import Region has_group = "building" @@ -60,7 +61,7 @@ class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionL return True_() return self.logic.received(building) - carpenter_rule = self.logic.received(Event.can_construct_buildings) + carpenter_rule = self.logic.building.can_construct_buildings if not self.options.building_progression & BuildingProgression.option_progressive: return Has(building, self.registry.building_rules, has_group) & carpenter_rule @@ -75,6 +76,10 @@ class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionL building = " ".join(["Progressive", *building.split(" ")[1:]]) return self.logic.received(building, count) & carpenter_rule + @cached_property + def can_construct_buildings(self) -> StardewRule: + return self.logic.region.can_reach(Region.carpenter) + @cache_self1 def has_house(self, upgrade_level: int) -> StardewRule: if upgrade_level < 1: @@ -83,7 +88,7 @@ class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionL if upgrade_level > 3: return False_() - carpenter_rule = self.logic.received(Event.can_construct_buildings) + carpenter_rule = self.logic.building.can_construct_buildings if self.options.building_progression & BuildingProgression.option_progressive: return carpenter_rule & self.logic.received(f"Progressive House", upgrade_level) diff --git a/worlds/stardew_valley/logic/money_logic.py b/worlds/stardew_valley/logic/money_logic.py index 73c5291a..85370273 100644 --- a/worlds/stardew_valley/logic/money_logic.py +++ b/worlds/stardew_valley/logic/money_logic.py @@ -1,3 +1,4 @@ +import typing from typing import Union from Utils import cache_self1 @@ -11,10 +12,14 @@ from .time_logic import TimeLogicMixin from ..data.shop import ShopSource from ..options import SpecialOrderLocations from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_, true_ -from ..strings.ap_names.event_names import Event from ..strings.currency_names import Currency from ..strings.region_names import Region, LogicRegion +if typing.TYPE_CHECKING: + from .shipping_logic import ShippingLogicMixin + + assert ShippingLogicMixin + qi_gem_rewards = ("100 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems", "25 Qi Gems", "20 Qi Gems", "15 Qi Gems", "10 Qi Gems") @@ -26,7 +31,7 @@ class MoneyLogicMixin(BaseLogicMixin): class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SeasonLogicMixin, -GrindLogicMixin]]): +GrindLogicMixin, 'ShippingLogicMixin']]): @cache_self1 def can_have_earned_total(self, amount: int) -> StardewRule: @@ -37,7 +42,7 @@ GrindLogicMixin]]): willy_rule = self.logic.region.can_reach_all((Region.fish_shop, LogicRegion.fishing)) clint_rule = self.logic.region.can_reach_all((Region.blacksmith, Region.mines_floor_5)) robin_rule = self.logic.region.can_reach_all((Region.carpenter, Region.secret_woods)) - shipping_rule = self.logic.received(Event.can_ship_items) + shipping_rule = self.logic.shipping.can_use_shipping_bin if amount < 2000: selling_any_rule = pierre_rule | willy_rule | clint_rule | robin_rule | shipping_rule @@ -50,7 +55,7 @@ GrindLogicMixin]]): if amount < 10000: return shipping_rule - seed_rules = self.logic.received(Event.can_shop_at_pierre) + seed_rules = self.logic.region.can_reach(Region.pierre_store) if amount < 40000: return shipping_rule & seed_rules diff --git a/worlds/stardew_valley/logic/shipping_logic.py b/worlds/stardew_valley/logic/shipping_logic.py index 8d545e21..e9f22581 100644 --- a/worlds/stardew_valley/logic/shipping_logic.py +++ b/worlds/stardew_valley/logic/shipping_logic.py @@ -11,7 +11,6 @@ from ..locations import LocationTags, locations_by_tag from ..options import ExcludeGingerIsland, Shipsanity from ..options import SpecialOrderLocations from ..stardew_rule import StardewRule -from ..strings.ap_names.event_names import Event from ..strings.building_names import Building @@ -29,7 +28,7 @@ class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, Buil @cache_self1 def can_ship(self, item: str) -> StardewRule: - return self.logic.received(Event.can_ship_items) & self.logic.has(item) + return self.logic.shipping.can_use_shipping_bin & self.logic.has(item) def can_ship_everything(self) -> StardewRule: shipsanity_prefix = "Shipsanity: " @@ -49,7 +48,7 @@ class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, Buil def can_ship_everything_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule: if self.options.shipsanity == Shipsanity.option_none: - return self.can_ship_everything() + return self.logic.shipping.can_ship_everything() rules = [self.logic.building.has_building(Building.shipping_bin)] diff --git a/worlds/stardew_valley/logic/special_order_logic.py b/worlds/stardew_valley/logic/special_order_logic.py index 65497df4..8bcd78d7 100644 --- a/worlds/stardew_valley/logic/special_order_logic.py +++ b/worlds/stardew_valley/logic/special_order_logic.py @@ -21,7 +21,6 @@ from ..content.vanilla.ginger_island import ginger_island_content_pack from ..content.vanilla.qi_board import qi_board_content_pack from ..stardew_rule import StardewRule, Has, false_ from ..strings.animal_product_names import AnimalProduct -from ..strings.ap_names.event_names import Event from ..strings.ap_names.transport_names import Transportation from ..strings.artisan_good_names import ArtisanGood from ..strings.crop_names import Vegetable, Fruit @@ -61,7 +60,7 @@ AbilityLogicMixin, SpecialOrderLogicMixin, MonsterLogicMixin]]): SpecialOrder.gifts_for_george: self.logic.season.has(Season.spring) & self.logic.has(Forageable.leek), SpecialOrder.fragments_of_the_past: self.logic.monster.can_kill(Monster.skeleton), SpecialOrder.gus_famous_omelet: self.logic.has(AnimalProduct.any_egg), - SpecialOrder.crop_order: self.logic.ability.can_farm_perfectly() & self.logic.received(Event.can_ship_items), + SpecialOrder.crop_order: self.logic.ability.can_farm_perfectly() & self.logic.shipping.can_use_shipping_bin, SpecialOrder.community_cleanup: self.logic.skill.can_crab_pot, SpecialOrder.the_strong_stuff: self.logic.has(ArtisanGood.specific_juice(Vegetable.potato)), SpecialOrder.pierres_prime_produce: self.logic.ability.can_farm_perfectly(), @@ -94,12 +93,12 @@ AbilityLogicMixin, SpecialOrderLogicMixin, MonsterLogicMixin]]): self.update_rules({ SpecialOrder.qis_crop: self.logic.ability.can_farm_perfectly() & self.logic.region.can_reach(Region.greenhouse) & self.logic.region.can_reach(Region.island_west) & self.logic.skill.has_total_level(50) & - self.logic.has(Machine.seed_maker) & self.logic.received(Event.can_ship_items), + self.logic.has(Machine.seed_maker) & self.logic.shipping.can_use_shipping_bin, SpecialOrder.lets_play_a_game: self.logic.arcade.has_junimo_kart_max_level(), SpecialOrder.four_precious_stones: self.logic.time.has_lived_max_months & self.logic.has("Prismatic Shard") & self.logic.ability.can_mine_perfectly_in_the_skull_cavern(), SpecialOrder.qis_hungry_challenge: self.logic.ability.can_mine_perfectly_in_the_skull_cavern(), - SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.received(Event.can_ship_items) & + SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.shipping.can_use_shipping_bin & (self.logic.money.can_spend_at(Region.saloon, 205000) | self.logic.money.can_spend_at(Region.pierre_store, 170000)), SpecialOrder.qis_kindness: self.logic.relationship.can_give_loved_gifts_to_everyone(), SpecialOrder.extended_family: self.logic.ability.can_fish_perfectly() & self.logic.has(Fish.angler) & self.logic.has(Fish.glacierfish) & diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index eda2d437..e7107e89 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -27,7 +27,6 @@ from .stardew_rule.indirect_connection import look_for_indirect_connection from .stardew_rule.rule_explain import explain from .strings.ap_names.ap_option_names import OptionName from .strings.ap_names.community_upgrade_names import CommunityUpgrade -from .strings.ap_names.event_names import Event from .strings.ap_names.mods.mod_items import SVEQuestItem, SVERunes from .strings.ap_names.transport_names import Transportation from .strings.artisan_good_names import ArtisanGood @@ -251,7 +250,8 @@ def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: S set_entrance_rule(multiworld, player, Entrance.enter_witch_warp_cave, logic.quest.has_dark_talisman() | (logic.mod.magic.can_blink())) set_entrance_rule(multiworld, player, Entrance.enter_witch_hut, (logic.has(ArtisanGood.void_mayonnaise) | logic.mod.magic.can_blink())) set_entrance_rule(multiworld, player, Entrance.enter_mutant_bug_lair, - (logic.received(Event.start_dark_talisman_quest) & logic.relationship.can_meet(NPC.krobus)) | logic.mod.magic.can_blink()) + (logic.wallet.has_rusty_key() & logic.region.can_reach(Region.railroad) & logic.relationship.can_meet( + NPC.krobus)) | logic.mod.magic.can_blink()) set_entrance_rule(multiworld, player, Entrance.enter_casino, logic.quest.has_club_card()) set_bedroom_entrance_rules(logic, multiworld, player, world_options) @@ -307,8 +307,7 @@ def set_mines_floor_entrance_rules(logic, multiworld, player): rule = logic.mine.has_mine_elevator_to_floor(floor - 10) if floor == 5 or floor == 45 or floor == 85: rule = rule & logic.mine.can_progress_in_the_mines_from_floor(floor) - entrance = multiworld.get_entrance(dig_to_mines_floor(floor), player) - MultiWorldRules.set_rule(entrance, rule) + set_entrance_rule(multiworld, player, dig_to_mines_floor(floor), rule) def set_skull_cavern_floor_entrance_rules(logic, multiworld, player): @@ -316,8 +315,7 @@ def set_skull_cavern_floor_entrance_rules(logic, multiworld, player): rule = logic.mod.elevator.has_skull_cavern_elevator_to_floor(floor - 25) if floor == 25 or floor == 75 or floor == 125: rule = rule & logic.mine.can_progress_in_the_skull_cavern_from_floor(floor) - entrance = multiworld.get_entrance(dig_to_skull_floor(floor), player) - MultiWorldRules.set_rule(entrance, rule) + set_entrance_rule(multiworld, player, dig_to_skull_floor(floor), rule) def set_blacksmith_entrance_rules(logic, multiworld, player): @@ -346,9 +344,8 @@ def set_skill_entrance_rules(logic, multiworld, player, world_options: StardewVa def set_blacksmith_upgrade_rule(logic, multiworld, player, entrance_name: str, item_name: str, tool_material: str): - material_entrance = multiworld.get_entrance(entrance_name, player) upgrade_rule = logic.has(item_name) & logic.money.can_spend(tool_upgrade_prices[tool_material]) - MultiWorldRules.set_rule(material_entrance, upgrade_rule) + set_entrance_rule(multiworld, player, entrance_name, upgrade_rule) def set_festival_entrance_rules(logic, multiworld, player): @@ -880,25 +877,19 @@ def set_traveling_merchant_day_rules(logic: StardewLogic, multiworld: MultiWorld def set_arcade_machine_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_junimo_kart, player), - logic.received(Wallet.skull_key)) + play_junimo_kart_rule = logic.received(Wallet.skull_key) + if world_options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling: + set_entrance_rule(multiworld, player, Entrance.play_junimo_kart, play_junimo_kart_rule) return - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_junimo_kart, player), - logic.has("Junimo Kart Small Buff")) - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_2, player), - logic.has("Junimo Kart Medium Buff")) - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_3, player), - logic.has("Junimo Kart Big Buff")) - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_junimo_kart_4, player), - logic.has("Junimo Kart Max Buff")) - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.play_journey_of_the_prairie_king, player), - logic.has("JotPK Small Buff")) - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_jotpk_world_2, player), - logic.has("JotPK Medium Buff")) - MultiWorldRules.add_rule(multiworld.get_entrance(Entrance.reach_jotpk_world_3, player), - logic.has("JotPK Big Buff")) + set_entrance_rule(multiworld, player, Entrance.play_junimo_kart, play_junimo_kart_rule & logic.has("Junimo Kart Small Buff")) + set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_2, logic.has("Junimo Kart Medium Buff")) + set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_3, logic.has("Junimo Kart Big Buff")) + set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_4, logic.has("Junimo Kart Max Buff")) + set_entrance_rule(multiworld, player, Entrance.play_journey_of_the_prairie_king, logic.has("JotPK Small Buff")) + set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_2, logic.has("JotPK Medium Buff")) + set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_3, logic.has("JotPK Big Buff")) MultiWorldRules.add_rule(multiworld.get_location("Journey of the Prairie King Victory", player), logic.has("JotPK Max Buff")) @@ -1049,6 +1040,7 @@ def set_entrance_rule(multiworld, player, entrance: str, rule: StardewRule): potentially_required_regions = look_for_indirect_connection(rule) if potentially_required_regions: for region in potentially_required_regions: + logger.debug(f"Registering indirect condition for {region} -> {entrance}") multiworld.register_indirect_condition(multiworld.get_region(region, player), multiworld.get_entrance(entrance, player)) MultiWorldRules.set_rule(multiworld.get_entrance(entrance, player), rule) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index a9767c7b..2e2b9c95 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from functools import cached_property, singledispatch from typing import Iterable, Set, Tuple, List, Optional -from BaseClasses import CollectionState +from BaseClasses import CollectionState, Location, Entrance from worlds.generic.Rules import CollectionRule from . import StardewRule, AggregatingStardewRule, Count, Has, TotalReceived, Received, Reach, true_ @@ -12,10 +12,10 @@ from . import StardewRule, AggregatingStardewRule, Count, Has, TotalReceived, Re @dataclass class RuleExplanation: rule: StardewRule - state: CollectionState + state: CollectionState = field(repr=False, hash=False) expected: bool sub_rules: Iterable[StardewRule] = field(default_factory=list) - explored_rules_key: Set[Tuple[str, str]] = field(default_factory=set) + explored_rules_key: Set[Tuple[str, str]] = field(default_factory=set, repr=False, hash=False) current_rule_explored: bool = False def __post_init__(self): @@ -38,13 +38,6 @@ class RuleExplanation: 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 __repr__(self, depth=0): - if not self.sub_rules: - return self.summary(depth) - - return self.summary(depth) + "\n" + "\n".join(i.__repr__(depth + 1) - for i in sorted(self.explained_sub_rules, key=lambda x: x.result)) - @cached_property def result(self) -> bool: try: @@ -134,6 +127,10 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T access_rules = [Reach(spot.parent_region.name, "Region", rule.player)] else: access_rules = [spot.access_rule, Reach(spot.parent_region.name, "Region", rule.player)] + elif spot.access_rule == Location.access_rule: + # Sometime locations just don't have an access rule and all the relevant logic is in the parent region. + access_rules = [Reach(spot.parent_region.name, "Region", rule.player)] + elif rule.resolution_hint == 'Entrance': spot = state.multiworld.get_entrance(rule.spot, rule.player) @@ -143,6 +140,9 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T access_rules = [Reach(spot.parent_region.name, "Region", rule.player)] else: access_rules = [spot.access_rule, Reach(spot.parent_region.name, "Region", rule.player)] + elif spot.access_rule == Entrance.access_rule: + # Sometime entrances just don't have an access rule and all the relevant logic is in the parent region. + access_rules = [Reach(spot.parent_region.name, "Region", rule.player)] else: spot = state.multiworld.get_region(rule.spot, rule.player) diff --git a/worlds/stardew_valley/strings/ap_names/event_names.py b/worlds/stardew_valley/strings/ap_names/event_names.py index 88f9715a..449bb672 100644 --- a/worlds/stardew_valley/strings/ap_names/event_names.py +++ b/worlds/stardew_valley/strings/ap_names/event_names.py @@ -8,10 +8,6 @@ def event(name: str): class Event: victory = event("Victory") - can_construct_buildings = event("Can Construct Buildings") - start_dark_talisman_quest = event("Start Dark Talisman Quest") - can_ship_items = event("Can Ship Items") - can_shop_at_pierre = event("Can Shop At Pierre's") spring_farming = event("Spring Farming") summer_farming = event("Summer Farming") fall_farming = event("Fall Farming") diff --git a/worlds/stardew_valley/test/rules/TestBuildings.py b/worlds/stardew_valley/test/rules/TestBuildings.py index 2c276d8b..cacd6ea3 100644 --- a/worlds/stardew_valley/test/rules/TestBuildings.py +++ b/worlds/stardew_valley/test/rules/TestBuildings.py @@ -23,10 +23,6 @@ class TestBuildingLogic(SVTestBase): self.assertFalse(big_coop_blueprint_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True) - self.assertFalse(big_coop_blueprint_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=False) self.assertTrue(big_coop_blueprint_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") @@ -35,7 +31,6 @@ class TestBuildingLogic(SVTestBase): self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) self.collect_lots_of_money() - self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True) self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) self.multiworld.state.collect(self.create_item("Progressive Coop"), prevent_sweep=True) @@ -53,10 +48,6 @@ class TestBuildingLogic(SVTestBase): self.assertFalse(big_shed_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Can Construct Buildings"), prevent_sweep=True) - self.assertFalse(big_shed_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") - self.multiworld.state.collect(self.create_item("Progressive Shed"), prevent_sweep=True) self.assertTrue(big_shed_rule(self.multiworld.state), f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}")