Stardew Valley: Move all the goal logic into its own file (#4383)

This commit is contained in:
Jérémie Bolduc
2025-03-22 15:29:16 -04:00
committed by GitHub
parent 294a67a4b4
commit 9de49aa419
8 changed files with 223 additions and 156 deletions

View File

@@ -214,67 +214,67 @@ class StardewValleyWorld(World):
def setup_victory(self): def setup_victory(self):
if self.options.goal == Goal.option_community_center: if self.options.goal == Goal.option_community_center:
self.create_event_location(location_table[GoalName.community_center], self.create_event_location(location_table[GoalName.community_center],
self.logic.bundle.can_complete_community_center, self.logic.goal.can_complete_community_center(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_grandpa_evaluation: elif self.options.goal == Goal.option_grandpa_evaluation:
self.create_event_location(location_table[GoalName.grandpa_evaluation], self.create_event_location(location_table[GoalName.grandpa_evaluation],
self.logic.can_finish_grandpa_evaluation(), self.logic.goal.can_finish_grandpa_evaluation(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_bottom_of_the_mines: elif self.options.goal == Goal.option_bottom_of_the_mines:
self.create_event_location(location_table[GoalName.bottom_of_the_mines], self.create_event_location(location_table[GoalName.bottom_of_the_mines],
True_(), self.logic.goal.can_complete_bottom_of_the_mines(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_cryptic_note: elif self.options.goal == Goal.option_cryptic_note:
self.create_event_location(location_table[GoalName.cryptic_note], self.create_event_location(location_table[GoalName.cryptic_note],
self.logic.quest.can_complete_quest("Cryptic Note"), self.logic.goal.can_complete_cryptic_note(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_master_angler: elif self.options.goal == Goal.option_master_angler:
self.create_event_location(location_table[GoalName.master_angler], self.create_event_location(location_table[GoalName.master_angler],
self.logic.fishing.can_catch_every_fish_for_fishsanity(), self.logic.goal.can_complete_master_angler(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_complete_collection: elif self.options.goal == Goal.option_complete_collection:
self.create_event_location(location_table[GoalName.complete_museum], self.create_event_location(location_table[GoalName.complete_museum],
self.logic.museum.can_complete_museum(), self.logic.goal.can_complete_complete_collection(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_full_house: elif self.options.goal == Goal.option_full_house:
self.create_event_location(location_table[GoalName.full_house], self.create_event_location(location_table[GoalName.full_house],
(self.logic.relationship.has_children(2) & self.logic.relationship.can_reproduce()), self.logic.goal.can_complete_full_house(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_greatest_walnut_hunter: elif self.options.goal == Goal.option_greatest_walnut_hunter:
self.create_event_location(location_table[GoalName.greatest_walnut_hunter], self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
self.logic.walnut.has_walnut(130), self.logic.goal.can_complete_greatest_walnut_hunter(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_protector_of_the_valley: elif self.options.goal == Goal.option_protector_of_the_valley:
self.create_event_location(location_table[GoalName.protector_of_the_valley], self.create_event_location(location_table[GoalName.protector_of_the_valley],
self.logic.monster.can_complete_all_monster_slaying_goals(), self.logic.goal.can_complete_protector_of_the_valley(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_full_shipment: elif self.options.goal == Goal.option_full_shipment:
self.create_event_location(location_table[GoalName.full_shipment], self.create_event_location(location_table[GoalName.full_shipment],
self.logic.shipping.can_ship_everything_in_slot(self.get_all_location_names()), self.logic.goal.can_complete_full_shipment(self.get_all_location_names()),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_gourmet_chef: elif self.options.goal == Goal.option_gourmet_chef:
self.create_event_location(location_table[GoalName.gourmet_chef], self.create_event_location(location_table[GoalName.gourmet_chef],
self.logic.cooking.can_cook_everything, self.logic.goal.can_complete_gourmet_chef(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_craft_master: elif self.options.goal == Goal.option_craft_master:
self.create_event_location(location_table[GoalName.craft_master], self.create_event_location(location_table[GoalName.craft_master],
self.logic.crafting.can_craft_everything, self.logic.goal.can_complete_craft_master(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_legend: elif self.options.goal == Goal.option_legend:
self.create_event_location(location_table[GoalName.legend], self.create_event_location(location_table[GoalName.legend],
self.logic.money.can_have_earned_total(10_000_000), self.logic.goal.can_complete_legend(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_mystery_of_the_stardrops: elif self.options.goal == Goal.option_mystery_of_the_stardrops:
self.create_event_location(location_table[GoalName.mystery_of_the_stardrops], self.create_event_location(location_table[GoalName.mystery_of_the_stardrops],
self.logic.has_all_stardrops(), self.logic.goal.can_complete_mystery_of_the_stardrop(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_allsanity: elif self.options.goal == Goal.option_allsanity:
self.create_event_location(location_table[GoalName.allsanity], self.create_event_location(location_table[GoalName.allsanity],
HasProgressionPercent(self.player, 100), self.logic.goal.can_complete_allsanity(),
Event.victory) Event.victory)
elif self.options.goal == Goal.option_perfection: elif self.options.goal == Goal.option_perfection:
self.create_event_location(location_table[GoalName.perfection], self.create_event_location(location_table[GoalName.perfection],
HasProgressionPercent(self.player, 100), self.logic.goal.can_complete_perfection(),
Event.victory) Event.victory)
self.multiworld.completion_condition[self.player] = lambda state: state.has(Event.victory, self.player) self.multiworld.completion_condition[self.player] = lambda state: state.has(Event.victory, self.player)

View File

@@ -13,12 +13,9 @@ from .relationship_logic import RelationshipLogicMixin
from .season_logic import SeasonLogicMixin from .season_logic import SeasonLogicMixin
from .skill_logic import SkillLogicMixin from .skill_logic import SkillLogicMixin
from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \ from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \
QueenOfSauceSource, CookingRecipe, ShopFriendshipSource, \ QueenOfSauceSource, CookingRecipe, ShopFriendshipSource
all_cooking_recipes_by_name
from ..data.recipe_source import CutsceneSource, ShopTradeSource from ..data.recipe_source import CutsceneSource, ShopTradeSource
from ..locations import locations_by_tag, LocationTags
from ..options import Chefsanity from ..options import Chefsanity
from ..options import ExcludeGingerIsland
from ..stardew_rule import StardewRule, True_, False_ from ..stardew_rule import StardewRule, True_, False_
from ..strings.region_names import LogicRegion from ..strings.region_names import LogicRegion
from ..strings.skill_names import Skill from ..strings.skill_names import Skill
@@ -92,17 +89,3 @@ BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]
@cache_self1 @cache_self1
def received_recipe(self, meal_name: str): def received_recipe(self, meal_name: str):
return self.logic.received(f"{meal_name} Recipe") return self.logic.received(f"{meal_name} Recipe")
@cached_property
def can_cook_everything(self) -> StardewRule:
cooksanity_prefix = "Cook "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
for location in locations_by_tag[LocationTags.COOKSANITY]:
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
continue
if location.mod_name and location.mod_name not in self.options.mods:
continue
all_recipes_names.append(location.name[len(cooksanity_prefix):])
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return self.logic.and_(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))

View File

@@ -1,4 +1,3 @@
from functools import cached_property
from typing import Union from typing import Union
from Utils import cache_self1 from Utils import cache_self1
@@ -12,11 +11,10 @@ from .relationship_logic import RelationshipLogicMixin
from .skill_logic import SkillLogicMixin from .skill_logic import SkillLogicMixin
from .special_order_logic import SpecialOrderLogicMixin from .special_order_logic import SpecialOrderLogicMixin
from .. import options from .. import options
from ..data.craftable_data import CraftingRecipe, all_crafting_recipes_by_name from ..data.craftable_data import CraftingRecipe
from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \ from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \
FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource
from ..locations import locations_by_tag, LocationTags from ..options import Craftsanity, SpecialOrderLocations
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland
from ..stardew_rule import StardewRule, True_, False_ from ..stardew_rule import StardewRule, True_, False_
from ..strings.region_names import Region from ..strings.region_names import Region
@@ -71,7 +69,8 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
if isinstance(recipe.source, ShopSource): if isinstance(recipe.source, ShopSource):
return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price) return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price)
if isinstance(recipe.source, SkillCraftsanitySource): if isinstance(recipe.source, SkillCraftsanitySource):
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level) & self.logic.skill.can_earn_level(recipe.source.skill, recipe.source.level) return self.logic.skill.has_level(recipe.source.skill, recipe.source.level) & self.logic.skill.can_earn_level(recipe.source.skill,
recipe.source.level)
if isinstance(recipe.source, SkillSource): if isinstance(recipe.source, SkillSource):
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level) return self.logic.skill.has_level(recipe.source.skill, recipe.source.level)
if isinstance(recipe.source, MasterySource): if isinstance(recipe.source, MasterySource):
@@ -95,23 +94,3 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
@cache_self1 @cache_self1
def received_recipe(self, item_name: str): def received_recipe(self, item_name: str):
return self.logic.received(f"{item_name} Recipe") return self.logic.received(f"{item_name} Recipe")
@cached_property
def can_craft_everything(self) -> StardewRule:
craftsanity_prefix = "Craft "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_masteries = not self.content.features.skill_progression.are_masteries_shuffled
for location in locations_by_tag[LocationTags.CRAFTSANITY]:
if not location.name.startswith(craftsanity_prefix):
continue
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
continue
# FIXME Remove when recipes are in content packs
if exclude_masteries and LocationTags.REQUIRES_MASTERIES in location.tags:
continue
if location.mod_name and location.mod_name not in self.options.mods:
continue
all_recipes_names.append(location.name[len(craftsanity_prefix):])
all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return self.logic.and_(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))

View File

@@ -29,7 +29,7 @@ class FishingLogicMixin(BaseLogicMixin):
class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin,
SkillLogicMixin]]): SkillLogicMixin]]):
def can_fish_in_freshwater(self) -> StardewRule: def can_fish_in_freshwater(self) -> StardewRule:
return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain)) return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain))
@@ -97,19 +97,5 @@ class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLog
return self.logic.and_(*rules) return self.logic.and_(*rules)
def can_catch_every_fish_for_fishsanity(self) -> StardewRule:
if not self.content.features.fishsanity.is_enabled:
return self.can_catch_every_fish()
rules = [self.has_max_fishing()]
rules.extend(
self.logic.fishing.can_catch_fish_for_fishsanity(fish)
for fish in self.content.fishes.values()
if self.content.features.fishsanity.is_included(fish)
)
return self.logic.and_(*rules)
def has_specific_bait(self, fish: FishItem) -> StardewRule: def has_specific_bait(self, fish: FishItem) -> StardewRule:
return self.can_catch_fish(fish) & self.logic.has(Machine.bait_maker) return self.can_catch_fish(fish) & self.logic.has(Machine.bait_maker)

View File

@@ -0,0 +1,173 @@
import typing
from .base_logic import BaseLogic, BaseLogicMixin
from ..data.craftable_data import all_crafting_recipes_by_name
from ..data.recipe_data import all_cooking_recipes_by_name
from ..locations import LocationTags, locations_by_tag
from ..mods.mod_data import ModNames
from ..options import options
from ..stardew_rule import StardewRule
from ..strings.building_names import Building
from ..strings.quest_names import Quest
from ..strings.season_names import Season
from ..strings.wallet_item_names import Wallet
if typing.TYPE_CHECKING:
from .logic import StardewLogic
else:
StardewLogic = object
class GoalLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.goal = GoalLogic(*args, **kwargs)
class GoalLogic(BaseLogic[StardewLogic]):
def can_complete_community_center(self) -> StardewRule:
return self.logic.bundle.can_complete_community_center
def can_finish_grandpa_evaluation(self) -> StardewRule:
# https://stardewvalleywiki.com/Grandpa
rules_worth_a_point = [
self.logic.money.can_have_earned_total(50_000),
self.logic.money.can_have_earned_total(100_000),
self.logic.money.can_have_earned_total(200_000),
self.logic.money.can_have_earned_total(300_000),
self.logic.money.can_have_earned_total(500_000),
self.logic.money.can_have_earned_total(1_000_000), # first point
self.logic.money.can_have_earned_total(1_000_000), # second point
self.logic.skill.has_total_level(30),
self.logic.skill.has_total_level(50),
self.logic.museum.can_complete_museum(),
# Catching every fish not expected
# Shipping every item not expected
self.logic.relationship.can_get_married() & self.logic.building.has_house(2),
self.logic.relationship.has_hearts_with_n(5, 8), # 5 Friends
self.logic.relationship.has_hearts_with_n(10, 8), # 10 friends
self.logic.pet.has_pet_hearts(5), # Max Pet
self.logic.bundle.can_complete_community_center, # 1 point for Community Center Completion
self.logic.bundle.can_complete_community_center, # Ceremony first point
self.logic.bundle.can_complete_community_center, # Ceremony second point
self.logic.received(Wallet.skull_key),
self.logic.wallet.has_rusty_key(),
]
return self.logic.count(12, *rules_worth_a_point)
def can_complete_bottom_of_the_mines(self) -> StardewRule:
# The location is in the bottom of the mines region, so no actual rule is required
return self.logic.true_
def can_complete_cryptic_note(self) -> StardewRule:
return self.logic.quest.can_complete_quest(Quest.cryptic_note)
def can_complete_master_angler(self) -> StardewRule:
if not self.content.features.fishsanity.is_enabled:
return self.logic.fishing.can_catch_every_fish()
rules = [self.logic.fishing.has_max_fishing()]
rules.extend(
self.logic.fishing.can_catch_fish_for_fishsanity(fish)
for fish in self.content.fishes.values()
if self.content.features.fishsanity.is_included(fish)
)
return self.logic.and_(*rules)
def can_complete_complete_collection(self) -> StardewRule:
return self.logic.museum.can_complete_museum()
def can_complete_full_house(self) -> StardewRule:
return self.logic.relationship.has_children(2) & self.logic.relationship.can_reproduce()
def can_complete_greatest_walnut_hunter(self) -> StardewRule:
return self.logic.walnut.has_walnut(130)
def can_complete_protector_of_the_valley(self) -> StardewRule:
return self.logic.monster.can_complete_all_monster_slaying_goals()
def can_complete_full_shipment(self, all_location_names_in_slot: list[str]) -> StardewRule:
if self.options.shipsanity == options.Shipsanity.option_none:
return self.logic.shipping.can_ship_everything()
rules = [self.logic.building.has_building(Building.shipping_bin)]
for shipsanity_location in locations_by_tag[LocationTags.SHIPSANITY]:
if shipsanity_location.name not in all_location_names_in_slot:
continue
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
return self.logic.and_(*rules)
def can_complete_gourmet_chef(self) -> StardewRule:
cooksanity_prefix = "Cook "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
for location in locations_by_tag[LocationTags.COOKSANITY]:
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
continue
if location.mod_name and location.mod_name not in self.options.mods:
continue
all_recipes_names.append(location.name[len(cooksanity_prefix):])
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return self.logic.and_(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))
def can_complete_craft_master(self) -> StardewRule:
craftsanity_prefix = "Craft "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
exclude_masteries = not self.content.features.skill_progression.are_masteries_shuffled
for location in locations_by_tag[LocationTags.CRAFTSANITY]:
if not location.name.startswith(craftsanity_prefix):
continue
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
continue
# FIXME Remove when recipes are in content packs
if exclude_masteries and LocationTags.REQUIRES_MASTERIES in location.tags:
continue
if location.mod_name and location.mod_name not in self.options.mods:
continue
all_recipes_names.append(location.name[len(craftsanity_prefix):])
all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return self.logic.and_(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))
def can_complete_legend(self) -> StardewRule:
return self.logic.money.can_have_earned_total(10_000_000)
def can_complete_mystery_of_the_stardrop(self) -> StardewRule:
other_rules = []
number_of_stardrops_to_receive = 0
number_of_stardrops_to_receive += 1 # The Mines level 100
number_of_stardrops_to_receive += 1 # Old Master Cannoli
number_of_stardrops_to_receive += 1 # Museum Stardrop
number_of_stardrops_to_receive += 1 # Krobus Stardrop
# Master Angler Stardrop
if self.content.features.fishsanity.is_enabled:
number_of_stardrops_to_receive += 1
else:
other_rules.append(self.logic.fishing.can_catch_every_fish())
if self.options.festival_locations == options.FestivalLocations.option_disabled: # Fair Stardrop
other_rules.append(self.logic.season.has(Season.fall))
else:
number_of_stardrops_to_receive += 1
# Spouse Stardrop
if self.content.features.friendsanity.is_enabled:
number_of_stardrops_to_receive += 1
else:
other_rules.append(self.logic.relationship.has_hearts_with_any_bachelor(13))
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
number_of_stardrops_to_receive += 1
return self.logic.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules, allow_empty=True)
def can_complete_allsanity(self) -> StardewRule:
return self.logic.has_progress_percent(100)
def can_complete_perfection(self) -> StardewRule:
return self.logic.has_progress_percent(100)

View File

@@ -1,5 +1,5 @@
from .base_logic import BaseLogic from .base_logic import BaseLogic
from ..stardew_rule import StardewRule, And, Or, Has, Count, true_, false_ from ..stardew_rule import StardewRule, And, Or, Has, Count, true_, false_, HasProgressionPercent
class HasLogicMixin(BaseLogic[None]): class HasLogicMixin(BaseLogic[None]):
@@ -23,6 +23,12 @@ class HasLogicMixin(BaseLogic[None]):
def has_n(self, *items: str, count: int): def has_n(self, *items: str, count: int):
return self.count(count, *(self.has(item) for item in items)) return self.count(count, *(self.has(item) for item in items))
def has_progress_percent(self, percent: int):
assert percent >= 0, "Can't have a negative progress percent"
assert percent <= 100, "Can't have a progress percent over 100"
return HasProgressionPercent(self.player, percent)
@staticmethod @staticmethod
def count(count: int, *rules: StardewRule) -> StardewRule: def count(count: int, *rules: StardewRule) -> StardewRule:
assert rules, "Can't create a Count conditions without rules" assert rules, "Can't create a Count conditions without rules"
@@ -47,8 +53,14 @@ class HasLogicMixin(BaseLogic[None]):
return Count(rules, count) return Count(rules, count)
@staticmethod @staticmethod
def and_(*rules: StardewRule) -> StardewRule: def and_(*rules: StardewRule, allow_empty: bool = False) -> StardewRule:
assert rules, "Can't create a And conditions without rules" """
:param rules: The rules to combine
:param allow_empty: If True, return true_ when no rules are given. Otherwise, raise an error.
"""
if not rules:
assert allow_empty, "Can't create a And conditions without rules"
return true_
if len(rules) == 1: if len(rules) == 1:
return rules[0] return rules[0]
@@ -56,8 +68,14 @@ class HasLogicMixin(BaseLogic[None]):
return And(*rules) return And(*rules)
@staticmethod @staticmethod
def or_(*rules: StardewRule) -> StardewRule: def or_(*rules: StardewRule, allow_empty: bool = False) -> StardewRule:
assert rules, "Can't create a Or conditions without rules" """
:param rules: The rules to combine
:param allow_empty: If True, return false_ when no rules are given. Otherwise, raise an error.
"""
if not rules:
assert allow_empty, "Can't create a Or conditions without rules"
return false_
if len(rules) == 1: if len(rules) == 1:
return rules[0] return rules[0]

View File

@@ -19,6 +19,7 @@ from .farming_logic import FarmingLogicMixin
from .festival_logic import FestivalLogicMixin from .festival_logic import FestivalLogicMixin
from .fishing_logic import FishingLogicMixin from .fishing_logic import FishingLogicMixin
from .gift_logic import GiftLogicMixin from .gift_logic import GiftLogicMixin
from .goal_logic import GoalLogicMixin
from .grind_logic import GrindLogicMixin from .grind_logic import GrindLogicMixin
from .harvesting_logic import HarvestingLogicMixin from .harvesting_logic import HarvestingLogicMixin
from .has_logic import HasLogicMixin from .has_logic import HasLogicMixin
@@ -50,8 +51,7 @@ from ..data.museum_data import all_museum_items
from ..data.recipe_data import all_cooking_recipes from ..data.recipe_data import all_cooking_recipes
from ..mods.logic.magic_logic import MagicLogicMixin from ..mods.logic.magic_logic import MagicLogicMixin
from ..mods.logic.mod_logic import ModLogicMixin from ..mods.logic.mod_logic import ModLogicMixin
from ..mods.mod_data import ModNames from ..options import ExcludeGingerIsland, StardewValleyOptions
from ..options import ExcludeGingerIsland, FestivalLocations, StardewValleyOptions
from ..stardew_rule import False_, True_, StardewRule from ..stardew_rule import False_, True_, StardewRule
from ..strings.animal_names import Animal from ..strings.animal_names import Animal
from ..strings.animal_product_names import AnimalProduct from ..strings.animal_product_names import AnimalProduct
@@ -93,7 +93,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin, CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin,
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin, SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin, SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin,
RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, FestivalLogicMixin, WalnutLogicMixin): RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, FestivalLogicMixin, WalnutLogicMixin, GoalLogicMixin):
player: int player: int
options: StardewValleyOptions options: StardewValleyOptions
content: StardewContent content: StardewContent
@@ -375,71 +375,11 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
def can_smelt(self, item: str) -> StardewRule: def can_smelt(self, item: str) -> StardewRule:
return self.has(Machine.furnace) & self.has(item) return self.has(Machine.furnace) & self.has(item)
def can_finish_grandpa_evaluation(self) -> StardewRule:
# https://stardewvalleywiki.com/Grandpa
rules_worth_a_point = [
self.money.can_have_earned_total(50000), # 50 000g
self.money.can_have_earned_total(100000), # 100 000g
self.money.can_have_earned_total(200000), # 200 000g
self.money.can_have_earned_total(300000), # 300 000g
self.money.can_have_earned_total(500000), # 500 000g
self.money.can_have_earned_total(1000000), # 1 000 000g first point
self.money.can_have_earned_total(1000000), # 1 000 000g second point
self.skill.has_total_level(30), # Total Skills: 30
self.skill.has_total_level(50), # Total Skills: 50
self.museum.can_complete_museum(), # Completing the museum for a point
# Catching every fish not expected
# Shipping every item not expected
self.relationship.can_get_married() & self.building.has_house(2),
self.relationship.has_hearts_with_n(5, 8), # 5 Friends
self.relationship.has_hearts_with_n(10, 8), # 10 friends
self.pet.has_pet_hearts(5), # Max Pet
self.bundle.can_complete_community_center, # Community Center Completion
self.bundle.can_complete_community_center, # CC Ceremony first point
self.bundle.can_complete_community_center, # CC Ceremony second point
self.received(Wallet.skull_key), # Skull Key obtained
self.wallet.has_rusty_key(), # Rusty key obtained
]
return self.count(12, *rules_worth_a_point)
def has_island_trader(self) -> StardewRule: def has_island_trader(self) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return False_() return False_()
return self.region.can_reach(Region.island_trader) return self.region.can_reach(Region.island_trader)
def has_all_stardrops(self) -> StardewRule:
other_rules = []
number_of_stardrops_to_receive = 0
number_of_stardrops_to_receive += 1 # The Mines level 100
number_of_stardrops_to_receive += 1 # Old Master Cannoli
number_of_stardrops_to_receive += 1 # Museum Stardrop
number_of_stardrops_to_receive += 1 # Krobus Stardrop
# Master Angler Stardrop
if self.content.features.fishsanity.is_enabled:
number_of_stardrops_to_receive += 1
else:
other_rules.append(self.fishing.can_catch_every_fish())
if self.options.festival_locations == FestivalLocations.option_disabled: # Fair Stardrop
other_rules.append(self.season.has(Season.fall))
else:
number_of_stardrops_to_receive += 1
# Spouse Stardrop
if self.content.features.friendsanity.is_enabled:
number_of_stardrops_to_receive += 1
else:
other_rules.append(self.relationship.has_hearts_with_any_bachelor(13))
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
number_of_stardrops_to_receive += 1
if not other_rules:
return self.received("Stardrop", number_of_stardrops_to_receive)
return self.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules)
def has_abandoned_jojamart(self) -> StardewRule: def has_abandoned_jojamart(self) -> StardewRule:
return self.received(CommunityUpgrade.movie_theater, 1) return self.received(CommunityUpgrade.movie_theater, 1)

View File

@@ -1,5 +1,5 @@
from functools import cached_property from functools import cached_property
from typing import Union, List from typing import Union
from Utils import cache_self1 from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin from .base_logic import BaseLogic, BaseLogicMixin
@@ -8,7 +8,7 @@ from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin from .region_logic import RegionLogicMixin
from ..locations import LocationTags, locations_by_tag from ..locations import LocationTags, locations_by_tag
from ..options import ExcludeGingerIsland, Shipsanity from ..options import ExcludeGingerIsland
from ..options import SpecialOrderLocations from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule from ..stardew_rule import StardewRule
from ..strings.building_names import Building from ..strings.building_names import Building
@@ -45,15 +45,3 @@ class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, Buil
continue continue
all_items_to_ship.append(location.name[len(shipsanity_prefix):]) all_items_to_ship.append(location.name[len(shipsanity_prefix):])
return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship) return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship)
def can_ship_everything_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
if self.options.shipsanity == Shipsanity.option_none:
return self.logic.shipping.can_ship_everything()
rules = [self.logic.building.has_building(Building.shipping_bin)]
for shipsanity_location in locations_by_tag[LocationTags.SHIPSANITY]:
if shipsanity_location.name not in all_location_names_in_slot:
continue
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
return self.logic.and_(*rules)