diff --git a/worlds/stardew_valley/data/museum_data.py b/worlds/stardew_valley/data/museum_data.py index eb42a17dd5db..b786f5b2d00c 100644 --- a/worlds/stardew_valley/data/museum_data.py +++ b/worlds/stardew_valley/data/museum_data.py @@ -43,8 +43,8 @@ def __repr__(self): unlikely = () -all_artifact_items: List[MuseumItem] = [] -all_mineral_items: List[MuseumItem] = [] +all_museum_artifacts: List[MuseumItem] = [] +all_museum_minerals: List[MuseumItem] = [] all_museum_items: List[MuseumItem] = [] @@ -56,7 +56,7 @@ def create_artifact(name: str, geodes: Union[str, Tuple[str, ...]] = (), monsters: Union[str, Tuple[str, ...]] = ()) -> MuseumItem: artifact_item = MuseumItem.of(name, item_id, difficulty, locations, geodes, monsters) - all_artifact_items.append(artifact_item) + all_museum_artifacts.append(artifact_item) all_museum_items.append(artifact_item) return artifact_item @@ -79,7 +79,7 @@ def create_mineral(name: str, difficulty += 31.0 / 2750.0 * 100 mineral_item = MuseumItem.of(name, item_id, difficulty, locations, geodes, monsters) - all_mineral_items.append(mineral_item) + all_museum_minerals.append(mineral_item) all_museum_items.append(mineral_item) return mineral_item diff --git a/worlds/stardew_valley/logic.py b/worlds/stardew_valley/logic.py index d63a2008818d..3d8209710efe 100644 --- a/worlds/stardew_valley/logic.py +++ b/worlds/stardew_valley/logic.py @@ -9,7 +9,7 @@ from .data.bundle_data import BundleItem from .data.crops_data import crops_by_name from .data.fish_data import island_fish -from .data.museum_data import all_museum_items, MuseumItem, all_artifact_items, dwarf_scrolls +from .data.museum_data import all_museum_items, MuseumItem, all_museum_artifacts, dwarf_scrolls, all_museum_minerals from .data.recipe_data import all_cooking_recipes, CookingRecipe, RecipeSource, FriendshipSource, QueenOfSauceSource, \ StarterSource, ShopSource, SkillSource from .data.villagers_data import all_villagers_by_name, Villager @@ -475,8 +475,8 @@ def __post_init__(self): FestivalCheck.mermaid_pearl: self.has_season(Season.winter) & self.can_reach_region(Region.beach), FestivalCheck.cone_hat: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(2500), FestivalCheck.iridium_fireplace: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(15000), - FestivalCheck.rarecrow_7: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(5000) & self.can_find_museum_artifacts(20), - FestivalCheck.rarecrow_8: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(5000) & self.can_find_museum_items(40), + FestivalCheck.rarecrow_7: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(5000) & self.can_donate_museum_artifacts(20), + FestivalCheck.rarecrow_8: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(5000) & self.can_donate_museum_items(40), FestivalCheck.lupini_red_eagle: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(1200), FestivalCheck.lupini_portrait_mermaid: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(1200), FestivalCheck.lupini_solar_kingdom: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(1200), @@ -1213,7 +1213,7 @@ def can_finish_grandpa_evaluation(self) -> StardewRule: self.can_have_earned_total_money(1000000), # 1 000 000g second point self.has_total_skill_level(30), # Total Skills: 30 self.has_total_skill_level(50), # Total Skills: 50 - # Completing the museum not expected + self.can_complete_museum(), # Completing the museum for a point # Catching every fish not expected # Shipping every item not expected self.can_get_married() & self.has_house(2), @@ -1224,7 +1224,7 @@ def can_finish_grandpa_evaluation(self) -> StardewRule: self.can_complete_community_center(), # CC Ceremony first point self.can_complete_community_center(), # CC Ceremony second point self.received(Wallet.skull_key), # Skull Key obtained - self.has_rusty_key(), # Rusty key not expected + self.has_rusty_key(), # Rusty key obtained ] return Count(12, rules_worth_a_point) @@ -1266,9 +1266,21 @@ def has_year_three(self) -> StardewRule: def can_speak_dwarf(self) -> StardewRule: if self.options[options.Museumsanity] == options.Museumsanity.option_none: - return self.has([item.name for item in dwarf_scrolls]) + return And([self.can_donate_museum_item(item) for item in dwarf_scrolls]) return self.received("Dwarvish Translation Guide") + def can_donate_museum_item(self, item: MuseumItem) -> StardewRule: + return self.can_reach_region(Region.museum) & self.can_find_museum_item(item) + + def can_donate_museum_items(self, number: int) -> StardewRule: + return self.can_reach_region(Region.museum) & self.can_find_museum_items(number) + + def can_donate_museum_artifacts(self, number: int) -> StardewRule: + return self.can_reach_region(Region.museum) & self.can_find_museum_artifacts(number) + + def can_donate_museum_minerals(self, number: int) -> StardewRule: + return self.can_reach_region(Region.museum) & self.can_find_museum_minerals(number) + def can_find_museum_item(self, item: MuseumItem) -> StardewRule: region_rule = self.can_reach_all_regions_except_one(item.locations) geodes_rule = And([self.can_open_geode(geode) for geode in item.geodes]) @@ -1281,9 +1293,15 @@ def can_find_museum_item(self, item: MuseumItem) -> StardewRule: def can_find_museum_artifacts(self, number: int) -> StardewRule: rules = [] - for donation in all_museum_items: - if donation in all_artifact_items: - rules.append(self.can_find_museum_item(donation)) + for artifact in all_museum_artifacts: + rules.append(self.can_find_museum_item(artifact)) + + return Count(number, rules) + + def can_find_museum_minerals(self, number: int) -> StardewRule: + rules = [] + for mineral in all_museum_minerals: + rules.append(self.can_find_museum_item(mineral)) return Count(number, rules) @@ -1295,7 +1313,7 @@ def can_find_museum_items(self, number: int) -> StardewRule: return Count(number, rules) def can_complete_museum(self) -> StardewRule: - rules = [self.can_mine_perfectly()] + rules = [self.can_reach_region(Region.museum), self.can_mine_perfectly()] if self.options[options.Museumsanity] != options.Museumsanity.option_none: rules.append(self.received("Traveling Merchant Metal Detector", 4)) diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index f0d2deffa327..34ee1f807dd3 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -5,15 +5,14 @@ from worlds.generic import Rules as MultiWorldRules from . import options, locations from .bundles import Bundle -from .data.crops_data import crops_by_name from .strings.entrance_names import dig_to_mines_floor, dig_to_skull_floor, Entrance, move_to_woods_depth, \ DeepWoodsEntrance, AlecEntrance, MagicEntrance -from .data.museum_data import all_museum_items, all_mineral_items, all_artifact_items, \ +from .data.museum_data import all_museum_items, all_museum_minerals, all_museum_artifacts, \ dwarf_scrolls, skeleton_front, \ - skeleton_middle, skeleton_back, all_museum_items_by_name + skeleton_middle, skeleton_back, all_museum_items_by_name, Artifact from .strings.region_names import Region from .mods.mod_data import ModNames -from .mods.logic import magic, skills, deepwoods +from .mods.logic import magic, deepwoods from .locations import LocationTags from .logic import StardewLogic, And, tool_upgrade_prices from .options import StardewOptions @@ -23,7 +22,6 @@ from .strings.craftable_names import Craftable from .strings.material_names import Material from .strings.metal_names import MetalBar -from .strings.spells import MagicSpell from .strings.skill_names import ModSkill, Skill from .strings.tool_names import Tool, ToolMaterial from .strings.villager_names import NPC, ModNPC @@ -432,7 +430,7 @@ def set_museum_individual_donations_rules(all_location_names, logic: StardewLogi if museum_location.name in all_location_names: donation_name = museum_location.name[len(museum_prefix):] required_detectors = counter * 5 // number_donations - rule = logic.has(donation_name) & logic.received("Traveling Merchant Metal Detector", required_detectors) + rule = logic.can_donate_museum_item(all_museum_items_by_name[donation_name]) & logic.received("Traveling Merchant Metal Detector", required_detectors) MultiWorldRules.set_rule(multi_world.get_location(museum_location.name, player), rule.simplify()) counter += 1 @@ -447,31 +445,31 @@ def set_museum_milestone_rule(logic: StardewLogic, multi_world: MultiWorld, muse metal_detector = "Traveling Merchant Metal Detector" rule = None if milestone_name.endswith(donations_suffix): - rule = get_museum_item_count_rule(logic, donations_suffix, milestone_name, all_museum_items) + rule = get_museum_item_count_rule(logic, donations_suffix, milestone_name, all_museum_items, logic.can_donate_museum_items) elif milestone_name.endswith(minerals_suffix): - rule = get_museum_item_count_rule(logic, minerals_suffix, milestone_name, all_mineral_items) + rule = get_museum_item_count_rule(logic, minerals_suffix, milestone_name, all_museum_minerals, logic.can_donate_museum_minerals) elif milestone_name.endswith(artifacts_suffix): - rule = get_museum_item_count_rule(logic, artifacts_suffix, milestone_name, all_artifact_items) + rule = get_museum_item_count_rule(logic, artifacts_suffix, milestone_name, all_museum_artifacts, logic.can_donate_museum_artifacts) elif milestone_name == "Dwarf Scrolls": - rule = logic.has([item.name for item in dwarf_scrolls]) & logic.received(metal_detector, 4) + rule = And([logic.can_donate_museum_item(item) for item in dwarf_scrolls]) & logic.received(metal_detector, 4) elif milestone_name == "Skeleton Front": - rule = logic.has([item.name for item in skeleton_front]) & logic.received(metal_detector, 4) + rule = And([logic.can_donate_museum_item(item) for item in skeleton_front]) & logic.received(metal_detector, 4) elif milestone_name == "Skeleton Middle": - rule = logic.has([item.name for item in skeleton_middle]) & logic.received(metal_detector, 4) + rule = And([logic.can_donate_museum_item(item) for item in skeleton_middle]) & logic.received(metal_detector, 4) elif milestone_name == "Skeleton Back": - rule = logic.has([item.name for item in skeleton_back]) & logic.received(metal_detector, 4) + rule = And([logic.can_donate_museum_item(item) for item in skeleton_back]) & logic.received(metal_detector, 4) elif milestone_name == "Ancient Seed": - rule = logic.has("Ancient Seed") & logic.received(metal_detector, 4) + rule = logic.can_donate_museum_item(Artifact.ancient_seed) & logic.received(metal_detector, 4) if rule is None: return MultiWorldRules.set_rule(multi_world.get_location(museum_milestone.name, player), rule.simplify()) -def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items): +def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items, donation_func): metal_detector = "Traveling Merchant Metal Detector" num = int(milestone_name[:milestone_name.index(suffix)]) required_detectors = (num - 1) * 5 // len(accepted_items) - rule = logic.has([item.name for item in accepted_items], num) & logic.received(metal_detector, required_detectors) + rule = donation_func(num) & logic.received(metal_detector, required_detectors) return rule diff --git a/worlds/stardew_valley/test/TestRules.py b/worlds/stardew_valley/test/TestRules.py index be7105aa50b0..8556dac1d89a 100644 --- a/worlds/stardew_valley/test/TestRules.py +++ b/worlds/stardew_valley/test/TestRules.py @@ -2,10 +2,12 @@ from . import SVTestBase from .. import options +from ..locations import locations_by_tag, LocationTags, location_table from ..strings.animal_names import Animal from ..strings.animal_product_names import AnimalProduct from ..strings.artisan_good_names import ArtisanGood from ..strings.crop_names import Vegetable +from ..strings.entrance_names import Entrance from ..strings.food_names import Meal from ..strings.ingredient_names import Ingredient from ..strings.machine_names import Machine @@ -369,3 +371,76 @@ class TestRecipeLogic(SVTestBase): # self.assertTrue(logic.has(Machine.cheese_press)(self.multiworld.state)) # self.assertTrue(logic.has(ArtisanGood.cheese)(self.multiworld.state)) # self.assertTrue(logic.has(Meal.pizza)(self.multiworld.state)) + + +class TestDonationLogicAll(SVTestBase): + options = { + options.Museumsanity.internal_name: options.Museumsanity.option_all + } + + def test_cannot_make_any_donation_without_museum_access(self): + guild_item = "Adventurer's Guild" + swap_museum_and_guild(self.multiworld, self.player) + collect_all_except(self.multiworld, guild_item) + + for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]: + self.assertFalse(self.world.logic.can_reach_location(donation.name)(self.multiworld.state)) + + self.multiworld.state.collect(self.world.create_item(guild_item), event=True) + + for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]: + self.assertTrue(self.world.logic.can_reach_location(donation.name)(self.multiworld.state)) + + +class TestDonationLogicRandomized(SVTestBase): + options = { + options.Museumsanity.internal_name: options.Museumsanity.option_randomized + } + + def test_cannot_make_any_donation_without_museum_access(self): + guild_item = "Adventurer's Guild" + swap_museum_and_guild(self.multiworld, self.player) + collect_all_except(self.multiworld, guild_item) + donation_locations = [location for location in self.multiworld.get_locations() if not location.event and LocationTags.MUSEUM_DONATIONS in location_table[location.name].tags] + + for donation in donation_locations: + self.assertFalse(self.world.logic.can_reach_location(donation.name)(self.multiworld.state)) + + self.multiworld.state.collect(self.world.create_item(guild_item), event=True) + + for donation in donation_locations: + self.assertTrue(self.world.logic.can_reach_location(donation.name)(self.multiworld.state)) + + +class TestDonationLogicMilestones(SVTestBase): + options = { + options.Museumsanity.internal_name: options.Museumsanity.option_milestones + } + + def test_cannot_make_any_donation_without_museum_access(self): + guild_item = "Adventurer's Guild" + swap_museum_and_guild(self.multiworld, self.player) + collect_all_except(self.multiworld, guild_item) + + for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]: + self.assertFalse(self.world.logic.can_reach_location(donation.name)(self.multiworld.state)) + + self.multiworld.state.collect(self.world.create_item(guild_item), event=True) + + for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]: + self.assertTrue(self.world.logic.can_reach_location(donation.name)(self.multiworld.state)) + + +def swap_museum_and_guild(multiworld, player): + museum_region = multiworld.get_region(Region.museum, player) + guild_region = multiworld.get_region(Region.adventurer_guild, player) + museum_entrance = multiworld.get_entrance(Entrance.town_to_museum, player) + guild_entrance = multiworld.get_entrance(Entrance.mountain_to_adventurer_guild, player) + museum_entrance.connect(guild_region) + guild_entrance.connect(museum_region) + + +def collect_all_except(multiworld, item_to_not_collect: str): + for item in multiworld.get_items(): + if item.name != item_to_not_collect: + multiworld.state.collect(item)