Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stardew Valley: Added rules requiring museum access to make donations #2107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions worlds/stardew_valley/data/museum_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []

Expand All @@ -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

Expand All @@ -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

Expand Down
38 changes: 28 additions & 10 deletions worlds/stardew_valley/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand All @@ -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)

Expand Down Expand Up @@ -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])
Expand All @@ -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)

Expand All @@ -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))
Expand Down
30 changes: 14 additions & 16 deletions worlds/stardew_valley/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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


Expand Down
75 changes: 75 additions & 0 deletions worlds/stardew_valley/test/TestRules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)