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))