Files
agilbert1412 1de91fab67 Stardew Valley: 7.x.x - The Jojapocalypse Update (#5432)
Major Content update for Stardew Valley

### Features
- New BundleRandomization Value: Meme Bundles - Over 100 custom bundles, designed to be jokes, references, trolls, etc
- New Setting: Bundles Per Room modifier
- New Setting: Backpack Size
- New Setting: Secretsanity - Checks for triggering easter eggs and secrets
- New Setting: Moviesanity - Checks for watching movies and sharing snacks with Villagers
- New Setting: Eatsanity - Checks for eating items
- New Setting: Hatsanity - Checks for wearing Hats
- New Setting: Start Without - Allows you to select any combination of various "starting" items, that you will actually not start with. Notably, tools, backpack slots, Day5 unlocks, etc.
- New Setting: Allowed Filler Items - Allows you to customize the filler items you'll get
- New Setting: Endgame Locations - Checks for various expensive endgame tasks and purchases
- New Shipsanity value: Crops and Fish
- New Settings: Jojapocalypse and settings to customize it
- Bundle Plando: Replaced with BundleWhitelist and BundleBlacklist, for more customization freedom
- Added a couple of Host.yaml settings to help hosts allow or ban specific difficult settings that could cause problems if the people don't know what they are signing up for.

Plus a truckload of improvements on the mod side, not seen in this PR.

### Removed features
- Integration for Stardew Valley Expanded. It is simply disabled, the code is all still there, but I'm extremely tired of providing tech support for it, plus Stardew Valley 1.7 was announced and that will break it again, so I'm done. When a maintainer steps up, it can be re-enabled.
2026-02-15 18:02:21 +01:00

1303 lines
88 KiB
Python

import itertools
import logging
from dataclasses import dataclass
from typing import List, Dict, Set
from BaseClasses import MultiWorld, CollectionState
from worlds.generic.Rules import set_rule as _set_rule
from . import locations
from .bundles.bundle_room import BundleRoom
from .content import StardewContent
from .content.feature import friendsanity
from .content.vanilla.ginger_island import ginger_island_content_pack
from .content.vanilla.qi_board import qi_board_content_pack
from .data.craftable_data import all_crafting_recipes_by_name
from .data.game_item import ItemTag
from .data.harvest import HarvestCropSource, HarvestFruitTreeSource
from .data.museum_data import all_museum_items, dwarf_scrolls, skeleton_front, skeleton_middle, skeleton_back, \
all_museum_items_by_name, all_museum_minerals, \
all_museum_artifacts, Artifact
from .data.recipe_data import all_cooking_recipes_by_name
from .data.secret_note_data import gift_requirements, SecretNote
from .locations import LocationTags
from .logic.logic import StardewLogic
from .logic.time_logic import MAX_MONTHS
from .logic.tool_logic import tool_upgrade_prices
from .mods.mod_data import ModNames
from .options import SpecialOrderLocations, Museumsanity, BackpackProgression, Shipsanity, \
Monstersanity, Chefsanity, Craftsanity, ArcadeMachineLocations, Cooksanity, StardewValleyOptions, Walnutsanity
from .options.options import FarmType, Moviesanity, Eatsanity, Friendsanity, ExcludeGingerIsland, \
IncludeEndgameLocations
from .stardew_rule import And, StardewRule, true_
from .stardew_rule.indirect_connection import look_for_indirect_connection
from .stardew_rule.rule_explain import explain
from .strings.animal_product_names import AnimalProduct
from .strings.ap_names.ap_option_names import WalnutsanityOptionName, SecretsanityOptionName, StartWithoutOptionName, CustomLogicOptionName
from .strings.ap_names.community_upgrade_names import CommunityUpgrade, Bookseller
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
from .strings.backpack_tiers import Backpack
from .strings.building_names import Building, WizardBuilding
from .strings.bundle_names import CCRoom
from .strings.calendar_names import Weekday
from .strings.craftable_names import Bomb, Furniture, Consumable, Craftable
from .strings.crop_names import Fruit, Vegetable
from .strings.currency_names import Currency
from .strings.entrance_names import dig_to_mines_floor, dig_to_skull_floor, Entrance, move_to_woods_depth, \
DeepWoodsEntrance, AlecEntrance, \
SVEEntrance, LaceyEntrance, BoardingHouseEntrance, LogicEntrance
from .strings.fish_names import Fish
from .strings.food_names import Meal
from .strings.forageable_names import Forageable
from .strings.generic_names import Generic
from .strings.geode_names import Geode
from .strings.gift_names import Gift
from .strings.machine_names import Machine
from .strings.material_names import Material
from .strings.metal_names import Artifact as ArtifactName, MetalBar, Mineral
from .strings.monster_names import Monster
from .strings.performance_names import Performance
from .strings.quest_names import Quest
from .strings.region_names import Region, LogicRegion
from .strings.season_names import Season
from .strings.skill_names import Skill
from .strings.special_item_names import SpecialItem
from .strings.special_order_names import SpecialOrder
from .strings.tool_names import Tool, ToolMaterial, FishingRod
from .strings.tv_channel_names import Channel
from .strings.villager_names import NPC, ModNPC
from .strings.wallet_item_names import Wallet
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class StardewRuleCollector:
multiworld: MultiWorld
player: int
content: StardewContent
def set_entrance_rule(self, entrance_name: str, rule: StardewRule) -> None:
try:
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_name}")
self.multiworld.register_indirect_condition(self.multiworld.get_region(region, self.player),
self.multiworld.get_entrance(entrance_name, self.player))
_set_rule(self.multiworld.get_entrance(entrance_name, self.player), rule)
except KeyError as ex:
logger.error(f"""Failed to evaluate indirect connection in: {explain(rule, CollectionState(self.multiworld))}""")
raise ex
def set_island_entrance_rule(self, entrance_name: str, rule: StardewRule) -> None:
if not self.content.is_enabled(ginger_island_content_pack):
return
self.set_entrance_rule(entrance_name, rule)
def set_many_island_entrances_rules(self, entrance_rules: dict[str, StardewRule]) -> None:
if not self.content.is_enabled(ginger_island_content_pack):
return
for entrance, rule in entrance_rules.items():
self.set_entrance_rule(entrance, rule)
def set_location_rule(self, location_name: str, rule: StardewRule) -> None:
_set_rule(self.multiworld.get_location(location_name, self.player), rule)
def set_rules(world):
world_options = world.options
world_content = world.content
rule_collector = StardewRuleCollector(world.multiworld, world.player, world_content)
logic = world.logic
bundle_rooms: List[BundleRoom] = world.modified_bundles
trash_bear_requests: Dict[str, List[str]] = world.trash_bear_requests
all_location_names = set(location.name for location in world.multiworld.get_locations(world.player))
set_entrance_rules(logic, rule_collector, bundle_rooms, world_options, world_content)
set_ginger_island_rules(logic, rule_collector, world_options, world_content)
set_tool_rules(logic, rule_collector, world_content)
set_skills_rules(logic, rule_collector, world_content)
set_bundle_rules(bundle_rooms, logic, rule_collector, world_options)
set_building_rules(logic, rule_collector, world_content)
set_cropsanity_rules(logic, rule_collector, world_content)
set_story_quests_rules(all_location_names, logic, rule_collector, world_options)
set_special_order_rules(all_location_names, logic, rule_collector, world_options, world_content)
set_help_wanted_quests_rules(logic, rule_collector, world_options)
set_fishsanity_rules(all_location_names, logic, rule_collector)
set_museumsanity_rules(all_location_names, logic, rule_collector, world_options)
set_friendsanity_rules(logic, rule_collector, world_content)
set_backpack_rules(logic, rule_collector, world_options, world_content)
set_festival_rules(all_location_names, logic, rule_collector)
set_monstersanity_rules(all_location_names, logic, rule_collector, world_options)
set_shipsanity_rules(all_location_names, logic, rule_collector, world_options)
set_cooksanity_rules(all_location_names, logic, rule_collector, world_options)
set_chefsanity_rules(all_location_names, logic, rule_collector, world_options)
set_craftsanity_rules(all_location_names, logic, rule_collector, world_options)
set_booksanity_rules(logic, rule_collector, world_content)
set_isolated_locations_rules(logic, rule_collector, trash_bear_requests)
set_arcade_machine_rules(logic, rule_collector, world_options)
set_movie_rules(logic, rule_collector, world_options, world_content)
set_secrets_rules(logic, rule_collector, world_options, world_content)
set_hatsanity_rules(logic, rule_collector, world_content)
set_eatsanity_rules(all_location_names, logic, rule_collector, world_options)
set_endgame_locations_rules(logic, rule_collector, world_options)
set_deepwoods_rules(logic, rule_collector, world_content)
set_magic_spell_rules(logic, rule_collector, world_content)
set_sve_rules(logic, rule_collector, world_content)
def set_isolated_locations_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, trash_bear_requests: Dict[str, List[str]]):
rule_collector.set_location_rule("Beach Bridge Repair", logic.grind.can_grind_item(300, "Wood"))
rule_collector.set_location_rule("Grim Reaper Statue", logic.combat.can_fight_at_level(Performance.decent) & logic.tool.has_tool(Tool.pickaxe))
rule_collector.set_location_rule("Galaxy Sword Shrine", logic.has("Prismatic Shard"))
rule_collector.set_location_rule("Krobus Stardrop", logic.money.can_spend(20000))
rule_collector.set_location_rule("Demetrius's Breakthrough", logic.money.can_have_earned_total(25000))
for request_type in trash_bear_requests:
location = f"Trash Bear {request_type}"
items = trash_bear_requests[request_type]
rule_collector.set_location_rule(location, logic.bundle.can_feed_trash_bear(*items))
def set_tool_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
tool_progression = content.features.tool_progression
if not tool_progression.is_progressive:
return
rule_collector.set_location_rule("Purchase Fiberglass Rod", (logic.skill.has_level(Skill.fishing, 2) & logic.money.can_spend(1800)))
rule_collector.set_location_rule("Purchase Iridium Rod", (logic.skill.has_level(Skill.fishing, 6) & logic.money.can_spend(7500)))
rule_collector.set_location_rule("Copper Pan Cutscene", logic.received("Glittering Boulder Removed"))
# Pan has no basic tier, so it is removed from materials.
pan_materials = ToolMaterial.materials[1:]
for previous, material in itertools.product(pan_materials[:-1], pan_materials[1:]):
location_name = tool_progression.to_upgrade_location_name(Tool.pan, material)
# You need to receive the previous tool to be able to upgrade it.
rule_collector.set_location_rule(location_name, logic.tool.has_pan(previous))
materials = ToolMaterial.materials
tool = [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can]
for (previous, material), tool in itertools.product(zip(materials[:-1], materials[1:]), tool):
location_name = tool_progression.to_upgrade_location_name(tool, material)
# You need to receive the previous tool to be able to upgrade it.
rule_collector.set_location_rule(location_name, logic.tool.has_tool(tool, previous))
def set_building_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
building_progression = content.features.building_progression
if not building_progression.is_progressive:
return
for building in content.farm_buildings.values():
if building.name in building_progression.starting_buildings:
continue
location_name = building_progression.to_location_name(building.name)
rule_collector.set_location_rule(location_name, logic.building.can_build(building.name))
def set_bundle_rules(bundle_rooms: List[BundleRoom], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
for bundle_room in bundle_rooms:
if bundle_room.name == CCRoom.raccoon_requests:
# The rule for the raccoon bundles are placed on their entrance, not on the location itself.
continue
room_rules = []
for bundle in bundle_room.bundles:
bundle_rules = logic.bundle.can_complete_bundle(bundle)
room_rules.append(bundle_rules)
rule_collector.set_location_rule(bundle.name, bundle_rules)
if bundle_room.name == CCRoom.abandoned_joja_mart or bundle_room.name == CCRoom.raccoon_requests:
continue
room_location = f"Complete {bundle_room.name}"
rule_collector.set_location_rule(room_location, And(*room_rules))
def set_skills_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
skill_progression = content.features.skill_progression
if not skill_progression.is_progressive:
return
for skill in content.skills.values():
for level, level_name in skill_progression.get_randomized_level_names_by_level(skill):
rule = logic.skill.can_earn_level(skill.name, level)
rule_collector.set_location_rule(level_name, rule)
if skill_progression.is_mastery_randomized(skill):
rule = logic.skill.can_earn_mastery(skill.name)
rule_collector.set_location_rule(skill.mastery_name, rule)
def set_entrance_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, bundle_rooms: List[BundleRoom], world_options: StardewValleyOptions,
content: StardewContent):
set_mines_floor_entrance_rules(logic, rule_collector, world_options)
set_skull_cavern_floor_entrance_rules(logic, rule_collector, world_options)
set_blacksmith_entrance_rules(logic, rule_collector)
set_skill_entrance_rules(logic, rule_collector, content)
set_traveling_merchant_day_entrance_rules(logic, rule_collector)
set_dangerous_mine_rules(logic, rule_collector, content)
rule_collector.set_entrance_rule(Entrance.enter_tide_pools, logic.received("Beach Bridge") | logic.mod.magic.can_blink())
rule_collector.set_entrance_rule(Entrance.mountain_to_outside_adventure_guild, logic.received("Landslide Removed"))
rule_collector.set_entrance_rule(Entrance.enter_quarry,
(logic.received("Bridge Repair") | logic.mod.magic.can_blink()) & logic.tool.has_tool(Tool.pickaxe))
rule_collector.set_entrance_rule(Entrance.enter_secret_woods, logic.tool.has_tool(Tool.axe, ToolMaterial.iron) | logic.mod.magic.can_blink() | logic.ability.can_chair_skip())
rule_collector.set_entrance_rule(Entrance.town_to_community_center, logic.received("Community Center Key"))
rule_collector.set_entrance_rule(Entrance.forest_to_wizard_tower, logic.received("Wizard Invitation"))
rule_collector.set_entrance_rule(Entrance.forest_to_sewer, logic.wallet.has_rusty_key())
rule_collector.set_entrance_rule(Entrance.town_to_sewer, logic.wallet.has_rusty_key())
# The money requirement is just in case Joja got replaced by a theater, you need to buy a ticket.
# We do not put directly a ticket requirement, because we don't want to place an indirect theater requirement only
# for the safeguard "in case you get a theater"
rule_collector.set_entrance_rule(Entrance.town_to_jojamart, logic.money.can_spend(1000))
rule_collector.set_entrance_rule(Entrance.enter_abandoned_jojamart, logic.has_abandoned_jojamart())
movie_theater_rule = logic.has_movie_theater()
rule_collector.set_entrance_rule(Entrance.purchase_movie_ticket, movie_theater_rule)
rule_collector.set_entrance_rule(Entrance.enter_movie_theater, movie_theater_rule & logic.has(Gift.movie_ticket))
rule_collector.set_entrance_rule(Entrance.take_bus_to_desert, logic.received(Transportation.bus_repair) & logic.money.can_spend(500))
rule_collector.set_entrance_rule(Entrance.enter_skull_cavern, logic.received(Wallet.skull_key))
rule_collector.set_entrance_rule(LogicEntrance.talk_to_mines_dwarf,
logic.wallet.can_speak_dwarf() & logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron))
rule_collector.set_entrance_rule(LogicEntrance.buy_from_traveling_merchant, logic.traveling_merchant.has_days() & logic.money.can_spend(1200))
set_raccoon_rules(logic, rule_collector, bundle_rooms, world_options)
rule_collector.set_entrance_rule(LogicEntrance.fish_in_waterfall,
logic.skill.has_level(Skill.fishing, 5) & logic.tool.has_fishing_rod(FishingRod.bamboo))
set_farm_buildings_entrance_rules(logic, rule_collector)
rule_collector.set_entrance_rule(Entrance.mountain_to_railroad, logic.received("Railroad Boulder Removed"))
rule_collector.set_entrance_rule(Entrance.enter_witch_warp_cave, logic.quest.has_dark_talisman() | (logic.mod.magic.can_blink()))
rule_collector.set_entrance_rule(Entrance.enter_witch_hut, (logic.quest.can_complete_quest(Quest.goblin_problem) | logic.mod.magic.can_blink()))
rule_collector.set_entrance_rule(Entrance.enter_mutant_bug_lair,
(logic.wallet.has_rusty_key() & logic.region.can_reach(Region.railroad) & logic.relationship.can_meet(NPC.krobus))
| logic.mod.magic.can_blink())
rule_collector.set_entrance_rule(Entrance.enter_casino, logic.quest.has_club_card())
set_bedroom_entrance_rules(logic, rule_collector, content)
set_festival_entrance_rules(logic, rule_collector)
# I can't remember why this was here, but clearly we do not need kitchen rules for island cooking....
# rule_collector.set_island_entrance_rule(LogicEntrance.island_cooking, logic.cooking.can_cook_in_kitchen)
rule_collector.set_entrance_rule(LogicEntrance.farmhouse_cooking, logic.cooking.can_cook_in_kitchen)
rule_collector.set_entrance_rule(LogicEntrance.shipping, logic.shipping.can_use_shipping_bin)
rule_collector.set_entrance_rule(LogicEntrance.find_secret_notes,
logic.quest.has_magnifying_glass() & (logic.ability.can_chop_trees() | logic.mine.can_mine_in_the_mines_floor_1_40()))
rule_collector.set_entrance_rule(LogicEntrance.watch_queen_of_sauce, logic.action.can_watch(Channel.queen_of_sauce))
rule_collector.set_entrance_rule(Entrance.forest_to_mastery_cave, logic.skill.can_enter_mastery_cave)
set_bookseller_rules(logic, rule_collector)
rule_collector.set_entrance_rule(Entrance.adventurer_guild_to_bedroom, logic.monster.can_kill_max(Generic.any))
if world_options.include_endgame_locations == IncludeEndgameLocations.option_true:
rule_collector.set_entrance_rule(LogicEntrance.purchase_wizard_blueprints, logic.quest.has_magic_ink())
rule_collector.set_entrance_rule(LogicEntrance.search_garbage_cans, logic.time.has_lived_months(MAX_MONTHS / 2))
rule_collector.set_entrance_rule(Entrance.forest_beach_shortcut, logic.received("Forest To Beach Shortcut"))
rule_collector.set_entrance_rule(Entrance.mountain_jojamart_shortcut, logic.received("Mountain Shortcuts"))
rule_collector.set_entrance_rule(Entrance.mountain_town_shortcut, logic.received("Mountain Shortcuts"))
rule_collector.set_entrance_rule(Entrance.town_tidepools_shortcut, logic.received("Town To Tide Pools Shortcut"))
rule_collector.set_entrance_rule(Entrance.tunnel_backwoods_shortcut, logic.received("Tunnel To Backwoods Shortcut"))
rule_collector.set_entrance_rule(Entrance.mountain_lake_to_outside_adventure_guild_shortcut, logic.received("Mountain Shortcuts"))
rule_collector.set_entrance_rule(Entrance.feed_trash_bear, logic.received("Trash Bear Arrival"))
rule_collector.set_entrance_rule(Entrance.enter_shorts_maze, logic.has(Craftable.staircase))
rule_collector.set_entrance_rule(Entrance.enter_mens_locker_room, logic.wallet.has_mens_locker_key())
rule_collector.set_entrance_rule(Entrance.enter_womens_locker_room, logic.wallet.has_womens_locker_key())
def set_bookseller_rules(logic, rule_collector):
rule_collector.set_entrance_rule(LogicEntrance.buy_books, logic.received(Bookseller.days))
rule_collector.set_entrance_rule(LogicEntrance.buy_experience_books, logic.received(Bookseller.stock_experience_books))
rule_collector.set_entrance_rule(LogicEntrance.buy_permanent_books, logic.received(Bookseller.stock_permanent_books))
rare_books_rule = (logic.received(Bookseller.days, 4) & logic.received(Bookseller.stock_rare_books)) | \
(logic.received(Bookseller.days, 2) & logic.received(Bookseller.stock_rare_books, 2))
rule_collector.set_entrance_rule(LogicEntrance.buy_rare_books, rare_books_rule)
def set_raccoon_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, bundle_rooms: List[BundleRoom], world_options: StardewValleyOptions):
rule_collector.set_entrance_rule(LogicEntrance.has_giant_stump, logic.received(CommunityUpgrade.raccoon))
rule_collector.set_entrance_rule(LogicEntrance.buy_from_raccoon_1, logic.quest.has_raccoon_shop())
rule_collector.set_entrance_rule(LogicEntrance.buy_from_raccoon_2, logic.quest.has_raccoon_shop(2))
rule_collector.set_entrance_rule(LogicEntrance.buy_from_raccoon_3, logic.quest.has_raccoon_shop(3))
rule_collector.set_entrance_rule(LogicEntrance.buy_from_raccoon_4, logic.quest.has_raccoon_shop(4))
rule_collector.set_entrance_rule(LogicEntrance.buy_from_raccoon_5, logic.quest.has_raccoon_shop(5))
rule_collector.set_entrance_rule(LogicEntrance.buy_from_raccoon_6, logic.quest.has_raccoon_shop(6))
raccoon_room = next(iter(room for room in bundle_rooms if room.name == CCRoom.raccoon_requests))
extra_raccoons = 1 if world_options.quest_locations.has_story_quests() else 0
for bundle in raccoon_room.bundles:
num = int(bundle.name[-1])
bundle_rules = logic.received(CommunityUpgrade.raccoon, num + extra_raccoons) & logic.bundle.can_complete_bundle(bundle)
rule_collector.set_entrance_rule("Can Complete " + bundle.name, bundle_rules)
def set_dangerous_mine_rules(logic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.is_enabled(ginger_island_content_pack):
return
dangerous_mine_rule = logic.mine.has_mine_elevator_to_floor(120) & logic.region.can_reach(Region.qi_walnut_room)
rule_collector.set_entrance_rule(Entrance.dig_to_dangerous_mines_20, dangerous_mine_rule)
rule_collector.set_entrance_rule(Entrance.dig_to_dangerous_mines_60, dangerous_mine_rule)
rule_collector.set_entrance_rule(Entrance.dig_to_dangerous_mines_100, dangerous_mine_rule)
rule_collector.set_entrance_rule(Entrance.enter_dangerous_skull_cavern,
(logic.received(Wallet.skull_key) & logic.region.can_reach(Region.qi_walnut_room)))
def set_farm_buildings_entrance_rules(logic, rule_collector: StardewRuleCollector):
rule_collector.set_entrance_rule(Entrance.downstairs_to_cellar, logic.building.has_building(Building.cellar))
rule_collector.set_entrance_rule(Entrance.use_desert_obelisk, logic.can_use_obelisk(Transportation.desert_obelisk))
rule_collector.set_entrance_rule(Entrance.enter_greenhouse, logic.received("Greenhouse"))
rule_collector.set_entrance_rule(Entrance.enter_coop, logic.building.has_building(Building.coop))
rule_collector.set_entrance_rule(Entrance.enter_barn, logic.building.has_building(Building.barn))
rule_collector.set_entrance_rule(Entrance.enter_shed, logic.building.has_building(Building.shed))
rule_collector.set_entrance_rule(Entrance.enter_slime_hutch, logic.building.has_building(Building.slime_hutch))
def set_bedroom_entrance_rules(logic, rule_collector: StardewRuleCollector, content: StardewContent):
rule_collector.set_entrance_rule(Entrance.enter_harvey_room, logic.relationship.has_hearts(NPC.harvey, 2))
rule_collector.set_entrance_rule(Entrance.mountain_to_maru_room, logic.relationship.has_hearts(NPC.maru, 2))
rule_collector.set_entrance_rule(Entrance.enter_sebastian_room, (logic.relationship.has_hearts(NPC.sebastian, 2) | logic.mod.magic.can_blink()))
rule_collector.set_entrance_rule(Entrance.forest_to_leah_cottage, logic.relationship.has_hearts(NPC.leah, 2))
rule_collector.set_entrance_rule(Entrance.enter_elliott_house, logic.relationship.has_hearts(NPC.elliott, 2))
rule_collector.set_entrance_rule(Entrance.enter_sunroom, logic.relationship.has_hearts(NPC.caroline, 2))
rule_collector.set_entrance_rule(Entrance.enter_wizard_basement, logic.relationship.has_hearts(NPC.wizard, 4))
rule_collector.set_entrance_rule(Entrance.enter_lewis_bedroom, logic.relationship.has_hearts(NPC.lewis, 2))
if content.is_enabled(ModNames.alec):
rule_collector.set_entrance_rule(AlecEntrance.petshop_to_bedroom, (logic.relationship.has_hearts(ModNPC.alec, 2) | logic.mod.magic.can_blink()))
if content.is_enabled(ModNames.lacey):
rule_collector.set_entrance_rule(LaceyEntrance.forest_to_hat_house, logic.relationship.has_hearts(ModNPC.lacey, 2))
def set_mines_floor_entrance_rules(logic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
for floor in range(5, 120 + 5, 5):
elevator_difference = 10
if CustomLogicOptionName.very_deep_mining in world_options.custom_logic:
elevator_difference = 40
elif CustomLogicOptionName.deep_mining in world_options.custom_logic:
elevator_difference = 20
rule = logic.mine.has_mine_elevator_to_floor(floor - elevator_difference)
if floor == 5 or floor == 45 or floor == 85:
rule = rule & logic.mine.can_progress_in_the_mines_from_floor(floor)
rule_collector.set_entrance_rule(dig_to_mines_floor(floor), rule)
def set_skull_cavern_floor_entrance_rules(logic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
rule_collector.set_entrance_rule(Entrance.mine_in_skull_cavern, logic.mine.can_progress_in_the_mines_from_floor(120))
for floor in range(25, 200 + 25, 25):
elevator_difference = 25
if CustomLogicOptionName.very_deep_mining in world_options.custom_logic:
elevator_difference = 50
elif CustomLogicOptionName.deep_mining in world_options.custom_logic:
elevator_difference = 100
rule = logic.mod.elevator.has_skull_cavern_elevator_to_floor(max(0, floor - elevator_difference))
if floor == 25 or floor == 75 or floor == 125:
rule = rule & logic.mine.can_progress_in_the_skull_cavern_from_floor(floor)
rule_collector.set_entrance_rule(dig_to_skull_floor(floor), rule)
def set_skill_entrance_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
rule_collector.set_entrance_rule(LogicEntrance.grow_spring_crops, logic.farming.has_farming_tools_and_water & logic.season.has_spring)
rule_collector.set_entrance_rule(LogicEntrance.grow_summer_crops, logic.farming.has_farming_tools_and_water & logic.season.has_summer)
rule_collector.set_entrance_rule(LogicEntrance.grow_fall_crops, logic.farming.has_farming_tools_and_water & logic.season.has_fall)
rule_collector.set_entrance_rule(LogicEntrance.grow_winter_crops, logic.farming.has_farming_tools_and_water & logic.season.has_winter)
rule_collector.set_entrance_rule(LogicEntrance.grow_spring_crops_in_greenhouse, logic.farming.has_farming_and_watering_tools)
rule_collector.set_entrance_rule(LogicEntrance.grow_summer_crops_in_greenhouse, logic.farming.has_farming_and_watering_tools)
rule_collector.set_entrance_rule(LogicEntrance.grow_fall_crops_in_greenhouse, logic.farming.has_farming_and_watering_tools)
rule_collector.set_entrance_rule(LogicEntrance.grow_winter_crops_in_greenhouse, logic.farming.has_farming_and_watering_tools)
rule_collector.set_entrance_rule(LogicEntrance.grow_indoor_crops_in_greenhouse, logic.farming.has_farming_and_watering_tools)
rule_collector.set_island_entrance_rule(LogicEntrance.grow_spring_crops_on_island, logic.farming.has_farming_tools_and_water)
rule_collector.set_island_entrance_rule(LogicEntrance.grow_summer_crops_on_island, logic.farming.has_farming_tools_and_water)
rule_collector.set_island_entrance_rule(LogicEntrance.grow_fall_crops_on_island, logic.farming.has_farming_tools_and_water)
rule_collector.set_island_entrance_rule(LogicEntrance.grow_winter_crops_on_island, logic.farming.has_farming_tools_and_water)
rule_collector.set_island_entrance_rule(LogicEntrance.grow_indoor_crops_on_island, logic.farming.has_farming_tools_and_water)
rule_collector.set_entrance_rule(LogicEntrance.grow_summer_fall_crops_in_summer, true_)
rule_collector.set_entrance_rule(LogicEntrance.grow_summer_fall_crops_in_fall, true_)
rule_collector.set_entrance_rule(LogicEntrance.fishing, logic.fishing.can_fish_anywhere())
def set_blacksmith_entrance_rules(logic, rule_collector: StardewRuleCollector):
set_blacksmith_upgrade_rule(logic, rule_collector, LogicEntrance.blacksmith_copper, MetalBar.copper, ToolMaterial.copper)
set_blacksmith_upgrade_rule(logic, rule_collector, LogicEntrance.blacksmith_iron, MetalBar.iron, ToolMaterial.iron)
set_blacksmith_upgrade_rule(logic, rule_collector, LogicEntrance.blacksmith_gold, MetalBar.gold, ToolMaterial.gold)
set_blacksmith_upgrade_rule(logic, rule_collector, LogicEntrance.blacksmith_iridium, MetalBar.iridium, ToolMaterial.iridium)
def set_blacksmith_upgrade_rule(logic, rule_collector: StardewRuleCollector, entrance_name: str, item_name: str, tool_material: str):
upgrade_rule = logic.has(item_name) & logic.money.can_spend(tool_upgrade_prices[tool_material])
rule_collector.set_entrance_rule(entrance_name, upgrade_rule)
def set_festival_entrance_rules(logic, rule_collector: StardewRuleCollector):
rule_collector.set_entrance_rule(LogicEntrance.attend_egg_festival, logic.season.has(Season.spring))
rule_collector.set_entrance_rule(LogicEntrance.attend_desert_festival, logic.season.has(Season.spring) & logic.received(Transportation.bus_repair))
rule_collector.set_entrance_rule(LogicEntrance.attend_flower_dance, logic.season.has(Season.spring))
rule_collector.set_entrance_rule(LogicEntrance.attend_luau, logic.season.has(Season.summer))
rule_collector.set_entrance_rule(LogicEntrance.attend_trout_derby,
logic.season.has(Season.summer) & logic.fishing.can_use_specific_bait(Fish.rainbow_trout))
rule_collector.set_entrance_rule(LogicEntrance.attend_moonlight_jellies, logic.season.has(Season.summer))
rule_collector.set_entrance_rule(LogicEntrance.attend_fair, logic.season.has(Season.fall))
rule_collector.set_entrance_rule(LogicEntrance.attend_spirit_eve, logic.season.has(Season.fall))
rule_collector.set_entrance_rule(LogicEntrance.attend_festival_of_ice, logic.season.has(Season.winter))
rule_collector.set_entrance_rule(LogicEntrance.attend_squidfest, logic.season.has(Season.winter) & logic.fishing.can_use_specific_bait(Fish.squid))
rule_collector.set_entrance_rule(LogicEntrance.attend_night_market, logic.season.has(Season.winter))
rule_collector.set_entrance_rule(LogicEntrance.attend_winter_star, logic.season.has(Season.winter))
def set_ginger_island_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions, content: StardewContent):
set_island_entrances_rules(logic, rule_collector, content)
if not content.is_enabled(ginger_island_content_pack):
return
set_boat_repair_rules(logic, rule_collector)
set_island_parrot_rules(logic, rule_collector)
rule_collector.set_location_rule("Open Professor Snail Cave", logic.has(Bomb.cherry_bomb))
rule_collector.set_location_rule("Complete Island Field Office", logic.walnut.can_complete_field_office())
set_walnut_rules(logic, rule_collector, world_options)
def set_boat_repair_rules(logic: StardewLogic, rule_collector: StardewRuleCollector):
rule_collector.set_location_rule("Repair Boat Hull", logic.has(Material.hardwood))
rule_collector.set_location_rule("Repair Boat Anchor", logic.has(MetalBar.iridium))
rule_collector.set_location_rule("Repair Ticket Machine", logic.has(ArtisanGood.battery_pack))
def set_island_entrances_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
boat_repaired = logic.received(Transportation.boat_repair)
dig_site_rule = logic.received("Dig Site Bridge")
entrance_rules = {
Entrance.use_island_obelisk: logic.can_use_obelisk(Transportation.island_obelisk),
Entrance.use_farm_obelisk: logic.can_use_obelisk(Transportation.farm_obelisk),
Entrance.fish_shop_to_boat_tunnel: boat_repaired,
Entrance.boat_to_ginger_island: boat_repaired & logic.money.can_spend(1000),
Entrance.island_south_to_west: logic.received("Island West Turtle"),
Entrance.island_south_to_north: logic.received("Island North Turtle"),
Entrance.island_west_to_islandfarmhouse: logic.received("Island Farmhouse"),
Entrance.island_west_to_gourmand_cave: logic.received("Island Farmhouse"),
Entrance.island_north_to_dig_site: dig_site_rule | logic.ability.can_chair_skip(),
Entrance.dig_site_to_professor_snail_cave: logic.received("Open Professor Snail Cave"),
Entrance.talk_to_island_trader: logic.received("Island Trader"),
Entrance.island_south_to_southeast: logic.received("Island Resort"),
Entrance.use_island_resort: logic.received("Island Resort"),
Entrance.island_west_to_qi_walnut_room: logic.received("Qi Walnut Room"),
Entrance.island_north_to_volcano: logic.tool.can_water() | logic.received("Volcano Bridge") | logic.mod.magic.can_blink(),
Entrance.volcano_to_secret_beach: logic.tool.can_water(3),
Entrance.climb_to_volcano_5: logic.ability.can_mine_perfectly() & logic.tool.can_water(2),
Entrance.talk_to_volcano_dwarf: logic.wallet.can_speak_dwarf(),
Entrance.climb_to_volcano_10: logic.ability.can_mine_perfectly() & logic.tool.can_water(2),
Entrance.mountain_to_leo_treehouse: logic.received("Treehouse"),
}
parrots = [Entrance.parrot_express_docks_to_volcano, Entrance.parrot_express_jungle_to_volcano,
Entrance.parrot_express_dig_site_to_volcano, Entrance.parrot_express_docks_to_dig_site,
Entrance.parrot_express_jungle_to_dig_site, Entrance.parrot_express_volcano_to_dig_site,
Entrance.parrot_express_docks_to_jungle, Entrance.parrot_express_dig_site_to_jungle,
Entrance.parrot_express_volcano_to_jungle, Entrance.parrot_express_jungle_to_docks,
Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_volcano_to_docks]
parrot_express_rule = logic.received(Transportation.parrot_express)
parrot_express_to_dig_site_rule = dig_site_rule & parrot_express_rule
for parrot in parrots:
if "Dig Site" in parrot:
entrance_rules[parrot] = parrot_express_to_dig_site_rule
else:
entrance_rules[parrot] = parrot_express_rule
rule_collector.set_many_island_entrances_rules(entrance_rules)
def set_island_parrot_rules(logic: StardewLogic, rule_collector: StardewRuleCollector):
# Logic rules require more walnuts than in reality, to allow the player to spend them "wrong"
has_walnut = logic.walnut.has_walnut(5)
has_5_walnut = logic.walnut.has_walnut(15)
has_10_walnut = logic.walnut.has_walnut(40)
has_20_walnut = logic.walnut.has_walnut(60)
rule_collector.set_location_rule("Leo's Parrot", has_walnut)
rule_collector.set_location_rule("Island West Turtle", has_10_walnut & logic.received("Island North Turtle"))
rule_collector.set_location_rule("Island Farmhouse", has_20_walnut)
rule_collector.set_location_rule("Island Mailbox", has_5_walnut & logic.received("Island Farmhouse"))
rule_collector.set_location_rule(Transportation.farm_obelisk, has_20_walnut & logic.received("Island Mailbox"))
rule_collector.set_location_rule("Dig Site Bridge", has_10_walnut & logic.received("Island West Turtle"))
rule_collector.set_location_rule("Island Trader", has_10_walnut & logic.received("Island Farmhouse"))
rule_collector.set_location_rule("Volcano Bridge",
has_5_walnut & logic.received("Island West Turtle") & logic.region.can_reach(Region.volcano_floor_10))
rule_collector.set_location_rule("Volcano Exit Shortcut", has_5_walnut & logic.received("Island West Turtle"))
rule_collector.set_location_rule("Island Resort", has_20_walnut & logic.received("Island Farmhouse"))
rule_collector.set_location_rule(Transportation.parrot_express, has_10_walnut)
def set_walnut_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
if world_options.walnutsanity == Walnutsanity.preset_none:
return
set_walnut_puzzle_rules(logic, rule_collector, world_options)
set_walnut_bushes_rules(logic, rule_collector, world_options)
set_walnut_dig_spot_rules(logic, rule_collector, world_options)
set_walnut_repeatable_rules(logic, rule_collector, world_options)
def set_walnut_puzzle_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options):
if WalnutsanityOptionName.puzzles not in world_options.walnutsanity:
return
rule_collector.set_location_rule("Walnutsanity: Open Golden Coconut", logic.has(Geode.golden_coconut))
rule_collector.set_location_rule("Walnutsanity: Banana Altar", logic.has(Fruit.banana))
rule_collector.set_location_rule("Walnutsanity: Leo's Tree", logic.tool.has_tool(Tool.axe))
rule_collector.set_location_rule("Walnutsanity: Gem Birds Shrine",
logic.has_all(Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz)
& logic.region.can_reach_all(Region.island_north, Region.island_west, Region.island_east, Region.island_south))
rule_collector.set_location_rule("Walnutsanity: Gourmand Frog Melon", logic.has(Fruit.melon) & logic.region.can_reach(Region.island_west))
rule_collector.set_location_rule("Walnutsanity: Gourmand Frog Wheat",
logic.has(Vegetable.wheat) & logic.region.can_reach(Region.island_west)
& logic.region.can_reach_location("Walnutsanity: Gourmand Frog Melon"))
rule_collector.set_location_rule("Walnutsanity: Gourmand Frog Garlic",
logic.has(Vegetable.garlic) & logic.region.can_reach(Region.island_west)
& logic.region.can_reach_location("Walnutsanity: Gourmand Frog Wheat"))
rule_collector.set_location_rule("Walnutsanity: Whack A Mole", logic.tool.has_tool(Tool.watering_can, ToolMaterial.iridium))
rule_collector.set_location_rule("Walnutsanity: Complete Large Animal Collection", logic.walnut.can_complete_large_animal_collection())
rule_collector.set_location_rule("Walnutsanity: Complete Snake Collection", logic.walnut.can_complete_snake_collection())
rule_collector.set_location_rule("Walnutsanity: Complete Mummified Frog Collection", logic.walnut.can_complete_frog_collection())
rule_collector.set_location_rule("Walnutsanity: Complete Mummified Bat Collection", logic.walnut.can_complete_bat_collection())
rule_collector.set_location_rule("Walnutsanity: Purple Flowers Island Survey", logic.walnut.can_start_field_office)
rule_collector.set_location_rule("Walnutsanity: Purple Starfish Island Survey", logic.walnut.can_start_field_office)
rule_collector.set_location_rule("Walnutsanity: Protruding Tree Walnut", logic.combat.has_slingshot)
rule_collector.set_location_rule("Walnutsanity: Starfish Tide Pool", logic.tool.has_fishing_rod())
rule_collector.set_location_rule("Walnutsanity: Mermaid Song", logic.has(Furniture.flute_block))
def set_walnut_bushes_rules(logic, rule_collector: StardewRuleCollector, world_options):
if WalnutsanityOptionName.bushes not in world_options.walnutsanity:
return
# I don't think any of the bushes require something special, but that might change with ER
return
def set_walnut_dig_spot_rules(logic, rule_collector: StardewRuleCollector, world_options):
if WalnutsanityOptionName.dig_spots not in world_options.walnutsanity:
return
for dig_spot_walnut in locations.locations_by_tag[LocationTags.WALNUTSANITY_DIG]:
rule = logic.tool.has_tool(Tool.hoe)
if "Journal Scrap" in dig_spot_walnut.name:
rule = rule & logic.has(Forageable.journal_scrap)
if "Starfish Diamond" in dig_spot_walnut.name:
rule = rule & logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron)
rule_collector.set_location_rule(dig_spot_walnut.name, rule)
def set_walnut_repeatable_rules(logic, rule_collector: StardewRuleCollector, world_options):
if WalnutsanityOptionName.repeatables not in world_options.walnutsanity:
return
for i in range(1, 6):
rule_collector.set_location_rule(f"Walnutsanity: Fishing Walnut {i}", logic.tool.has_fishing_rod())
rule_collector.set_location_rule(f"Walnutsanity: Harvesting Walnut {i}", logic.skill.can_get_farming_xp)
rule_collector.set_location_rule(f"Walnutsanity: Mussel Node Walnut {i}", logic.tool.has_tool(Tool.pickaxe))
rule_collector.set_location_rule(f"Walnutsanity: Volcano Rocks Walnut {i}", logic.tool.has_tool(Tool.pickaxe))
rule_collector.set_location_rule(f"Walnutsanity: Volcano Monsters Walnut {i}", logic.combat.has_galaxy_weapon)
rule_collector.set_location_rule(f"Walnutsanity: Volcano Crates Walnut {i}", logic.combat.has_any_weapon)
rule_collector.set_location_rule(f"Walnutsanity: Tiger Slime Walnut", logic.monster.can_kill(Monster.tiger_slime))
def set_cropsanity_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_content: StardewContent):
if not world_content.features.cropsanity.is_enabled:
return
for item in world_content.find_tagged_items(ItemTag.CROPSANITY):
location = world_content.features.cropsanity.to_location_name(item.name)
harvest_sources = (source for source in item.sources if isinstance(source, (HarvestFruitTreeSource, HarvestCropSource)))
rule_collector.set_location_rule(location, logic.source.has_access_to_any(harvest_sources))
def set_story_quests_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
if world_options.quest_locations.has_no_story_quests():
return
for quest_location in locations.locations_by_tag[LocationTags.STORY_QUEST]:
quest_location_name = quest_location.name
if quest_location_name in all_location_names:
quest_prefix = "Quest: "
quest_name = quest_location_name[len(quest_prefix):]
rule_collector.set_location_rule(quest_location_name, logic.registry.quest_rules[quest_name])
def set_special_order_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector,
world_options: StardewValleyOptions, content: StardewContent):
if world_options.special_order_locations & SpecialOrderLocations.option_board:
board_rule = logic.received("Special Order Board") & logic.time.has_lived_months(4)
for board_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]:
if board_order.name in all_location_names:
order_rule = board_rule & logic.registry.special_order_rules[board_order.name]
rule_collector.set_location_rule(board_order.name, order_rule)
if content.is_enabled(qi_board_content_pack):
qi_rule = logic.region.can_reach(Region.qi_walnut_room) & logic.time.has_lived_months(8)
for qi_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_QI]:
if qi_order.name in all_location_names:
order_rule = qi_rule & logic.registry.special_order_rules[qi_order.name]
rule_collector.set_location_rule(qi_order.name, order_rule)
help_wanted_prefix = "Help Wanted:"
item_delivery = "Item Delivery"
gathering = "Gathering"
fishing = "Fishing"
slay_monsters = "Slay Monsters"
def set_help_wanted_quests_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
if world_options.quest_locations.has_no_story_quests():
return
help_wanted_number = world_options.quest_locations.value
for i in range(0, help_wanted_number):
set_number = i // 7
month_rule = logic.time.has_lived_months(set_number)
quest_number = set_number + 1
quest_number_in_set = i % 7
if quest_number_in_set < 4:
quest_number = set_number * 4 + quest_number_in_set + 1
set_help_wanted_delivery_rule(logic, rule_collector, month_rule, quest_number)
elif quest_number_in_set == 4:
set_help_wanted_fishing_rule(logic, rule_collector, month_rule, quest_number)
elif quest_number_in_set == 5:
set_help_wanted_slay_monsters_rule(logic, rule_collector, month_rule, quest_number)
elif quest_number_in_set == 6:
set_help_wanted_gathering_rule(logic, rule_collector, month_rule, quest_number)
def set_help_wanted_delivery_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, month_rule, quest_number):
location_name = f"{help_wanted_prefix} {item_delivery} {quest_number}"
rule_collector.set_location_rule(location_name, logic.quest.can_do_item_delivery_quest() & month_rule)
def set_help_wanted_gathering_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, month_rule, quest_number):
location_name = f"{help_wanted_prefix} {gathering} {quest_number}"
rule_collector.set_location_rule(location_name, logic.quest.can_do_gathering_quest() & month_rule)
def set_help_wanted_fishing_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, month_rule, quest_number):
location_name = f"{help_wanted_prefix} {fishing} {quest_number}"
rule_collector.set_location_rule(location_name, logic.quest.can_do_fishing_quest() & month_rule)
def set_help_wanted_slay_monsters_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, month_rule, quest_number):
location_name = f"{help_wanted_prefix} {slay_monsters} {quest_number}"
rule_collector.set_location_rule(location_name, logic.quest.can_do_slaying_quest() & month_rule)
def set_fishsanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector):
fish_prefix = "Fishsanity: "
for fish_location in locations.locations_by_tag[LocationTags.FISHSANITY]:
if fish_location.name in all_location_names:
fish_name = fish_location.name[len(fish_prefix):]
rule_collector.set_location_rule(fish_location.name, logic.has(fish_name))
def set_museumsanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
museum_prefix = "Museumsanity: "
if world_options.museumsanity == Museumsanity.option_milestones:
for museum_milestone in locations.locations_by_tag[LocationTags.MUSEUM_MILESTONES]:
set_museum_milestone_rule(logic, rule_collector, museum_milestone, museum_prefix)
elif world_options.museumsanity != Museumsanity.option_none:
set_museum_individual_donations_rules(all_location_names, logic, rule_collector, museum_prefix)
def set_museum_individual_donations_rules(all_location_names, logic: StardewLogic, rule_collector: StardewRuleCollector, museum_prefix: str):
all_donations = sorted(locations.locations_by_tag[LocationTags.MUSEUM_DONATIONS],
key=lambda x: all_museum_items_by_name[x.name[len(museum_prefix):]].difficulty, reverse=True)
counter = 0
number_donations = len(all_donations)
for museum_location in all_donations:
if museum_location.name in all_location_names:
donation_name = museum_location.name[len(museum_prefix):]
required_detectors = counter * 3 // number_donations
rule = logic.museum.can_find_museum_item(all_museum_items_by_name[donation_name]) & logic.received(Wallet.metal_detector, required_detectors)
rule_collector.set_location_rule(museum_location.name, rule)
counter += 1
def set_museum_milestone_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, museum_milestone, museum_prefix: str):
milestone_name = museum_milestone.name[len(museum_prefix):]
donations_suffix = " Donations"
minerals_suffix = " Minerals"
artifacts_suffix = " Artifacts"
metal_detector = Wallet.metal_detector
rule = None
if milestone_name.endswith(donations_suffix):
rule = get_museum_item_count_rule(logic, donations_suffix, milestone_name, all_museum_items, logic.museum.can_find_museum_items)
elif milestone_name.endswith(minerals_suffix):
rule = get_museum_item_count_rule(logic, minerals_suffix, milestone_name, all_museum_minerals, logic.museum.can_find_museum_minerals)
elif milestone_name.endswith(artifacts_suffix):
rule = get_museum_item_count_rule(logic, artifacts_suffix, milestone_name, all_museum_artifacts, logic.museum.can_find_museum_artifacts)
elif milestone_name == "Dwarf Scrolls":
rule = And(*(logic.museum.can_find_museum_item(item) for item in dwarf_scrolls)) & logic.received(metal_detector, 2)
elif milestone_name == "Skeleton Front":
rule = And(*(logic.museum.can_find_museum_item(item) for item in skeleton_front)) & logic.received(metal_detector, 2)
elif milestone_name == "Skeleton Middle":
rule = And(*(logic.museum.can_find_museum_item(item) for item in skeleton_middle)) & logic.received(metal_detector, 2)
elif milestone_name == "Skeleton Back":
rule = And(*(logic.museum.can_find_museum_item(item) for item in skeleton_back)) & logic.received(metal_detector, 2)
elif milestone_name == "Ancient Seed":
rule = logic.museum.can_find_museum_item(Artifact.ancient_seed) & logic.received(metal_detector, 2)
if rule is None:
return
rule_collector.set_location_rule(museum_milestone.name, rule)
def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items, donation_func):
metal_detector = Wallet.metal_detector
num = int(milestone_name[:milestone_name.index(suffix)])
required_detectors = (num - 1) * 3 // len(accepted_items)
rule = donation_func(num) & logic.received(metal_detector, required_detectors)
return rule
def set_backpack_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions, content: StardewContent):
if world_options.backpack_progression == BackpackProgression.option_vanilla:
return
num_per_tier = world_options.backpack_size.count_per_tier()
start_without_backpack = bool(StartWithoutOptionName.backpack in world_options.start_without)
backpack_tier_names = Backpack.get_purchasable_tiers(content.is_enabled(ModNames.big_backpack), start_without_backpack)
previous_backpacks = 0
for tier in backpack_tier_names:
for i in range(1, num_per_tier + 1):
loc_name = f"{tier} {i}"
if num_per_tier == 1:
loc_name = tier
price = Backpack.prices_per_tier[tier]
rule_collector.set_location_rule(loc_name, logic.money.can_spend(price) & logic.received("Progressive Backpack", previous_backpacks))
previous_backpacks += 1
def set_festival_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector):
festival_locations = []
festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL])
festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL_HARD])
for festival in festival_locations:
if festival.name in all_location_names:
rule_collector.set_location_rule(festival.name, logic.registry.festival_rules[festival.name])
monster_eradication_prefix = "Monster Eradication: "
def set_monstersanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
monstersanity_option = world_options.monstersanity
if monstersanity_option == Monstersanity.option_none:
return
if monstersanity_option == Monstersanity.option_one_per_monster or monstersanity_option == Monstersanity.option_split_goals:
set_monstersanity_monster_rules(all_location_names, logic, rule_collector, monstersanity_option)
return
if monstersanity_option == Monstersanity.option_progressive_goals:
set_monstersanity_progressive_category_rules(all_location_names, logic, rule_collector)
return
set_monstersanity_category_rules(all_location_names, logic, rule_collector, monstersanity_option)
def set_monstersanity_monster_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, monstersanity_option):
for monster_name in logic.monster.all_monsters_by_name:
location_name = f"{monster_eradication_prefix}{monster_name}"
if location_name not in all_location_names:
continue
if monstersanity_option == Monstersanity.option_split_goals:
rule = logic.monster.can_kill_many(logic.monster.all_monsters_by_name[monster_name])
else:
rule = logic.monster.can_kill(logic.monster.all_monsters_by_name[monster_name])
rule_collector.set_location_rule(location_name, rule)
def set_monstersanity_progressive_category_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector):
for monster_category in logic.monster.all_monsters_by_category:
set_monstersanity_progressive_single_category_rules(all_location_names, logic, rule_collector, monster_category)
def set_monstersanity_progressive_single_category_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector,
monster_category: str):
location_names = [name for name in all_location_names if name.startswith(monster_eradication_prefix) and name.endswith(monster_category)]
if not location_names:
return
location_names = sorted(location_names, key=lambda name: get_monster_eradication_number(name, monster_category))
for i in range(5):
location_name = location_names[i]
set_monstersanity_progressive_category_rule(all_location_names, logic, rule_collector, monster_category, location_name, i)
def set_monstersanity_progressive_category_rule(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector,
monster_category: str, location_name: str, goal_index):
if location_name not in all_location_names:
return
if goal_index < 3:
rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category], goal_index + 1)
else:
rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category], goal_index * 2)
rule_collector.set_location_rule(location_name, rule)
def get_monster_eradication_number(location_name, monster_category) -> int:
number = location_name[len(monster_eradication_prefix):-len(monster_category)]
number = number.strip()
if number.isdigit():
return int(number)
return 1000
def set_monstersanity_category_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, monstersanity_option):
for monster_category in logic.monster.all_monsters_by_category:
location_name = f"{monster_eradication_prefix}{monster_category}"
if location_name not in all_location_names:
continue
if monstersanity_option == Monstersanity.option_one_per_category:
rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category])
else:
rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category], MAX_MONTHS)
rule_collector.set_location_rule(location_name, rule)
def set_shipsanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
shipsanity_option = world_options.shipsanity
if shipsanity_option == Shipsanity.option_none:
return
shipsanity_prefix = "Shipsanity: "
for location in locations.locations_by_tag[LocationTags.SHIPSANITY]:
if location.name not in all_location_names:
continue
item_to_ship = location.name[len(shipsanity_prefix):]
rule_collector.set_location_rule(location.name, logic.shipping.can_ship(item_to_ship))
def set_cooksanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
cooksanity_option = world_options.cooksanity
if cooksanity_option == Cooksanity.option_none:
return
cooksanity_prefix = "Cook "
for location in locations.locations_by_tag[LocationTags.COOKSANITY]:
if location.name not in all_location_names:
continue
recipe_name = location.name[len(cooksanity_prefix):]
recipe = all_cooking_recipes_by_name[recipe_name]
cook_rule = logic.cooking.can_cook(recipe)
rule_collector.set_location_rule(location.name, cook_rule)
def set_chefsanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
chefsanity_option = world_options.chefsanity
if chefsanity_option == Chefsanity.preset_none:
return
chefsanity_suffix = " Recipe"
for location in locations.locations_by_tag[LocationTags.CHEFSANITY]:
if location.name not in all_location_names:
continue
recipe_name = location.name[:-len(chefsanity_suffix)]
recipe = all_cooking_recipes_by_name[recipe_name]
learn_rule = logic.cooking.can_learn_recipe(recipe.source)
rule_collector.set_location_rule(location.name, learn_rule)
def set_craftsanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
craftsanity_option = world_options.craftsanity
if craftsanity_option == Craftsanity.option_none:
return
craft_prefix = "Craft "
craft_suffix = " Recipe"
for location in locations.locations_by_tag[LocationTags.CRAFTSANITY]:
if location.name not in all_location_names:
continue
if location.name.endswith(craft_suffix):
recipe_name = location.name[:-len(craft_suffix)]
recipe = all_crafting_recipes_by_name[recipe_name]
craft_rule = logic.crafting.can_learn_recipe(recipe)
else:
recipe_name = location.name[len(craft_prefix):]
recipe = all_crafting_recipes_by_name[recipe_name]
craft_rule = logic.crafting.can_craft(recipe)
rule_collector.set_location_rule(location.name, craft_rule)
def set_booksanity_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
booksanity = content.features.booksanity
if not booksanity.is_enabled:
return
for book in content.find_tagged_items(ItemTag.BOOK):
if booksanity.is_included(book):
rule_collector.set_location_rule(booksanity.to_location_name(book.name), logic.has(book.name))
for i, book in enumerate(booksanity.get_randomized_lost_books()):
if i <= 0:
continue
rule_collector.set_location_rule(booksanity.to_location_name(book), logic.received(booksanity.progressive_lost_book, i))
def set_traveling_merchant_day_entrance_rules(logic: StardewLogic, rule_collector: StardewRuleCollector):
for day in Weekday.all_days:
item_for_day = f"Traveling Merchant: {day}"
entrance_name = f"Buy from Traveling Merchant {day}"
rule_collector.set_entrance_rule(entrance_name, logic.received(item_for_day))
def set_arcade_machine_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
play_junimo_kart_rule = logic.received(Wallet.skull_key)
if world_options.arcade_machine_locations != ArcadeMachineLocations.option_full_shuffling:
rule_collector.set_entrance_rule(Entrance.play_junimo_kart, play_junimo_kart_rule)
return
rule_collector.set_entrance_rule(Entrance.play_junimo_kart, play_junimo_kart_rule & logic.has("Junimo Kart Small Buff"))
rule_collector.set_entrance_rule(Entrance.reach_junimo_kart_2, logic.has("Junimo Kart Medium Buff"))
rule_collector.set_entrance_rule(Entrance.reach_junimo_kart_3, logic.has("Junimo Kart Big Buff"))
rule_collector.set_entrance_rule(Entrance.reach_junimo_kart_4, logic.has("Junimo Kart Max Buff"))
rule_collector.set_entrance_rule(Entrance.play_journey_of_the_prairie_king, logic.has("JotPK Small Buff"))
rule_collector.set_entrance_rule(Entrance.reach_jotpk_world_2, logic.has("JotPK Medium Buff"))
rule_collector.set_entrance_rule(Entrance.reach_jotpk_world_3, logic.has("JotPK Big Buff"))
rule_collector.set_location_rule("Journey of the Prairie King Victory", logic.has("JotPK Max Buff"))
def set_movie_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions, content: StardewContent):
moviesanity = world_options.moviesanity.value
if moviesanity <= Moviesanity.option_none:
return
if moviesanity >= Moviesanity.option_all_movies:
watch_prefix = "Watch "
for movie_location in locations.locations_by_tag[LocationTags.MOVIE]:
movie_name = movie_location.name[len(watch_prefix):]
if moviesanity == Moviesanity.option_all_movies:
rule = logic.movie.can_watch_movie(movie_name)
elif moviesanity == Moviesanity.option_all_movies_loved or moviesanity == Moviesanity.option_all_movies_and_all_snacks:
rule = logic.movie.can_watch_movie_with_loving_npc(movie_name)
else:
rule = logic.movie.can_watch_movie_with_loving_npc_and_snack(movie_name)
rule_collector.set_location_rule(movie_location.name, rule)
if moviesanity >= Moviesanity.option_all_movies_and_all_snacks:
snack_prefix = "Share "
for snack_location in locations.locations_by_tag[LocationTags.MOVIE_SNACK]:
snack_name = snack_location.name[len(snack_prefix):]
if moviesanity == Moviesanity.option_all_movies_and_all_loved_snacks:
rule = logic.movie.can_buy_snack_for_someone_who_loves_it(snack_name)
else:
rule = logic.movie.can_buy_snack(snack_name)
rule_collector.set_location_rule(snack_location.name, rule)
def set_secrets_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions, content: StardewContent):
if not world_options.secretsanity:
return
if SecretsanityOptionName.easy in world_options.secretsanity:
rule_collector.set_location_rule("Secret: Old Master Cannoli", logic.has(Fruit.sweet_gem_berry))
rule_collector.set_location_rule("Secret: Pot Of Gold", logic.season.has(Season.spring))
rule_collector.set_location_rule("Secret: Poison The Governor", logic.has(SpecialItem.lucky_purple_shorts))
rule_collector.set_location_rule("Secret: Grange Display Bribe", logic.has(SpecialItem.lucky_purple_shorts))
rule_collector.set_location_rule("Secret: Purple Lettuce", logic.has(SpecialItem.lucky_purple_shorts))
rule_collector.set_location_rule("Secret: Make Marnie Laugh", logic.has(SpecialItem.trimmed_purple_shorts) & logic.relationship.can_meet(NPC.marnie))
rule_collector.set_location_rule("Secret: Jumpscare Lewis", logic.has(SpecialItem.trimmed_purple_shorts) & logic.relationship.can_meet(NPC.lewis))
rule_collector.set_location_rule("Secret: Confront Marnie", logic.gifts.can_gift_to(NPC.marnie, SpecialItem.lucky_purple_shorts))
rule_collector.set_location_rule("Secret: Lucky Purple Bobber", logic.fishing.can_use_tackle(SpecialItem.lucky_purple_shorts))
rule_collector.set_location_rule("Secret: Something For Santa", logic.season.has(Season.winter) & logic.has_any(AnimalProduct.any_milk, Meal.cookie))
cc_rewards = ["Bridge Repair", "Greenhouse", "Glittering Boulder Removed", "Minecarts Repair", Transportation.bus_repair, "Friendship Bonus (2 <3)"]
rule_collector.set_location_rule("Secret: Jungle Junimo", logic.action.can_speak_junimo() & logic.and_(*[logic.received(reward) for reward in cc_rewards]))
rule_collector.set_location_rule("Secret: ??HMTGF??", logic.has(Fish.super_cucumber))
rule_collector.set_location_rule("Secret: ??Pinky Lemon??", logic.has(ArtisanGood.duck_mayonnaise))
rule_collector.set_location_rule("Secret: ??Foroguemon??", logic.has(Meal.strange_bun) & logic.relationship.has_hearts(NPC.vincent, 2))
rule_collector.set_location_rule("Secret: Galaxies Will Heed Your Cry", logic.wallet.can_speak_dwarf())
rule_collector.set_location_rule("Secret: Summon Bone Serpent", logic.has(ArtifactName.ancient_doll))
rule_collector.set_location_rule("Secret: Meowmere", logic.has(SpecialItem.far_away_stone) & logic.region.can_reach(Region.wizard_basement))
rule_collector.set_location_rule("Secret: A Familiar Tune", logic.relationship.can_meet(NPC.elliott))
rule_collector.set_location_rule("Secret: Flubber Experiment",
logic.relationship.can_get_married() & logic.building.has_building(Building.slime_hutch)
& logic.has_all(Machine.slime_incubator, AnimalProduct.slime_egg_green))
rule_collector.set_location_rule("Secret: Seems Fishy", logic.money.can_spend_at(Region.wizard_basement, 500))
rule_collector.set_location_rule("Secret: What kind of monster is this?", logic.gifts.can_gift_to(NPC.willy, Fish.mutant_carp))
rule_collector.set_location_rule("Secret: My mouth is watering already", logic.gifts.can_gift_to(NPC.abigail, Meal.magic_rock_candy))
rule_collector.set_location_rule("Secret: A gift of lovely perfume", logic.gifts.can_gift_to(NPC.krobus, Consumable.monster_musk))
rule_collector.set_location_rule("Secret: Where exactly does this juice come from?", logic.gifts.can_gift_to(NPC.dwarf, AnimalProduct.cow_milk))
rule_collector.set_location_rule("Secret: Thank the Devs", logic.received("Stardrop") & logic.money.can_spend_at(Region.wizard_basement, 500))
if content.is_enabled(ginger_island_content_pack) and content.is_enabled(qi_board_content_pack):
rule_collector.set_location_rule("Secret: Obtain my precious fruit whenever you like",
logic.special_order.can_complete_special_order(SpecialOrder.qis_crop) &
logic.tool.has_tool(Tool.axe))
if SecretsanityOptionName.fishing in world_options.secretsanity:
if world_options.farm_type == FarmType.option_beach:
rule_collector.set_location_rule("Fishing Secret: 'Boat'", logic.fishing.can_fish_at(Region.farm))
if content.is_enabled(ginger_island_content_pack):
rule_collector.set_location_rule("Fishing Secret: Foliage Print", logic.fishing.can_fish_with_cast_distance(Region.island_north, 5))
rule_collector.set_location_rule("Fishing Secret: Frog Hat", logic.fishing.can_fish_at(Region.gourmand_frog_cave))
rule_collector.set_location_rule("Fishing Secret: Gourmand Statue", logic.fishing.can_fish_at(Region.pirate_cove))
rule_collector.set_location_rule("Fishing Secret: 'Physics 101'", logic.fishing.can_fish_at(Region.volcano_floor_10))
rule_collector.set_location_rule("Fishing Secret: Lifesaver", logic.fishing.can_fish_at(Region.boat_tunnel))
rule_collector.set_location_rule("Fishing Secret: Squirrel Figurine", logic.fishing.can_fish_at(Region.volcano_secret_beach))
rule_collector.set_location_rule("Fishing Secret: Decorative Trash Can", logic.fishing.can_fish_at(Region.town))
rule_collector.set_location_rule("Fishing Secret: Iridium Krobus", logic.fishing.can_fish_with_cast_distance(Region.forest, 7))
rule_collector.set_location_rule("Fishing Secret: Pyramid Decal", logic.fishing.can_fish_with_cast_distance(Region.desert, 4))
rule_collector.set_location_rule("Fishing Secret: 'Vista'", logic.fishing.can_fish_at(Region.railroad) & logic.season.has_any_not_winter())
rule_collector.set_location_rule("Fishing Secret: Wall Basket", logic.fishing.can_fish_at(Region.secret_woods))
if SecretsanityOptionName.difficult in world_options.secretsanity:
rule_collector.set_location_rule("Secret: Free The Forsaken Souls", logic.action.can_watch(Channel.sinister_signal))
rule_collector.set_location_rule("Secret: Annoy the Moon Man", logic.shipping.can_use_shipping_bin & logic.time.has_lived_months(6))
rule_collector.set_location_rule("Secret: Strange Sighting", logic.region.can_reach_all(Region.bus_stop, Region.town) & logic.time.has_lived_months(6))
rule_collector.set_location_rule("Secret: Sea Monster Sighting", logic.region.can_reach(Region.beach) & logic.time.has_lived_months(2))
rule_collector.set_location_rule("Secret: ...Bigfoot?",
logic.region.can_reach_all(Region.forest, Region.town, Region.secret_woods) & logic.time.has_lived_months(4))
rule_collector.set_location_rule("Secret: 'Me me me me me me me me me me me me me me me me'",
logic.region.can_reach(Region.railroad) & logic.tool.has_scythe())
rule_collector.set_location_rule("Secret: Secret Iridium Stackmaster Trophy", logic.grind.can_grind_item(10000, Material.wood))
if SecretsanityOptionName.secret_notes in world_options.secretsanity:
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_1)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_2)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_3)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_4)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_5)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_6)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_7)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_8)
set_secret_note_gift_rule(logic, rule_collector, SecretNote.note_9)
rule_collector.set_location_rule(SecretNote.note_10, logic.registry.quest_rules[Quest.cryptic_note])
rule_collector.set_location_rule(SecretNote.note_11, logic.relationship.can_meet_all(NPC.marnie, NPC.jas, ))
rule_collector.set_location_rule(SecretNote.note_12, logic.region.can_reach(Region.town))
rule_collector.set_location_rule(SecretNote.note_13, logic.time.has_lived_months(1) & logic.region.can_reach(Region.town))
rule_collector.set_location_rule(SecretNote.note_14, logic.region.can_reach(Region.town) & logic.season.has(Season.spring))
rule_collector.set_location_rule(SecretNote.note_15, logic.region.can_reach(LogicRegion.night_market))
rule_collector.set_location_rule(SecretNote.note_16, logic.tool.can_use_tool_at(Tool.hoe, ToolMaterial.basic, Region.railroad))
rule_collector.set_location_rule(SecretNote.note_17, logic.tool.can_use_tool_at(Tool.hoe, ToolMaterial.basic, Region.town))
rule_collector.set_location_rule(SecretNote.note_18, logic.tool.can_use_tool_at(Tool.hoe, ToolMaterial.basic, Region.desert))
rule_collector.set_location_rule(SecretNote.note_19_part_1, logic.region.can_reach(Region.town))
rule_collector.set_location_rule(SecretNote.note_19_part_2, logic.region.can_reach(Region.town) & logic.has(SpecialItem.solid_gold_lewis))
rule_collector.set_location_rule(SecretNote.note_20, logic.region.can_reach(Region.town) & logic.has(AnimalProduct.rabbit_foot))
rule_collector.set_location_rule(SecretNote.note_21, logic.region.can_reach(Region.town))
rule_collector.set_location_rule(SecretNote.note_22, logic.registry.quest_rules[Quest.the_mysterious_qi])
rule_collector.set_location_rule(SecretNote.note_23, logic.registry.quest_rules[Quest.strange_note])
rule_collector.set_location_rule(SecretNote.note_24,
logic.building.has_wizard_building(WizardBuilding.junimo_hut) & logic.has(Mineral.any_gem)
& logic.season.has_any_not_winter())
rule_collector.set_location_rule(SecretNote.note_25, logic.season.has_any_not_winter() & logic.fishing.can_fish_at(Region.railroad)
& logic.relationship.can_meet_any(NPC.abigail, NPC.caroline, ))
rule_collector.set_location_rule(SecretNote.note_26,
logic.building.has_wizard_building(WizardBuilding.junimo_hut) & logic.has(ArtisanGood.raisins)
& logic.season.has_any_not_winter())
rule_collector.set_location_rule(SecretNote.note_27, logic.region.can_reach(Region.mastery_cave))
def set_secret_note_gift_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, secret_note_location: str) -> None:
rule_collector.set_location_rule(secret_note_location, logic.gifts.can_fulfill(gift_requirements[secret_note_location]))
def set_hatsanity_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
hatsanity = content.features.hatsanity
for hat in content.hats.values():
if not hatsanity.is_included(hat):
continue
rule_collector.set_location_rule(hatsanity.to_location_name(hat), logic.hat.can_wear(hat))
def set_eatsanity_rules(all_location_names: Set[str], logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
if world_options.eatsanity == Eatsanity.preset_none:
return
for eat_location in locations.locations_by_tag[LocationTags.EATSANITY]:
if eat_location.name not in all_location_names:
continue
eat_prefix = "Eat "
drink_prefix = "Drink "
if eat_location.name.startswith(eat_prefix):
item_name = eat_location.name[len(eat_prefix):]
elif eat_location.name.startswith(drink_prefix):
item_name = eat_location.name[len(drink_prefix):]
else:
raise Exception(f"Eatsanity Location does not have a recognized prefix: '{eat_location.name}'")
rule_collector.set_location_rule(eat_location.name, logic.has(item_name))
def set_endgame_locations_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions):
if not world_options.include_endgame_locations:
return
rule_collector.set_location_rule("Earth Obelisk Blueprint", logic.building.can_purchase_wizard_blueprint(WizardBuilding.earth_obelisk))
rule_collector.set_location_rule("Water Obelisk Blueprint", logic.building.can_purchase_wizard_blueprint(WizardBuilding.water_obelisk))
rule_collector.set_location_rule("Desert Obelisk Blueprint", logic.building.can_purchase_wizard_blueprint(WizardBuilding.desert_obelisk))
rule_collector.set_location_rule("Junimo Hut Blueprint", logic.building.can_purchase_wizard_blueprint(WizardBuilding.junimo_hut))
rule_collector.set_location_rule("Gold Clock Blueprint", logic.building.can_purchase_wizard_blueprint(WizardBuilding.gold_clock))
rule_collector.set_location_rule("Purchase Return Scepter", logic.money.can_spend_at(Region.sewer, 2_000_000))
rule_collector.set_location_rule("Pam House Blueprint",
logic.money.can_spend_at(Region.carpenter, 500_000) & logic.grind.can_grind_item(950, Material.wood))
rule_collector.set_location_rule("Forest To Beach Shortcut Blueprint", logic.money.can_spend_at(Region.carpenter, 75_000))
rule_collector.set_location_rule("Mountain Shortcuts Blueprint", logic.money.can_spend_at(Region.carpenter, 75_000))
rule_collector.set_location_rule("Town To Tide Pools Shortcut Blueprint", logic.money.can_spend_at(Region.carpenter, 75_000))
rule_collector.set_location_rule("Tunnel To Backwoods Shortcut Blueprint", logic.money.can_spend_at(Region.carpenter, 75_000))
rule_collector.set_location_rule("Purchase Statue Of Endless Fortune", logic.can_purchase_statue_of_endless_fortune())
rule_collector.set_location_rule("Purchase Catalogue", logic.money.can_spend_at(Region.pierre_store, 30_000))
rule_collector.set_location_rule("Purchase Furniture Catalogue", logic.money.can_spend_at(Region.carpenter, 200_000))
rule_collector.set_location_rule("Purchase Joja Furniture Catalogue",
logic.action.can_speak_junimo() & logic.money.can_spend_at(Region.movie_theater, 25_000))
rule_collector.set_location_rule("Purchase Junimo Catalogue",
logic.action.can_speak_junimo() & logic.money.can_spend_at(LogicRegion.traveling_cart, 70_000))
rule_collector.set_location_rule("Purchase Retro Catalogue", logic.money.can_spend_at(LogicRegion.traveling_cart, 110_000))
# rule_collector.set_location_rule( "Find Trash Catalogue", logic) # No need, the region is enough
rule_collector.set_location_rule("Purchase Wizard Catalogue", logic.money.can_spend_at(Region.sewer, 150_000))
rule_collector.set_location_rule("Purchase Tea Set", logic.money.can_spend_at(LogicRegion.traveling_cart, 1_000_000) & logic.time.has_lived_max_months)
if world_options.friendsanity == Friendsanity.option_all_with_marriage:
rule_collector.set_location_rule("Purchase Abigail Portrait", logic.relationship.can_purchase_portrait(NPC.abigail))
rule_collector.set_location_rule("Purchase Alex Portrait", logic.relationship.can_purchase_portrait(NPC.alex))
rule_collector.set_location_rule("Purchase Elliott Portrait", logic.relationship.can_purchase_portrait(NPC.elliott))
rule_collector.set_location_rule("Purchase Emily Portrait", logic.relationship.can_purchase_portrait(NPC.emily))
rule_collector.set_location_rule("Purchase Haley Portrait", logic.relationship.can_purchase_portrait(NPC.haley))
rule_collector.set_location_rule("Purchase Harvey Portrait", logic.relationship.can_purchase_portrait(NPC.harvey))
rule_collector.set_location_rule("Purchase Krobus Portrait", logic.relationship.can_purchase_portrait(NPC.krobus))
rule_collector.set_location_rule("Purchase Leah Portrait", logic.relationship.can_purchase_portrait(NPC.leah))
rule_collector.set_location_rule("Purchase Maru Portrait", logic.relationship.can_purchase_portrait(NPC.maru))
rule_collector.set_location_rule("Purchase Penny Portrait", logic.relationship.can_purchase_portrait(NPC.penny))
rule_collector.set_location_rule("Purchase Sam Portrait", logic.relationship.can_purchase_portrait(NPC.sam))
rule_collector.set_location_rule("Purchase Sebastian Portrait", logic.relationship.can_purchase_portrait(NPC.sebastian))
rule_collector.set_location_rule("Purchase Shane Portrait", logic.relationship.can_purchase_portrait(NPC.shane))
elif world_options.friendsanity != Friendsanity.option_none:
rule_collector.set_location_rule("Purchase Spouse Portrait", logic.relationship.can_purchase_portrait())
if world_options.exclude_ginger_island == ExcludeGingerIsland.option_false:
rule_collector.set_location_rule("Island Obelisk Blueprint", logic.building.can_purchase_wizard_blueprint(WizardBuilding.island_obelisk))
if world_options.special_order_locations & SpecialOrderLocations.value_qi:
rule_collector.set_location_rule("Purchase Horse Flute", logic.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 50))
rule_collector.set_location_rule("Purchase Pierre's Missing Stocklist", logic.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 50))
rule_collector.set_location_rule("Purchase Key To The Town", logic.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20))
rule_collector.set_location_rule("Purchase Mini-Shipping Bin", logic.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 60))
rule_collector.set_location_rule("Purchase Exotic Double Bed", logic.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 50))
rule_collector.set_location_rule("Purchase Golden Egg", logic.received(AnimalProduct.golden_egg) & logic.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100))
def set_friendsanity_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.features.friendsanity.is_enabled:
return
rule_collector.set_location_rule("Spouse Stardrop", logic.relationship.has_hearts_with_any_bachelor(13))
rule_collector.set_location_rule("Have a Baby", logic.relationship.can_reproduce(1))
rule_collector.set_location_rule("Have Another Baby", logic.relationship.can_reproduce(2))
for villager in content.villagers.values():
for heart in content.features.friendsanity.get_randomized_hearts(villager):
rule = logic.relationship.can_earn_relationship(villager.name, heart)
location_name = friendsanity.to_location_name(villager.name, heart)
rule_collector.set_location_rule(location_name, rule)
for heart in content.features.friendsanity.get_pet_randomized_hearts():
rule = logic.pet.can_befriend_pet(heart)
location_name = friendsanity.to_location_name(NPC.pet, heart)
rule_collector.set_location_rule(location_name, rule)
def set_deepwoods_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.is_enabled(ModNames.deepwoods):
return
rule_collector.set_location_rule("Breaking Up Deep Woods Gingerbread House", logic.tool.has_tool(Tool.axe, ToolMaterial.gold))
rule_collector.set_location_rule("Chop Down a Deep Woods Iridium Tree", logic.tool.has_tool(Tool.axe, ToolMaterial.iridium))
rule_collector.set_entrance_rule(DeepWoodsEntrance.use_woods_obelisk, logic.received("Woods Obelisk"))
for depth in range(10, 100 + 10, 10):
rule_collector.set_entrance_rule(move_to_woods_depth(depth), logic.mod.deepwoods.can_chop_to_depth(depth))
rule_collector.set_location_rule("The Sword in the Stone", logic.mod.deepwoods.can_pull_sword() & logic.mod.deepwoods.can_chop_to_depth(100))
def set_magic_spell_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.is_enabled(ModNames.magic):
return
rule_collector.set_location_rule("Analyze: Clear Debris", logic.tool.has_tool(Tool.axe) | logic.tool.has_tool(Tool.pickaxe))
rule_collector.set_location_rule("Analyze: Till", logic.tool.has_tool(Tool.hoe))
rule_collector.set_location_rule("Analyze: Water", logic.tool.has_tool(Tool.watering_can))
rule_collector.set_location_rule("Analyze All Toil School Locations",
logic.tool.has_tool(Tool.watering_can)
& logic.tool.has_tool(Tool.hoe)
& (logic.tool.has_tool(Tool.axe) | logic.tool.has_tool(Tool.pickaxe)))
# Do I *want* to add boots into logic when you get them even in vanilla without effort? idk
rule_collector.set_location_rule("Analyze: Evac", logic.ability.can_mine_perfectly())
rule_collector.set_location_rule("Analyze: Haste", logic.has("Coffee"))
rule_collector.set_location_rule("Analyze: Heal", logic.has("Life Elixir"))
rule_collector.set_location_rule("Analyze All Life School Locations",
logic.has_all("Coffee", "Life Elixir") & logic.ability.can_mine_perfectly())
rule_collector.set_location_rule("Analyze: Descend", logic.region.can_reach(Region.mines))
rule_collector.set_location_rule("Analyze: Fireball", logic.has("Fire Quartz"))
rule_collector.set_location_rule("Analyze: Frostbolt", logic.region.can_reach(Region.mines_floor_60) & logic.fishing.can_fish(85))
rule_collector.set_location_rule("Analyze All Elemental School Locations",
logic.has("Fire Quartz") & logic.region.can_reach(Region.mines_floor_60) & logic.fishing.can_fish(85))
# rule_collector.set_location_rule( "Analyze: Lantern", player),)
rule_collector.set_location_rule("Analyze: Tendrils", logic.region.can_reach(Region.farm))
rule_collector.set_location_rule("Analyze: Shockwave", logic.has("Earth Crystal"))
rule_collector.set_location_rule("Analyze All Nature School Locations", logic.has("Earth Crystal") & logic.region.can_reach("Farm")),
rule_collector.set_location_rule("Analyze: Meteor", logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12)),
rule_collector.set_location_rule("Analyze: Lucksteal", logic.region.can_reach(Region.witch_hut))
rule_collector.set_location_rule("Analyze: Bloodmana", logic.region.can_reach(Region.mines_floor_100))
rule_collector.set_location_rule("Analyze All Eldritch School Locations",
logic.region.can_reach_all(Region.witch_hut, Region.mines_floor_100, Region.farm) & logic.time.has_lived_months(12))
rule_collector.set_location_rule("Analyze Every Magic School Location",
logic.tool.has_tool(Tool.watering_can)
& logic.tool.has_tool(Tool.hoe)
& (logic.tool.has_tool(Tool.axe) | logic.tool.has_tool(Tool.pickaxe))
& logic.has_all("Coffee", "Life Elixir", "Earth Crystal", "Fire Quartz")
& logic.ability.can_mine_perfectly()
& logic.fishing.can_fish(85)
& logic.region.can_reach_all(Region.witch_hut, Region.mines_floor_100, Region.farm)
& logic.time.has_lived_months(12))
def set_sve_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.is_enabled(ModNames.sve):
return
rule_collector.set_entrance_rule(SVEEntrance.forest_to_lost_woods, logic.bundle.can_complete_community_center)
rule_collector.set_entrance_rule(SVEEntrance.enter_summit, logic.mod.sve.has_iridium_bomb())
rule_collector.set_entrance_rule(SVEEntrance.backwoods_to_grove, logic.mod.sve.has_any_rune())
rule_collector.set_entrance_rule(SVEEntrance.badlands_to_cave, logic.has("Aegis Elixir") | logic.combat.can_fight_at_level(Performance.maximum))
rule_collector.set_entrance_rule(SVEEntrance.forest_west_to_spring, logic.quest.can_complete_quest(Quest.magic_ink))
rule_collector.set_entrance_rule(SVEEntrance.railroad_to_grampleton_station, logic.received(SVEQuestItem.scarlett_job_offer))
rule_collector.set_entrance_rule(SVEEntrance.secret_woods_to_west, logic.tool.has_tool(Tool.axe, ToolMaterial.iron))
rule_collector.set_entrance_rule(SVEEntrance.grandpa_shed_to_interior, logic.tool.has_tool(Tool.axe, ToolMaterial.iron))
rule_collector.set_entrance_rule(SVEEntrance.aurora_warp_to_aurora, logic.received(SVERunes.nexus_aurora))
rule_collector.set_entrance_rule(SVEEntrance.farm_warp_to_farm, logic.received(SVERunes.nexus_farm))
rule_collector.set_entrance_rule(SVEEntrance.guild_warp_to_guild, logic.received(SVERunes.nexus_guild))
rule_collector.set_entrance_rule(SVEEntrance.junimo_warp_to_junimo, logic.received(SVERunes.nexus_junimo))
rule_collector.set_entrance_rule(SVEEntrance.spring_warp_to_spring, logic.received(SVERunes.nexus_spring))
rule_collector.set_entrance_rule(SVEEntrance.outpost_warp_to_outpost, logic.received(SVERunes.nexus_outpost))
rule_collector.set_entrance_rule(SVEEntrance.wizard_warp_to_wizard, logic.received(SVERunes.nexus_wizard))
rule_collector.set_entrance_rule(SVEEntrance.use_purple_junimo, logic.relationship.has_hearts(ModNPC.apples, 10))
rule_collector.set_entrance_rule(SVEEntrance.grandpa_interior_to_upstairs, logic.mod.sve.has_grandpa_shed_repaired())
rule_collector.set_entrance_rule(SVEEntrance.use_bear_shop, (logic.mod.sve.can_buy_bear_recipe()))
rule_collector.set_entrance_rule(SVEEntrance.railroad_to_grampleton_station, logic.received(SVEQuestItem.scarlett_job_offer))
rule_collector.set_entrance_rule(SVEEntrance.museum_to_gunther_bedroom, logic.relationship.has_hearts(ModNPC.gunther, 2))
rule_collector.set_entrance_rule(SVEEntrance.to_aurora_basement, logic.mod.quest.has_completed_aurora_vineyard_bundle())
logic.mod.sve.initialize_rules()
for location in logic.registry.sve_location_rules:
rule_collector.set_location_rule(location, logic.registry.sve_location_rules[location])
set_sve_ginger_island_rules(logic, rule_collector, content)
set_boarding_house_rules(logic, rule_collector, content)
def set_sve_ginger_island_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.is_enabled(ginger_island_content_pack):
return
rule_collector.set_entrance_rule(SVEEntrance.summit_to_highlands, logic.mod.sve.has_marlon_boat())
rule_collector.set_entrance_rule(SVEEntrance.wizard_to_fable_reef, logic.received(SVEQuestItem.fable_reef_portal))
rule_collector.set_entrance_rule(SVEEntrance.highlands_to_cave,
logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron) & logic.tool.has_tool(Tool.axe, ToolMaterial.iron))
rule_collector.set_entrance_rule(SVEEntrance.highlands_to_pond, logic.tool.has_tool(Tool.axe, ToolMaterial.iron))
def set_boarding_house_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent):
if not content.is_enabled(ModNames.boarding_house):
return
rule_collector.set_entrance_rule(BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins, logic.tool.has_tool(Tool.axe, ToolMaterial.iron))