Stardew Valley: Added rules requiring museum access to make donations (#2107)
This commit is contained in:
		| @@ -43,8 +43,8 @@ class MuseumItem(GameItem): | ||||
|  | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ from .data import all_fish, FishItem, all_purchasable_seeds, SeedItem, all_crops | ||||
| 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 @@ class StardewLogic: | ||||
|             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 @@ class StardewLogic: | ||||
|                                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 @@ class StardewLogic: | ||||
|                                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 @@ class StardewLogic: | ||||
|  | ||||
|     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 @@ class StardewLogic: | ||||
|  | ||||
|     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 @@ class StardewLogic: | ||||
|         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)) | ||||
|   | ||||
| @@ -5,15 +5,14 @@ from BaseClasses import MultiWorld | ||||
| 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.calendar_names import Weekday | ||||
| 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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,10 +2,12 @@ from collections import Counter | ||||
|  | ||||
| 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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 agilbert1412
					agilbert1412