diff --git a/CommonClient.py b/CommonClient.py index 1111adb080cc..c367bd47ee4b 100755 --- a/CommonClient.py +++ b/CommonClient.py @@ -65,6 +65,8 @@ def output(self, text: str): def _cmd_exit(self) -> bool: """Close connections and client""" + if self.ctx.ui: + self.ctx.ui.stop() self.ctx.exit_event.set() return True diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index a4cc3aa5acf3..1df4ada7ead5 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -55,6 +55,9 @@ {{ OptionTitle(option_name, option) }}
+ {% if option.default not in option.options.values() %} + + {% endif %} {% for id, name in option.name_lookup.items()|sort %} {% if name != "random" %} {% if option.default == id %} diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index ec96a9949e3c..558df4feb10c 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,30 +1,41 @@ import logging +import math import typing +from collections import Counter +from functools import wraps from random import Random -from typing import Dict, Any, Optional, List, TextIO +from typing import Dict, List, Any, ClassVar, TextIO, Optional import entrance_rando from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld +from worlds.LauncherComponents import components, Component, icon_paths, Type from .bundles.bundle_room import BundleRoom -from .bundles.bundles import get_all_bundles +from .bundles.bundles import get_all_bundles, get_trash_bear_requests from .content import StardewContent, create_content -from .early_items import setup_early_items -from .items import item_table, ItemData, Group, items_by_group -from .items.item_creation import create_items, get_all_filler_items, remove_limited_amount_packs, \ - generate_filler_choice_pool +from .content.feature.special_order_locations import get_qi_gem_amount +from .content.feature.walnutsanity import get_walnut_amount +from .items import item_table, ItemData, Group, items_by_group, create_items, generate_filler_choice_pool, \ + setup_early_items +from .items.item_data import FILLER_GROUPS from .locations import location_table, create_locations, LocationData, locations_by_tag +from .logic.combat_logic import valid_weapons from .logic.logic import StardewLogic from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, EnabledFillerBuffs, \ - NumberOfMovementBuffs, BuildingProgression, EntranceRandomization, FarmType -from .options.forced_options import force_change_options_if_incompatible + NumberOfMovementBuffs, BuildingProgression, EntranceRandomization, ToolProgression, BackpackProgression, TrapDistribution, BundlePrice, \ + BundleWhitelist, BundleBlacklist, BundlePerRoom, FarmType +from .options.forced_options import force_change_options_if_incompatible, force_change_options_if_banned +from .options.jojapocalypse_options import JojaAreYouSure from .options.option_groups import sv_option_groups from .options.presets import sv_options_presets +from .options.settings import StardewSettings from .options.worlds_group import apply_most_restrictive_options from .regions import create_regions, prepare_mod_data from .rules import set_rules from .stardew_rule import True_, StardewRule, HasProgressionPercent +from .strings.ap_names.ap_option_names import StartWithoutOptionName +from .strings.ap_names.ap_weapon_names import APWeapon from .strings.ap_names.event_names import Event from .strings.goal_names import Goal as GoalName @@ -34,6 +45,7 @@ UNIVERSAL_TRACKER_SEED_PROPERTY = "ut_seed" client_version = 0 +TRACKER_ENABLED = True class StardewLocation(Location): @@ -42,6 +54,12 @@ class StardewLocation(Location): class StardewItem(Item): game: str = STARDEW_VALLEY + events_to_collect: Counter[str] + + @wraps(Item.__init__) + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.events_to_collect = Counter() class StardewWebWorld(WebWorld): @@ -56,7 +74,7 @@ class StardewWebWorld(WebWorld): "English", "setup_en.md", "setup/en", - ["KaitoKid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"] + ["Kaito Kid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"] ) setup_fr = Tutorial( @@ -71,13 +89,35 @@ class StardewWebWorld(WebWorld): tutorials = [setup_en, setup_fr] +if TRACKER_ENABLED: + from .. import user_folder + import os + + # Best effort to detect if universal tracker is installed + if any("tracker.apworld" in f.name for f in os.scandir(user_folder)): + def launch_client(*args): + from worlds.LauncherComponents import launch + from .client import launch as client_main + launch(client_main, name="Stardew Valley Tracker", args=args) + + + components.append(Component( + "Stardew Valley Tracker", + func=launch_client, + component_type=Type.CLIENT, + icon='stardew' + )) + + icon_paths['stardew'] = f"ap:{__name__}/stardew.png" + + class StardewValleyWorld(World): """ Stardew Valley is an open-ended country-life RPG. You can farm, fish, mine, fight, complete quests, befriend villagers, and uncover dark secrets. """ game = STARDEW_VALLEY - topology_present = False + topology_present = True item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = {name: data.code for name, data in location_table.items()} @@ -95,14 +135,17 @@ class StardewValleyWorld(World): options_dataclass = StardewValleyOptions options: StardewValleyOptions + settings: ClassVar[StardewSettings] content: StardewContent logic: StardewLogic web = StardewWebWorld() modified_bundles: List[BundleRoom] randomized_entrances: Dict[str, str] + trash_bear_requests: Dict[str, List[str]] total_progression_items: int + classifications_to_override_post_fill: list[tuple[StardewItem, ItemClassification]] @classmethod def create_group(cls, multiworld: MultiWorld, new_player_id: int, players: set[int]) -> World: @@ -111,13 +154,15 @@ def create_group(cls, multiworld: MultiWorld, new_player_id: int, players: set[i group_options = typing.cast(StardewValleyOptions, world_group.options) worlds_options = [typing.cast(StardewValleyOptions, multiworld.worlds[player].options) for player in players] apply_most_restrictive_options(group_options, worlds_options) + world_group.content = create_content(group_options) return world_group def __init__(self, multiworld: MultiWorld, player: int): super().__init__(multiworld, player) - self.filler_item_pool_names = [] + self.filler_item_pool_names = None self.total_progression_items = 0 + self.classifications_to_override_post_fill = [] # Taking the seed specified in slot data for UT, otherwise just generating the seed. self.seed = getattr(multiworld, "re_gen_passthrough", {}).get(STARDEW_VALLEY, self.random.getrandbits(64)) @@ -128,9 +173,25 @@ def interpret_slot_data(self, slot_data: Dict[str, Any]) -> Optional[int]: seed = slot_data.get(UNIVERSAL_TRACKER_SEED_PROPERTY) if seed is None: logger.warning(f"World was generated before Universal Tracker support. Tracker might not be accurate.") + for option_name in slot_data: + if option_name in self.options_dataclass.type_hints: + option_value = slot_data[option_name] + option_type = self.options_dataclass.type_hints[option_name] + if isinstance(option_value, option_type): + self.options.__setattr__(option_name, option_value) + continue + parsed_option_value = option_type.from_any(option_value) + if isinstance(parsed_option_value, option_type): + self.options.__setattr__(option_name, parsed_option_value) + continue + logger.warning(f"Option {option_name} was found in slot data, but could not be automatically parsed to be used in generation.\n" + f"Slot Data Value: {option_value}" + f"Parsed Value: {parsed_option_value}" + f"Yaml Value: {self.options.__getattribute__(option_name)}") return seed def generate_early(self): + force_change_options_if_banned(self.options, self.settings, self.player, self.player_name) force_change_options_if_incompatible(self.options, self.player, self.player_name) self.content = create_content(self.options) @@ -141,23 +202,31 @@ def create_region(name: str) -> Region: world_regions = create_regions(create_region, self.options, self.content) self.logic = StardewLogic(self.player, self.options, self.content, world_regions.keys()) - self.modified_bundles = get_all_bundles(self.random, self.logic, self.content, self.options) + self.modified_bundles = get_all_bundles(self.random, self.logic, self.content, self.options, self.player_name) + self.trash_bear_requests = get_trash_bear_requests(self.random, self.content, self.options) + + for bundle_room in self.modified_bundles: + bundle_room.special_behavior(self) def add_location(name: str, code: Optional[int], region: str): + assert region in world_regions, f"Location {name} cannot be created in region {region}, because the region does not exist in this slot" region: Region = world_regions[region] location = StardewLocation(self.player, name, code, region) region.locations.append(location) - create_locations(add_location, self.modified_bundles, self.options, self.content, self.random) + create_locations(add_location, self.modified_bundles, self.trash_bear_requests, self.options, self.content, self.random) self.multiworld.regions.extend(world_regions.values()) def create_items(self): + self.precollect_start_inventory_items_if_needed() + self.precollect_start_without_items() self.precollect_starting_season() self.precollect_building_items() + self.precollect_starting_backpacks() items_to_exclude = [excluded_items for excluded_items in self.multiworld.precollected_items[self.player] if item_table[excluded_items.name].has_any_group(Group.MAXIMUM_ONE) - or not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK, Group.FRIENDSHIP_PACK)] + or not item_table[excluded_items.name].has_any_group(*FILLER_GROUPS, Group.FRIENDSHIP_PACK)] if self.options.season_randomization == SeasonRandomization.option_disabled: items_to_exclude = [item for item in items_to_exclude @@ -172,6 +241,7 @@ def create_items(self): self.multiworld.itempool += created_items setup_early_items(self.multiworld, self.options, self.content, self.player, self.random) + self.setup_logic_events() self.setup_victory() @@ -187,6 +257,31 @@ def create_items(self): self.total_progression_items += sum(1 for i in created_items if i.advancement) self.total_progression_items -= 1 # -1 for the victory event + player_state = self.multiworld.state.prog_items[self.player] + self.update_received_progression_percent(player_state) + + def precollect_start_inventory_items_if_needed(self): + # The only reason this is necessary, is because in an UT context, precollected items was not filled up, and this messes with the seeded random later + for item_name in self.options.start_inventory: + item_count = self.options.start_inventory[item_name] + precollected_count = len([precollected_item for precollected_item in self.multiworld.precollected_items[self.player] + if precollected_item.name == item_name]) + while precollected_count < item_count: + self.multiworld.push_precollected(self.create_item(item_name)) + precollected_count += 1 + + + def precollect_start_without_items(self): + if StartWithoutOptionName.landslide not in self.options.start_without: + self.multiworld.push_precollected(self.create_item("Landslide Removed")) + if StartWithoutOptionName.community_center not in self.options.start_without: + self.multiworld.push_precollected(self.create_item("Community Center Key")) + self.multiworld.push_precollected(self.create_item("Forest Magic")) + self.multiworld.push_precollected(self.create_item("Wizard Invitation")) + if StartWithoutOptionName.buildings not in self.options.start_without: + self.multiworld.push_precollected(self.create_item("Shipping Bin")) + self.multiworld.push_precollected(self.create_item("Pet Bowl")) + def precollect_starting_season(self): if self.options.season_randomization == SeasonRandomization.option_progressive: return @@ -220,9 +315,23 @@ def precollect_building_items(self): for _ in range(quantity): self.multiworld.push_precollected(self.create_item(item)) + def precollect_starting_backpacks(self): + if self.options.backpack_progression != BackpackProgression.option_vanilla and StartWithoutOptionName.backpack in self.options.start_without: + minimum_start_slots = 4 if StartWithoutOptionName.tools in self.options.start_without else 6 + num_starting_slots = max(minimum_start_slots, self.options.backpack_size.value) + num_starting_backpacks = math.ceil(num_starting_slots / self.options.backpack_size.value) + num_already_starting_backpacks = 0 + for precollected_item in self.multiworld.precollected_items[self.player]: + if precollected_item.name == "Progressive Backpack": + num_already_starting_backpacks += 1 + for i in range(num_starting_backpacks - num_already_starting_backpacks): + self.multiworld.push_precollected(self.create_item("Progressive Backpack")) + def setup_logic_events(self): - def register_event(name: str, region: str, rule: StardewRule): - event_location = LocationData(None, region, name) + def register_event(name: str, region: str, rule: StardewRule, location_name: str | None = None) -> None: + if location_name is None: + location_name = name + event_location = LocationData(None, region, location_name) self.create_event_location(event_location, rule, name) self.logic.setup_events(register_event) @@ -284,6 +393,14 @@ def setup_victory(self): self.create_event_location(location_table[GoalName.mystery_of_the_stardrops], self.logic.goal.can_complete_mystery_of_the_stardrop(), Event.victory) + elif self.options.goal == Goal.option_mad_hatter: + self.create_event_location(location_table[GoalName.mad_hatter], + self.logic.goal.can_complete_mad_hatter(self.get_all_location_names()), + Event.victory) + elif self.options.goal == Goal.option_ultimate_foodie: + self.create_event_location(location_table[GoalName.ultimate_foodie], + self.logic.goal.can_complete_ultimate_foodie(self.get_all_location_names()), + Event.victory) elif self.options.goal == Goal.option_allsanity: self.create_event_location(location_table[GoalName.allsanity], self.logic.goal.can_complete_allsanity(), @@ -298,18 +415,36 @@ def setup_victory(self): def get_all_location_names(self) -> List[str]: return list(location.name for location in self.multiworld.get_locations(self.player)) - def create_item(self, item: str | ItemData, override_classification: ItemClassification = None) -> StardewItem: + def create_item(self, item: str | ItemData, + classification_pre_fill: ItemClassification = None, + classification_post_fill: ItemClassification = None) -> StardewItem: if isinstance(item, str): item = item_table[item] - if override_classification is None: - override_classification = item.classification + if classification_pre_fill is None: + classification_pre_fill = item.classification + + stardew_item = StardewItem(item.name, classification_pre_fill, item.code, self.player) + + if stardew_item.advancement: + # Progress is only counted for pre-fill progression items, so we don't count filler items later converted to progression post-fill. + stardew_item.events_to_collect[Event.received_progression_item] = 1 - return StardewItem(item.name, override_classification, item.code, self.player) + if (walnut_amount := get_walnut_amount(stardew_item.name)) > 0: + stardew_item.events_to_collect[Event.received_walnuts] = walnut_amount + + if (qi_gem_amount := get_qi_gem_amount(stardew_item.name)) > 0: + stardew_item.events_to_collect[Event.received_qi_gems] = qi_gem_amount + + if classification_post_fill is not None: + self.classifications_to_override_post_fill.append((stardew_item, classification_post_fill)) + + return stardew_item def create_event_location(self, location_data: LocationData, rule: StardewRule, item: str): region = self.multiworld.get_region(location_data.region, self.player) - region.add_event(location_data.name, item, rule, StardewLocation, StardewItem) + item = typing.cast(StardewItem, region.add_event(location_data.name, item, rule, StardewLocation, StardewItem)) + item.events_to_collect[Event.received_progression_item] = 1 def set_rules(self): set_rules(self) @@ -322,9 +457,14 @@ def connect_entrances(self) -> None: def generate_basic(self): pass + def post_fill(self) -> None: + # Not updating the prog item count, as any change could make some locations inaccessible, which is pretty much illegal in post fill. + for item, classification in self.classifications_to_override_post_fill: + item.classification = classification + def get_filler_item_name(self) -> str: if not self.filler_item_pool_names: - self.filler_item_pool_names = generate_filler_choice_pool(self.options) + self.filler_item_pool_names = generate_filler_choice_pool(self.options, self.content) return self.random.choice(self.filler_item_pool_names) def write_spoiler_header(self, spoiler_handle: TextIO) -> None: @@ -365,9 +505,10 @@ def fill_slot_data(self) -> Dict[str, Any]: for bundle in room.bundles: bundles[room.name][bundle.name] = {"number_required": bundle.number_required} for i, item in enumerate(bundle.items): - bundles[room.name][bundle.name][i] = f"{item.get_item()}|{item.amount}|{item.quality}" + bundles[room.name][bundle.name][str(i)] = f"{item.get_item()}|{item.amount}|{item.quality}" - excluded_options = [BundleRandomization, NumberOfMovementBuffs, EnabledFillerBuffs] + excluded_options = [BundleRandomization, BundlePerRoom, NumberOfMovementBuffs, + EnabledFillerBuffs, TrapDistribution, BundleWhitelist, BundleBlacklist, JojaAreYouSure] excluded_option_names = [option.internal_name for option in excluded_options] generic_option_names = [option_name for option_name in PerGameCommonOptions.type_hints] excluded_option_names.extend(generic_option_names) @@ -377,8 +518,9 @@ def fill_slot_data(self) -> Dict[str, Any]: UNIVERSAL_TRACKER_SEED_PROPERTY: self.seed, "seed": self.random.randrange(1000000000), # Seed should be max 9 digits "randomized_entrances": self.randomized_entrances, + "trash_bear_requests": self.trash_bear_requests, "modified_bundles": bundles, - "client_version": "6.0.0", + "client_version": self.world_version.as_simple_string(), }) return slot_data @@ -389,18 +531,12 @@ def collect(self, state: CollectionState, item: StardewItem) -> bool: return False player_state = state.prog_items[self.player] + player_state.update(item.events_to_collect) - received_progression_count = player_state[Event.received_progression_item] - received_progression_count += 1 - if self.total_progression_items: - # Total progression items is not set until all items are created, but collect will be called during the item creation when an item is precollected. - # We can't update the percentage if we don't know the total progression items, can't divide by 0. - player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items - player_state[Event.received_progression_item] = received_progression_count + self.update_received_progression_percent(player_state) - walnut_amount = self.get_walnut_amount(item.name) - if walnut_amount: - player_state[Event.received_walnuts] += walnut_amount + if item.name in APWeapon.all_weapons: + player_state[Event.received_progressive_weapon] = max(player_state[Event.received_progressive_weapon], player_state[item.name]) return True @@ -410,26 +546,18 @@ def remove(self, state: CollectionState, item: StardewItem) -> bool: return False player_state = state.prog_items[self.player] + player_state.subtract(item.events_to_collect) - received_progression_count = player_state[Event.received_progression_item] - received_progression_count -= 1 - if self.total_progression_items: - # We can't update the percentage if we don't know the total progression items, can't divide by 0. - player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items - player_state[Event.received_progression_item] = received_progression_count + self.update_received_progression_percent(player_state) - walnut_amount = self.get_walnut_amount(item.name) - if walnut_amount: - player_state[Event.received_walnuts] -= walnut_amount + if item.name in APWeapon.all_weapons: + player_state[Event.received_progressive_weapon] = max(player_state[weapon] for weapon in APWeapon.all_weapons) return True - @staticmethod - def get_walnut_amount(item_name: str) -> int: - if item_name == "Golden Walnut": - return 1 - if item_name == "3 Golden Walnuts": - return 3 - if item_name == "5 Golden Walnuts": - return 5 - return 0 + def update_received_progression_percent(self, player_state: Counter[str]) -> None: + if self.total_progression_items: + received_progression_count = player_state[Event.received_progression_item] + # Total progression items is not set until all items are created, but collect will be called during the item creation when an item is precollected. + # We can't update the percentage if we don't know the total progression items, can't divide by 0. + player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items diff --git a/worlds/stardew_valley/archipelago.json b/worlds/stardew_valley/archipelago.json index e47d48324870..7f7e3a86085d 100644 --- a/worlds/stardew_valley/archipelago.json +++ b/worlds/stardew_valley/archipelago.json @@ -1,6 +1,6 @@ { "game": "Stardew Valley", - "authors": ["KaitoKid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"], + "authors": ["Kaito Kid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"], "minimum_ap_version": "0.6.4", - "world_version": "6.0.0" + "world_version": "7.4.0" } diff --git a/worlds/stardew_valley/bundles/bundle.py b/worlds/stardew_valley/bundles/bundle.py index 43afc750b87a..4e6165712c02 100644 --- a/worlds/stardew_valley/bundles/bundle.py +++ b/worlds/stardew_valley/bundles/bundle.py @@ -3,10 +3,13 @@ from random import Random from typing import List, Tuple +from Options import DeathLink from .bundle_item import BundleItem from ..content import StardewContent -from ..options import BundlePrice, StardewValleyOptions, ExcludeGingerIsland, FestivalLocations -from ..strings.currency_names import Currency +from ..options import BundlePrice, StardewValleyOptions, ExcludeGingerIsland, FestivalLocations, TrapDifficulty, \ + MultipleDaySleepEnabled, Gifting, EntranceRandomization +from ..strings.bundle_names import MemeBundleName +from ..strings.currency_names import Currency, MemeCurrency @dataclass @@ -19,6 +22,10 @@ class Bundle: def __repr__(self): return f"{self.name} -> {self.number_required} from {repr(self.items)}" + def special_behavior(self, world): + if self.name == MemeBundleName.clickbait: + world.options.exclude_locations.value.add(MemeBundleName.clickbait) + @dataclass class BundleTemplate: @@ -42,7 +49,42 @@ def extend_from(template, items: List[BundleItem]): template.number_required_items) def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: - number_required, price_multiplier = get_bundle_final_prices(options.bundle_price, self.number_required_items, False) + try: + number_required, price_multiplier = get_bundle_final_prices(options.bundle_price, self.number_required_items, False) + filtered_items = [item for item in self.items if item.can_appear(content, options)] + number_items = len(filtered_items) + number_chosen_items = self.number_possible_items + if number_chosen_items < number_required: + number_chosen_items = number_required + + if number_chosen_items > number_items: + chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items) + else: + chosen_items = random.sample(filtered_items, number_chosen_items) + chosen_items = [item.as_amount(min(999, max(1, math.floor(item.amount * price_multiplier)))) for item in chosen_items] + return Bundle(self.room, self.name, chosen_items, number_required) + except Exception as e: + raise Exception(f"Failed at creating bundle '{self.name}'. Error: {e}") + + + def can_appear(self, options: StardewValleyOptions) -> bool: + if self.name == MemeBundleName.trap and options.trap_items.value == TrapDifficulty.option_no_traps: + return False + if self.name == MemeBundleName.hibernation and options.multiple_day_sleep_enabled == MultipleDaySleepEnabled.option_false: + return False + if self.name == MemeBundleName.cooperation and options.gifting == Gifting.option_false: + return False + return True + + +class FixedMultiplierBundleTemplate(BundleTemplate): + + def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int, + number_required_items: int): + super().__init__(room, name, items, number_possible_items, number_required_items) + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + number_required = get_number_required_items(options.bundle_price, self.number_required_items) filtered_items = [item for item in self.items if item.can_appear(content, options)] number_items = len(filtered_items) number_chosen_items = self.number_possible_items @@ -53,11 +95,53 @@ def create_bundle(self, random: Random, content: StardewContent, options: Starde chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items) else: chosen_items = random.sample(filtered_items, number_chosen_items) - chosen_items = [item.as_amount(max(1, math.floor(item.amount * price_multiplier))) for item in chosen_items] + chosen_items = [item.as_amount(min(999, max(1, item.amount))) for item in chosen_items] return Bundle(self.room, self.name, chosen_items, number_required) - def can_appear(self, options: StardewValleyOptions) -> bool: - return True + +class FixedPriceBundleTemplate(BundleTemplate): + + def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int, + number_required_items: int): + super().__init__(room, name, items, number_possible_items, number_required_items) + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + filtered_items = [item for item in self.items if item.can_appear(content, options)] + number_items = len(filtered_items) + number_chosen_items = self.number_possible_items + if number_chosen_items < self.number_required_items: + number_chosen_items = self.number_required_items + + if number_chosen_items > number_items: + chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items) + else: + chosen_items = random.sample(filtered_items, number_chosen_items) + chosen_items = [item.as_amount(max(1, math.floor(item.amount))) for item in chosen_items] + return Bundle(self.room, self.name, chosen_items, self.number_required_items) + + +# This type of bundle will always match the number of slots and the number of items +class FixedSlotsBundleTemplate(BundleTemplate): + + def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int, + number_required_items: int): + super().__init__(room, name, items, number_possible_items, number_required_items) + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + try: + number_required, price_multiplier = get_bundle_final_prices(options.bundle_price, self.number_required_items, False) + filtered_items = [item for item in self.items if item.can_appear(content, options)] + number_items = len(filtered_items) + number_chosen_items = number_required + if number_chosen_items > number_items: + chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items) + else: + chosen_items = random.sample(filtered_items, number_chosen_items) + chosen_items = [item.as_amount(min(999, max(1, math.floor(item.amount * price_multiplier)))) for item in chosen_items] + return Bundle(self.room, self.name, chosen_items, number_required) + except Exception as e: + raise Exception(f"Failed at creating bundle '{self.name}'. Error: {e}") + class CurrencyBundleTemplate(BundleTemplate): @@ -77,15 +161,33 @@ def get_currency_amount(self, bundle_price_option: BundlePrice): return currency_amount def can_appear(self, options: StardewValleyOptions) -> bool: + if not super().can_appear(options): + return False if options.exclude_ginger_island == ExcludeGingerIsland.option_true: if self.item.item_name == Currency.qi_gem or self.item.item_name == Currency.golden_walnut or self.item.item_name == Currency.cinder_shard: return False if options.festival_locations == FestivalLocations.option_disabled: if self.item.item_name == Currency.star_token: return False + if options.entrance_randomization != EntranceRandomization.option_disabled: + if self.item.item_name == MemeCurrency.time_elapsed: + return False + if options.death_link != DeathLink.option_true: + if self.item.item_name == MemeCurrency.deathlinks: + return False return True +class FixedPriceCurrencyBundleTemplate(CurrencyBundleTemplate): + + def __init__(self, room: str, name: str, item: BundleItem): + super().__init__(room, name, item) + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + currency_amount = self.item.amount + return Bundle(self.room, self.name, [BundleItem(self.item.item_name, currency_amount)], 1) + + class MoneyBundleTemplate(CurrencyBundleTemplate): def __init__(self, room: str, default_name: str, item: BundleItem): @@ -111,11 +213,15 @@ def get_currency_amount(self, bundle_price_option: BundlePrice): class IslandBundleTemplate(BundleTemplate): def can_appear(self, options: StardewValleyOptions) -> bool: + if not super().can_appear(options): + return False return options.exclude_ginger_island == ExcludeGingerIsland.option_false class FestivalBundleTemplate(BundleTemplate): def can_appear(self, options: StardewValleyOptions) -> bool: + if not super().can_appear(options): + return False return options.festival_locations != FestivalLocations.option_disabled @@ -149,6 +255,96 @@ def create_bundle(self, random: Random, content: StardewContent, options: Starde return Bundle(self.room, self.name, chosen_items, number_required) +class FixedPriceDeepBundleTemplate(DeepBundleTemplate): + + def __init__(self, room: str, name: str, categories: List[List[BundleItem]], number_possible_items: int, + number_required_items: int): + super().__init__(room, name, categories, number_possible_items, number_required_items) + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + number_required = self.number_required_items + number_categories = len(self.categories) + number_chosen_categories = self.number_possible_items + if number_chosen_categories < number_required: + number_chosen_categories = number_required + + if number_chosen_categories > number_categories: + chosen_categories = self.categories + random.choices(self.categories, + k=number_chosen_categories - number_categories) + else: + chosen_categories = random.sample(self.categories, number_chosen_categories) + + chosen_items = [] + for category in chosen_categories: + filtered_items = [item for item in category if item.can_appear(content, options)] + chosen_items.append(random.choice(filtered_items)) + + chosen_items = [item.as_amount(max(1, math.floor(item.amount))) for item in chosen_items] + return Bundle(self.room, self.name, chosen_items, number_required) + + +@dataclass +class BureaucracyBundleTemplate(BundleTemplate): + + def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int, + number_required_items: int): + super(BureaucracyBundleTemplate, self).__init__(room, name, items, number_possible_items, number_required_items) + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + number_required = self.number_required_items + options.bundle_price.value + number_required = min(12, max(4, number_required)) + if options.bundle_price == BundlePrice.option_minimum: + number_required = 4 + if options.bundle_price == BundlePrice.option_maximum: + number_required = 12 + price_multiplier = 1 + + filtered_items = [item for item in self.items if item.can_appear(content, options)] + number_items = len(filtered_items) + number_chosen_items = self.number_possible_items + if number_chosen_items < number_required: + number_chosen_items = number_required + + if number_chosen_items > number_items: + chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items) + else: + chosen_items = random.sample(filtered_items, number_chosen_items) + chosen_items = [item.as_amount(max(1, math.floor(item.amount * price_multiplier))) for item in chosen_items] + return Bundle(self.room, self.name, chosen_items, number_required) + + +@dataclass +class RecursiveBundleTemplate(BundleTemplate): + number_sub_bundles: int + + def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int, + number_required_items: int, number_sub_bundles: int): + super(RecursiveBundleTemplate, self).__init__(room, name, items, number_possible_items, number_required_items) + self.number_sub_bundles = number_sub_bundles + + def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle: + number_required = self.number_required_items + (options.bundle_price.value * self.number_sub_bundles) + if options.bundle_price == BundlePrice.option_minimum: + number_required = self.number_sub_bundles + if options.bundle_price == BundlePrice.option_maximum: + number_required = self.number_sub_bundles * 8 + number_required = min(self.number_sub_bundles * 8, max(self.number_sub_bundles, number_required)) + price_multiplier = get_price_multiplier(options.bundle_price, False) + + filtered_items = [item for item in self.items if item.can_appear(content, options)] + number_items = len(filtered_items) + number_chosen_items = self.number_possible_items + if number_chosen_items < number_required: + number_chosen_items = number_required + + if number_chosen_items > number_items: + chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items) + else: + chosen_items = random.sample(filtered_items, number_chosen_items) + chosen_items = [item.as_amount(max(1, math.floor(item.amount * price_multiplier))) for item in chosen_items] + return Bundle(self.room, self.name, chosen_items, number_required) + + def get_bundle_final_prices(bundle_price_option: BundlePrice, default_required_items: int, is_currency: bool) -> Tuple[int, float]: number_required_items = get_number_required_items(bundle_price_option, default_required_items) price_multiplier = get_price_multiplier(bundle_price_option, is_currency) diff --git a/worlds/stardew_valley/bundles/bundle_item.py b/worlds/stardew_valley/bundles/bundle_item.py index 91e279d2a623..398de22a4f3d 100644 --- a/worlds/stardew_valley/bundles/bundle_item.py +++ b/worlds/stardew_valley/bundles/bundle_item.py @@ -37,6 +37,11 @@ def can_appear(self, content: StardewContent, options: StardewValleyOptions) -> return content.features.skill_progression.are_masteries_shuffled +class QiBoardItemSource(BundleItemSource): + def can_appear(self, content: StardewContent, options: StardewValleyOptions) -> bool: + return content_packs.qi_board_content_pack.name in content.registered_packs + + class ContentItemSource(BundleItemSource): """This is meant to be used for items that are managed by the content packs.""" @@ -51,6 +56,7 @@ class Sources: island = IslandItemSource() festival = FestivalItemSource() masteries = MasteryItemSource() + qi_board = QiBoardItemSource() content = ContentItemSource() item_name: str diff --git a/worlds/stardew_valley/bundles/bundle_room.py b/worlds/stardew_valley/bundles/bundle_room.py index 225fb4feab1b..40edc0425a8f 100644 --- a/worlds/stardew_valley/bundles/bundle_room.py +++ b/worlds/stardew_valley/bundles/bundle_room.py @@ -5,6 +5,7 @@ from .bundle import Bundle, BundleTemplate from ..content import StardewContent from ..options import StardewValleyOptions +from ..strings.bundle_names import CCRoom @dataclass @@ -12,6 +13,24 @@ class BundleRoom: name: str bundles: List[Bundle] + def special_behavior(self, world): + for bundle in self.bundles: + bundle.special_behavior(world) + + +def simplify_name(name: str) -> str: + return name.lower().replace(" ", "").replace("-", "").replace("_", "").replace(".", "") + + +# In the context of meme bundles, some of the bundles are directly references to specific people, mostly content creators. +# This ensures that they roll their own bundle as part of their community center. +def is_bundle_related_to_player(bundle: BundleTemplate, player_name: str) -> bool: + if player_name == "": + return False + simple_bundle = simplify_name(bundle.name) + simple_player = simplify_name(player_name) + return simple_player in simple_bundle or simple_bundle in simple_player + @dataclass class BundleRoomTemplate: @@ -19,25 +38,40 @@ class BundleRoomTemplate: bundles: List[BundleTemplate] number_bundles: int - def create_bundle_room(self, random: Random, content: StardewContent, options: StardewValleyOptions): + def create_bundle_room(self, random: Random, content: StardewContent, options: StardewValleyOptions, player_name: str = "", is_entire_cc: bool = False): filtered_bundles = [bundle for bundle in self.bundles if bundle.can_appear(options)] - priority_bundles = [] + whitelist_bundles = [] unpriority_bundles = [] + blacklist_bundles = [] for bundle in filtered_bundles: - if bundle.name in options.bundle_plando: - priority_bundles.append(bundle) - else: + if options.bundle_whitelist.prioritizes(bundle.name) or is_bundle_related_to_player(bundle, player_name): + whitelist_bundles.append(bundle) + elif options.bundle_blacklist.allows(bundle.name): unpriority_bundles.append(bundle) + else: + blacklist_bundles.append(bundle) - if self.number_bundles <= len(priority_bundles): - chosen_bundles = random.sample(priority_bundles, self.number_bundles) + modifier = options.bundle_per_room.value + if is_entire_cc: + modifier *= 6 + number_bundles = self.number_bundles + modifier + bundles_cap = max(len(filtered_bundles), self.number_bundles) + number_bundles = max(1, min(bundles_cap, number_bundles)) + if number_bundles < len(whitelist_bundles): + chosen_bundles = random.sample(whitelist_bundles, number_bundles) else: - chosen_bundles = priority_bundles - num_remaining_bundles = self.number_bundles - len(priority_bundles) - if num_remaining_bundles > len(unpriority_bundles): - chosen_bundles.extend(random.choices(unpriority_bundles, k=num_remaining_bundles)) - else: + chosen_bundles = whitelist_bundles + num_remaining_bundles = number_bundles - len(whitelist_bundles) + if num_remaining_bundles < len(unpriority_bundles): chosen_bundles.extend(random.sample(unpriority_bundles, num_remaining_bundles)) + else: + chosen_bundles.extend(unpriority_bundles) + num_remaining_bundles = num_remaining_bundles - len(unpriority_bundles) + if num_remaining_bundles > 0: + if self.name == CCRoom.raccoon_requests: + chosen_bundles.extend(random.choices(unpriority_bundles, k=num_remaining_bundles)) + else: + chosen_bundles.extend(random.sample(blacklist_bundles, num_remaining_bundles)) return BundleRoom(self.name, [bundle.create_bundle(random, content, options) for bundle in chosen_bundles]) diff --git a/worlds/stardew_valley/bundles/bundles.py b/worlds/stardew_valley/bundles/bundles.py index 99619e09aadf..1c3d8dd0141f 100644 --- a/worlds/stardew_valley/bundles/bundles.py +++ b/worlds/stardew_valley/bundles/bundles.py @@ -1,19 +1,26 @@ from random import Random -from typing import List, Tuple +from typing import List, Tuple, Dict from .bundle import Bundle from .bundle_room import BundleRoom, BundleRoomTemplate from ..content import StardewContent -from ..data.bundle_data import pantry_vanilla, crafts_room_vanilla, fish_tank_vanilla, boiler_room_vanilla, bulletin_board_vanilla, vault_vanilla, \ - pantry_thematic, crafts_room_thematic, fish_tank_thematic, boiler_room_thematic, bulletin_board_thematic, vault_thematic, pantry_remixed, \ - crafts_room_remixed, fish_tank_remixed, boiler_room_remixed, bulletin_board_remixed, vault_remixed, all_bundle_items_except_money, \ - abandoned_joja_mart_thematic, abandoned_joja_mart_vanilla, abandoned_joja_mart_remixed, raccoon_vanilla, raccoon_thematic, raccoon_remixed, \ - community_center_remixed_anywhere +from ..data.bundles_data.bundle_data import pantry_remixed, \ + crafts_room_remixed, fish_tank_remixed, boiler_room_remixed, bulletin_board_remixed, vault_remixed, \ + all_bundle_items_except_money, \ + abandoned_joja_mart_remixed, giant_stump_remixed +from ..data.bundles_data.bundle_set import vanilla_bundles, remixed_bundles, thematic_bundles +from ..data.bundles_data.meme_bundles import community_center_meme_bundles, pantry_meme, crafts_room_meme, \ + fish_tank_meme, bulletin_board_meme, \ + boiler_room_meme, vault_meme +from ..data.bundles_data.remixed_anywhere_bundles import community_center_remixed_anywhere +from ..data.game_item import ItemTag +from ..data.recipe_data import all_cooking_recipes from ..logic.logic import StardewLogic from ..options import BundleRandomization, StardewValleyOptions +from ..strings.bundle_names import CCRoom -def get_all_bundles(random: Random, logic: StardewLogic, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]: +def get_all_bundles(random: Random, logic: StardewLogic, content: StardewContent, options: StardewValleyOptions, player_name: str) -> List[BundleRoom]: if options.bundle_randomization == BundleRandomization.option_vanilla: return get_vanilla_bundles(random, content, options) elif options.bundle_randomization == BundleRandomization.option_thematic: @@ -24,72 +31,76 @@ def get_all_bundles(random: Random, logic: StardewLogic, content: StardewContent return get_remixed_bundles_anywhere(random, content, options) elif options.bundle_randomization == BundleRandomization.option_shuffled: return get_shuffled_bundles(random, logic, content, options) + elif options.bundle_randomization == BundleRandomization.option_meme: + return get_meme_bundles(random, content, options, player_name) raise NotImplementedError def get_vanilla_bundles(random: Random, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]: - pantry = pantry_vanilla.create_bundle_room(random, content, options) - crafts_room = crafts_room_vanilla.create_bundle_room(random, content, options) - fish_tank = fish_tank_vanilla.create_bundle_room(random, content, options) - boiler_room = boiler_room_vanilla.create_bundle_room(random, content, options) - bulletin_board = bulletin_board_vanilla.create_bundle_room(random, content, options) - vault = vault_vanilla.create_bundle_room(random, content, options) - abandoned_joja_mart = abandoned_joja_mart_vanilla.create_bundle_room(random, content, options) - raccoon = raccoon_vanilla.create_bundle_room(random, content, options) - fix_raccoon_bundle_names(raccoon) - return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart, raccoon] + generated_bundle_rooms = {room_name: vanilla_bundles.bundles_by_room[room_name].create_bundle_room(random, content, options) for room_name in + vanilla_bundles.bundles_by_room} + fix_raccoon_bundle_names(generated_bundle_rooms[CCRoom.raccoon_requests]) + return list(generated_bundle_rooms.values()) def get_thematic_bundles(random: Random, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]: - pantry = pantry_thematic.create_bundle_room(random, content, options) - crafts_room = crafts_room_thematic.create_bundle_room(random, content, options) - fish_tank = fish_tank_thematic.create_bundle_room(random, content, options) - boiler_room = boiler_room_thematic.create_bundle_room(random, content, options) - bulletin_board = bulletin_board_thematic.create_bundle_room(random, content, options) - vault = vault_thematic.create_bundle_room(random, content, options) - abandoned_joja_mart = abandoned_joja_mart_thematic.create_bundle_room(random, content, options) - raccoon = raccoon_thematic.create_bundle_room(random, content, options) - fix_raccoon_bundle_names(raccoon) - return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart, raccoon] + generated_bundle_rooms = {room_name: thematic_bundles.bundles_by_room[room_name].create_bundle_room(random, content, options) for room_name in + thematic_bundles.bundles_by_room} + fix_raccoon_bundle_names(generated_bundle_rooms[CCRoom.raccoon_requests]) + return list(generated_bundle_rooms.values()) def get_remixed_bundles(random: Random, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]: - pantry = pantry_remixed.create_bundle_room(random, content, options) - crafts_room = crafts_room_remixed.create_bundle_room(random, content, options) - fish_tank = fish_tank_remixed.create_bundle_room(random, content, options) - boiler_room = boiler_room_remixed.create_bundle_room(random, content, options) - bulletin_board = bulletin_board_remixed.create_bundle_room(random, content, options) - vault = vault_remixed.create_bundle_room(random, content, options) + generated_bundle_rooms = {room_name: remixed_bundles.bundles_by_room[room_name].create_bundle_room(random, content, options) for room_name in + remixed_bundles.bundles_by_room} + fix_raccoon_bundle_names(generated_bundle_rooms[CCRoom.raccoon_requests]) + return list(generated_bundle_rooms.values()) + + +def get_remixed_bundles_anywhere(random: Random, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]: + big_room = community_center_remixed_anywhere.create_bundle_room(random, content, options, is_entire_cc=True) + all_chosen_bundles = big_room.bundles + random.shuffle(all_chosen_bundles) + + end_index = 0 + + pantry, end_index = create_room_from_bundles(pantry_remixed, all_chosen_bundles, options, end_index) + crafts_room, end_index = create_room_from_bundles(crafts_room_remixed, all_chosen_bundles, options, end_index) + fish_tank, end_index = create_room_from_bundles(fish_tank_remixed, all_chosen_bundles, options, end_index) + boiler_room, end_index = create_room_from_bundles(boiler_room_remixed, all_chosen_bundles, options, end_index) + bulletin_board, end_index = create_room_from_bundles(bulletin_board_remixed, all_chosen_bundles, options, end_index) + vault, end_index = create_room_from_bundles(vault_remixed, all_chosen_bundles, options, end_index) + abandoned_joja_mart = abandoned_joja_mart_remixed.create_bundle_room(random, content, options) - raccoon = raccoon_remixed.create_bundle_room(random, content, options) + raccoon = giant_stump_remixed.create_bundle_room(random, content, options) fix_raccoon_bundle_names(raccoon) return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart, raccoon] -def get_remixed_bundles_anywhere(random: Random, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]: - big_room = community_center_remixed_anywhere.create_bundle_room(random, content, options) +def get_meme_bundles(random: Random, content: StardewContent, options: StardewValleyOptions, player_name: str) -> List[BundleRoom]: + big_room = community_center_meme_bundles.create_bundle_room(random, content, options, player_name, is_entire_cc=True) all_chosen_bundles = big_room.bundles random.shuffle(all_chosen_bundles) end_index = 0 - pantry, end_index = create_room_from_bundles(pantry_remixed, all_chosen_bundles, end_index) - crafts_room, end_index = create_room_from_bundles(crafts_room_remixed, all_chosen_bundles, end_index) - fish_tank, end_index = create_room_from_bundles(fish_tank_remixed, all_chosen_bundles, end_index) - boiler_room, end_index = create_room_from_bundles(boiler_room_remixed, all_chosen_bundles, end_index) - bulletin_board, end_index = create_room_from_bundles(bulletin_board_remixed, all_chosen_bundles, end_index) + pantry, end_index = create_room_from_bundles(pantry_meme, all_chosen_bundles, options, end_index) + crafts_room, end_index = create_room_from_bundles(crafts_room_meme, all_chosen_bundles, options, end_index) + fish_tank, end_index = create_room_from_bundles(fish_tank_meme, all_chosen_bundles, options, end_index) + boiler_room, end_index = create_room_from_bundles(boiler_room_meme, all_chosen_bundles, options, end_index) + bulletin_board, end_index = create_room_from_bundles(bulletin_board_meme, all_chosen_bundles, options, end_index) + vault, end_index = create_room_from_bundles(vault_meme, all_chosen_bundles, options, end_index) - vault = vault_remixed.create_bundle_room(random, content, options) abandoned_joja_mart = abandoned_joja_mart_remixed.create_bundle_room(random, content, options) - raccoon = raccoon_remixed.create_bundle_room(random, content, options) + raccoon = giant_stump_remixed.create_bundle_room(random, content, options) fix_raccoon_bundle_names(raccoon) return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart, raccoon] -def create_room_from_bundles(template: BundleRoomTemplate, all_bundles: List[Bundle], end_index: int) -> Tuple[BundleRoom, int]: +def create_room_from_bundles(template: BundleRoomTemplate, all_bundles: List[Bundle], options: StardewValleyOptions, end_index: int) -> Tuple[BundleRoom, int]: start_index = end_index - end_index += template.number_bundles + end_index += template.number_bundles + options.bundle_per_room.value return BundleRoom(template.name, all_bundles[start_index:end_index]), end_index @@ -122,3 +133,31 @@ def fix_raccoon_bundle_names(raccoon): for i in range(len(raccoon.bundles)): raccoon_bundle = raccoon.bundles[i] raccoon_bundle.name = f"Raccoon Request {i + 1}" + + +def get_trash_bear_requests(random: Random, content: StardewContent, options: StardewValleyOptions) -> Dict[str, List[str]]: + trash_bear_requests = dict() + num_per_type = 2 + if options.bundle_price < 0: + num_per_type = 1 + elif options.bundle_price > 0: + num_per_type = min(4, num_per_type + options.bundle_price) + + trash_bear_requests["Foraging"] = pick_trash_bear_items(ItemTag.FORAGE, content, num_per_type, random) + if options.bundle_per_room >= 0: + # Cooking items are not in content packs yet. This can be simplified once they are + # trash_bear_requests["Cooking"] = pick_trash_bear_items(ItemTag.COOKING, content, num_per_type, random) + trash_bear_requests["Cooking"] = random.sample( + [recipe.meal for recipe in all_cooking_recipes if not recipe.content_pack or content.is_enabled(recipe.content_pack)], num_per_type) + if options.bundle_per_room >= 1: + trash_bear_requests["Farming"] = pick_trash_bear_items(ItemTag.CROPSANITY, content, num_per_type, random) + if options.bundle_per_room >= 2: + # Fish items are not tagged properly in content packs yet. This can be simplified once they are + # trash_bear_requests["Fishing"] = pick_trash_bear_items(ItemTag.FISH, content, num_per_type, random) + trash_bear_requests["Fishing"] = random.sample([fish for fish in content.fishes], num_per_type) + return trash_bear_requests + + +def pick_trash_bear_items(item_tag: ItemTag, content: StardewContent, number_items: int, random: Random): + forage_items = [item.name for item in content.find_tagged_items(item_tag)] + return random.sample(forage_items, number_items) diff --git a/worlds/stardew_valley/client.py b/worlds/stardew_valley/client.py new file mode 100644 index 000000000000..f4a581fd4d1d --- /dev/null +++ b/worlds/stardew_valley/client.py @@ -0,0 +1,284 @@ +from __future__ import annotations + +import asyncio +import re +# webserver imports +import urllib.parse +from collections.abc import Iterable + +import Utils +from BaseClasses import CollectionState, Location +from CommonClient import logger, get_base_parser, gui_enabled, server_loop +from MultiServer import mark_raw +from NetUtils import JSONMessagePart +from kvui import CommandPromptTextInput +from . import StardewValleyWorld +from .logic.logic import StardewLogic +from .stardew_rule.rule_explain import explain, ExplainMode, RuleExplanation + +try: + from worlds.tracker.TrackerClient import TrackerGameContext, TrackerCommandProcessor as ClientCommandProcessor, UT_VERSION # noqa + from worlds.tracker.TrackerCore import TrackerCore + + tracker_loaded = True +except ImportError as e: + logger.error(e) + from CommonClient import CommonContext, ClientCommandProcessor + + TrackerCore = object + + + class TrackerGameContextMixin: + """Expecting the TrackerGameContext to have these methods.""" + tracker_core: TrackerCore + + def make_gui(self, manager): + ... + + def run_generator(self): + ... + + + class TrackerGameContext(CommonContext, TrackerGameContextMixin): + pass + + + tracker_loaded = False + UT_VERSION = "Not found" + + +class StardewCommandProcessor(ClientCommandProcessor): + ctx: StardewClientContext + + @mark_raw + def _cmd_explain(self, location: str = ""): + """Explain the logic behind a location.""" + logic = self.ctx.logic + if logic is None: + return + + try: + rule = logic.region.can_reach_location(location) + expl = explain(rule, self.ctx.current_state, expected=None, mode=ExplainMode.CLIENT) + except KeyError: + + result, usable, response = Utils.get_intended_text(location, [loc.name for loc in self.ctx.all_locations]) + if usable: + rule = logic.region.can_reach_location(result) + expl = explain(rule, self.ctx.current_state, expected=None, mode=ExplainMode.CLIENT) + else: + self.ctx.ui.last_autofillable_command = "/explain" + self.output(response) + return + + self.ctx.previous_explanation = expl + self.ctx.ui.print_json(parse_explanation(expl)) + + @mark_raw + def _cmd_explain_item(self, item: str = ""): + """Explain the logic behind a game item.""" + logic = self.ctx.logic + if logic is None: + return + + result, usable, response = Utils.get_intended_text(item, logic.registry.item_rules.keys()) + if usable: + rule = logic.has(result) + expl = explain(rule, self.ctx.current_state, expected=None, mode=ExplainMode.CLIENT) + else: + self.ctx.ui.last_autofillable_command = "/explain_item" + self.output(response) + return + + self.ctx.previous_explanation = expl + self.ctx.ui.print_json(parse_explanation(expl)) + + @mark_raw + def _cmd_explain_missing(self, location: str = ""): + """Explain what is missing for a location to be in logic. It explains the logic behind a location, while skipping the rules that are already satisfied.""" + self.__explain("/explain_missing", location, expected=True) + + @mark_raw + def _cmd_explain_how(self, location: str = ""): + """Explain how a location is in logic. It explains the logic behind the location, while skipping the rules that are not satisfied.""" + self.__explain("/explain_how", location, expected=False) + + def __explain(self, command: str, location: str, expected: bool | None = None): + logic = self.ctx.logic + if logic is None: + return + + try: + rule = logic.region.can_reach_location(location) + expl = explain(rule, self.ctx.current_state, expected=expected, mode=ExplainMode.CLIENT) + except KeyError: + + result, usable, response = Utils.get_intended_text(location, [loc.name for loc in self.ctx.all_locations]) + if usable: + rule = logic.region.can_reach_location(result) + expl = explain(rule, self.ctx.current_state, expected=expected, mode=ExplainMode.CLIENT) + else: + self.ctx.ui.last_autofillable_command = command + self.output(response) + return + + self.ctx.previous_explanation = expl + self.ctx.ui.print_json(parse_explanation(expl)) + + @mark_raw + def _cmd_more(self, index: str = ""): + """Will tell you what's missing to consider a location in logic.""" + if self.ctx.previous_explanation is None: + self.output("No previous explanation found.") + return + + try: + expl = self.ctx.previous_explanation.more(int(index)) + except (ValueError, IndexError): + self.output("Which previous rule do you want to explained?") + self.ctx.ui.last_autofillable_command = "/more" + for i, rule in enumerate(self.ctx.previous_explanation.more_explanations): + # TODO handle autofillable commands + self.output(f"/more {i} -> {str(rule)})") + return + + self.ctx.previous_explanation = expl + self.ctx.ui.print_json(parse_explanation(expl)) + + if not tracker_loaded: + del _cmd_explain + del _cmd_explain_missing + + +class StardewClientContext(TrackerGameContext): + game = "Stardew Valley" + command_processor = StardewCommandProcessor + previous_explanation: RuleExplanation | None = None + + def make_gui(self): + ui = super().make_gui() # before the kivy imports so kvui gets loaded first + + class StardewManager(ui): + base_title = f"Stardew Valley Tracker with UT {UT_VERSION} for AP version" # core appends ap version so this works + ctx: StardewClientContext + + def build(self): + container = super().build() + if not tracker_loaded: + logger.info("To enable the tracker page, install Universal Tracker.") + + # Until self.ctx.ui.last_autofillable_command allows for / commands, this is needed to remove the "!" before the /commands when using intended text autofill. + def on_text_remove_hardcoded_exclamation_mark_garbage(textinput: CommandPromptTextInput, text: str) -> None: + if text.startswith("!/"): + textinput.text = text[1:] + + self.textinput.bind(text=on_text_remove_hardcoded_exclamation_mark_garbage) + + return container + + return StardewManager + + @property + def logic(self) -> StardewLogic | None: + if self.tracker_core.get_current_world() is None: + logger.warning("Internal logic was not able to load, check your yamls and relaunch.") + return None + + if self.game != "Stardew Valley": + logger.warning(f"Please connect to a slot with explainable logic (not {self.game}).") + return None + + return self.tracker_core.get_current_world().logic + + @property + def current_state(self) -> CollectionState: + return self.tracker_core.updateTracker().state + + @property + def world(self) -> StardewValleyWorld: + return self.tracker_core.get_current_world() + + @property + def all_locations(self) -> Iterable[Location]: + return self.tracker_core.multiworld.get_locations(self.tracker_core.player_id) + + +def parse_explanation(explanation: RuleExplanation) -> list[JSONMessagePart]: + # Split the explanation in parts, by isolating all the delimiters, being \(, \), & , -> , | , \d+x , \[ , \] , \(\w+\), \n\s* + result_regex = r"(\(|\)| & | -> | \| |\d+x | \[|\](?: ->)?\s*| \(\w+\)|\n\s*)" + splits = re.split(result_regex, str(explanation).strip()) + + messages = [] + for s in splits: + if len(s) == 0: + continue + + if s == "True": + messages.append({"type": "color", "color": "green", "text": s}) + elif s == "False": + messages.append({"type": "color", "color": "salmon", "text": s}) + elif s.startswith("Reach Location "): + messages.append({"type": "text", "text": "Reach Location "}) + messages.append({"type": "location_name", "text": s[15:]}) + elif s.startswith("Reach Entrance "): + messages.append({"type": "text", "text": "Reach Entrance "}) + messages.append({"type": "entrance_name", "text": s[15:]}) + elif s.startswith("Reach Region "): + messages.append({"type": "text", "text": "Reach Region "}) + messages.append({"type": "color", "color": "yellow", "text": s[13:]}) + elif s.startswith("Received event "): + messages.append({"type": "text", "text": "Received event "}) + messages.append({"type": "item_name", "text": s[15:]}) + elif s.startswith("Received "): + messages.append({"type": "text", "text": "Received "}) + messages.append({"type": "item_name", "flags": 0b001, "text": s[9:]}) + elif s.startswith("Has "): + if s[4].isdigit(): + messages.append({"type": "text", "text": "Has "}) + digit_end = re.search(r"\D", s[4:]) + digit = s[4:4 + digit_end.start()] + messages.append({"type": "color", "color": "cyan", "text": digit}) + messages.append({"type": "text", "text": s[4 + digit_end.start():]}) + + else: + messages.append({"text": s, "type": "text"}) + else: + messages.append({"text": s, "type": "text"}) + + return messages + + +async def main(args): + ctx = StardewClientContext(args.connect, args.password) + + ctx.auth = args.name + ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop") + + if tracker_loaded: + ctx.run_generator() + else: + logger.warning("Could not find Universal Tracker.") + + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + + await ctx.exit_event.wait() + await ctx.shutdown() + + +def launch(*args): + parser = get_base_parser(description="Gameless Archipelago Client, for text interfacing.") + parser.add_argument('--name', default=None, help="Slot Name to connect as.") + parser.add_argument("url", nargs="?", help="Archipelago connection url") + args = parser.parse_args(args) + + if args.url: + url = urllib.parse.urlparse(args.url) + args.connect = url.netloc + if url.username: + args.name = urllib.parse.unquote(url.username) + if url.password: + args.password = urllib.parse.unquote(url.password) + + asyncio.run(main(args)) diff --git a/worlds/stardew_valley/content/__init__.py b/worlds/stardew_valley/content/__init__.py index 5f7d4a5fc567..de4c2c075ab9 100644 --- a/worlds/stardew_valley/content/__init__.py +++ b/worlds/stardew_valley/content/__init__.py @@ -1,8 +1,9 @@ from . import content_packs -from .feature import cropsanity, friendsanity, fishsanity, booksanity, building_progression, skill_progression, tool_progression +from .feature import cropsanity, friendsanity, fishsanity, booksanity, building_progression, skill_progression, tool_progression, hatsanity, museumsanity from .game_content import ContentPack, StardewContent, StardewFeatures from .unpacking import unpack_content from .. import options +from ..strings.ap_names.ap_option_names import StartWithoutOptionName from ..strings.building_names import Building @@ -34,8 +35,10 @@ def choose_features(player_options: options.StardewValleyOptions) -> StardewFeat choose_cropsanity(player_options.cropsanity), choose_fishsanity(player_options.fishsanity), choose_friendsanity(player_options.friendsanity, player_options.friendsanity_heart_size), + choose_hatsanity(player_options.hatsanity), + choose_museumsanity(player_options.museumsanity), choose_skill_progression(player_options.skill_progression), - choose_tool_progression(player_options.tool_progression, player_options.skill_progression), + choose_tool_progression(player_options.tool_progression, player_options.skill_progression, player_options.start_without), ) @@ -56,6 +59,33 @@ def choose_booksanity(booksanity_option: options.Booksanity) -> booksanity.Books return booksanity_feature +def choose_building_progression(building_option: options.BuildingProgression, + farm_type_option: options.FarmType) -> building_progression.BuildingProgressionFeature: + starting_buildings = {Building.farm_house, Building.pet_bowl, Building.shipping_bin} + + if farm_type_option == options.FarmType.option_meadowlands: + starting_buildings.add(Building.coop) + + if (building_option == options.BuildingProgression.option_vanilla + or building_option == options.BuildingProgression.option_vanilla_cheap + or building_option == options.BuildingProgression.option_vanilla_very_cheap): + return building_progression.BuildingProgressionVanilla( + starting_buildings=starting_buildings, + ) + + starting_buildings.remove(Building.shipping_bin) + starting_buildings.remove(Building.pet_bowl) + + if (building_option == options.BuildingProgression.option_progressive + or building_option == options.BuildingProgression.option_progressive_cheap + or building_option == options.BuildingProgression.option_progressive_very_cheap): + return building_progression.BuildingProgressionProgressive( + starting_buildings=starting_buildings, + ) + + raise ValueError(f"No building progression feature mapped to {str(building_option.value)}") + + cropsanity_by_option = { options.Cropsanity.option_disabled: cropsanity.CropsanityDisabled(), options.Cropsanity.option_enabled: cropsanity.CropsanityEnabled(), @@ -111,30 +141,27 @@ def choose_friendsanity(friendsanity_option: options.Friendsanity, heart_size: o raise ValueError(f"No friendsanity feature mapped to {str(friendsanity_option.value)}") -def choose_building_progression(building_option: options.BuildingProgression, - farm_type_option: options.FarmType) -> building_progression.BuildingProgressionFeature: - starting_buildings = {Building.farm_house, Building.pet_bowl, Building.shipping_bin} +def choose_hatsanity(hat_option: options.Hatsanity) -> hatsanity.HatsanityFeature: + if hat_option == options.Hatsanity.preset_none: + return hatsanity.HatsanityNone() - if farm_type_option == options.FarmType.option_meadowlands: - starting_buildings.add(Building.coop) + return hatsanity.HatsanityHats(enabled_hats=frozenset(hat_option.value)) - if (building_option == options.BuildingProgression.option_vanilla - or building_option == options.BuildingProgression.option_vanilla_cheap - or building_option == options.BuildingProgression.option_vanilla_very_cheap): - return building_progression.BuildingProgressionVanilla( - starting_buildings=starting_buildings, - ) - starting_buildings.remove(Building.shipping_bin) +def choose_museumsanity(museumsanity_option: options.Museumsanity) -> museumsanity.MuseumsanityFeature: + if museumsanity_option == options.Museumsanity.option_none: + return museumsanity.MuseumsanityNone() - if (building_option == options.BuildingProgression.option_progressive - or building_option == options.BuildingProgression.option_progressive_cheap - or building_option == options.BuildingProgression.option_progressive_very_cheap): - return building_progression.BuildingProgressionProgressive( - starting_buildings=starting_buildings, - ) + if museumsanity_option == options.Museumsanity.option_milestones: + return museumsanity.MuseumsanityMilestones() - raise ValueError(f"No building progression feature mapped to {str(building_option.value)}") + if museumsanity_option == options.Museumsanity.option_randomized: + return museumsanity.MuseumsanityRandomized() + + if museumsanity_option == options.Museumsanity.option_all: + return museumsanity.MuseumsanityAll() + + raise ValueError(f"No museumsanity feature mapped to {str(museumsanity_option.value)}") skill_progression_by_option = { @@ -153,16 +180,17 @@ def choose_skill_progression(skill_progression_option: options.SkillProgression) return skill_progression_feature -def choose_tool_progression(tool_option: options.ToolProgression, skill_option: options.SkillProgression) -> tool_progression.ToolProgressionFeature: +def choose_tool_progression(tool_option: options.ToolProgression, skill_option: options.SkillProgression, start_without_option: options.StartWithout) -> tool_progression.ToolProgressionFeature: if tool_option.is_vanilla: return tool_progression.ToolProgressionVanilla() - tools_distribution = tool_progression.get_tools_distribution( - progressive_tools_enabled=True, - skill_masteries_enabled=skill_option == options.SkillProgression.option_progressive_with_masteries, - ) - if tool_option.is_progressive: - return tool_progression.ToolProgressionProgressive(tools_distribution) + starting_tools, tools_distribution = tool_progression.get_tools_distribution( + progressive_tools_enabled=True, + skill_masteries_enabled=skill_option == options.SkillProgression.option_progressive_with_masteries, + no_starting_tools_enabled=bool(StartWithoutOptionName.tools in start_without_option), + ) + + return tool_progression.ToolProgressionProgressive(starting_tools, tools_distribution) raise ValueError(f"No tool progression feature mapped to {str(tool_option.value)}") diff --git a/worlds/stardew_valley/content/content_packs.py b/worlds/stardew_valley/content/content_packs.py index fb8df8c70cba..8358f1b7cfd7 100644 --- a/worlds/stardew_valley/content/content_packs.py +++ b/worlds/stardew_valley/content/content_packs.py @@ -11,14 +11,6 @@ from .vanilla.the_farm import the_farm from .vanilla.the_mines import the_mines -assert base_game -assert ginger_island_content_pack -assert pelican_town -assert qi_board_content_pack -assert the_desert -assert the_farm -assert the_mines - # Dynamically register everything currently in the mods folder. This would ideally be done through a metaclass, but I have not looked into that yet. mod_modules = pkgutil.iter_modules(mods.__path__) @@ -28,4 +20,13 @@ module = importlib.import_module("." + module_name, mods.__name__) loaded_modules[module_name] = module -assert by_mod +vanilla_content_pack_names = frozenset({ + base_game.name, + ginger_island_content_pack.name, + pelican_town.name, + qi_board_content_pack.name, + the_desert.name, + the_farm.name, + the_mines.name +}) +all_content_pack_names = vanilla_content_pack_names | frozenset(by_mod.keys()) diff --git a/worlds/stardew_valley/content/feature/__init__.py b/worlds/stardew_valley/content/feature/__init__.py index b2a88286c6b7..d9e09171ed63 100644 --- a/worlds/stardew_valley/content/feature/__init__.py +++ b/worlds/stardew_valley/content/feature/__init__.py @@ -1,7 +1,31 @@ -from . import booksanity -from . import building_progression -from . import cropsanity -from . import fishsanity -from . import friendsanity -from . import skill_progression -from . import tool_progression +from . import booksanity, building_progression, cropsanity, fishsanity, friendsanity, hatsanity, museumsanity, skill_progression, tool_progression +from .booksanity import BooksanityFeature +from .building_progression import BuildingProgressionFeature +from .cropsanity import CropsanityFeature +from .fishsanity import FishsanityFeature +from .friendsanity import FriendsanityFeature +from .hatsanity import HatsanityFeature +from .museumsanity import MuseumsanityFeature +from .skill_progression import SkillProgressionFeature +from .tool_progression import ToolProgressionFeature + +__exports__ = [ + "booksanity", + "BooksanityFeature", + "building_progression", + "BuildingProgressionFeature", + "cropsanity", + "CropsanityFeature", + "fishsanity", + "FishsanityFeature", + "friendsanity", + "FriendsanityFeature", + "hatsanity", + "HatsanityFeature", + "museumsanity", + "MuseumsanityFeature", + "skill_progression", + "SkillProgressionFeature", + "tool_progression" + "ToolProgressionFeature", +] diff --git a/worlds/stardew_valley/content/feature/base.py b/worlds/stardew_valley/content/feature/base.py new file mode 100644 index 000000000000..4adc944c04f8 --- /dev/null +++ b/worlds/stardew_valley/content/feature/base.py @@ -0,0 +1,71 @@ +import inspect +from abc import ABC +from collections.abc import Mapping +from typing import TYPE_CHECKING, Protocol + +from ...data.game_item import Source, Requirement + +if TYPE_CHECKING: + from ... import StardewContent + + +class DisableSourceHook(Protocol): + def __call__(self, source: Source, /, *, content: "StardewContent") -> bool: + """Return True if the source should be disabled by this feature.""" + ... + + +class DisableRequirementHook(Protocol): + def __call__(self, requirement: Requirement, /, *, content: "StardewContent") -> bool: + """Return True if the requirement should be disabled by this feature.""" + ... + + +def wrap_optional_content_arg(hook): + """Wraps a hook to ensure it has the correct signature.""" + if "content" in hook.__annotations__: + return hook + + def wrapper(*args, content: "StardewContent", **kwargs): + return hook(*args, **kwargs) + + return wrapper + + +class FeatureBase(ABC): + + @property + def disable_source_hooks(self) -> Mapping[type[Source], DisableSourceHook]: + """All hooks to call when a source is created to check if it has to be disabled by this feature.""" + disable_source_hooks = {} + for attribute_name in dir(self): + if not attribute_name.startswith("_disable_") or not callable((attribute := getattr(self, attribute_name))): + continue + + sig = inspect.signature(attribute) + + source_param = sig.parameters.get("source") + if source_param is not None: + source_type = source_param.annotation + disable_source_hooks[source_type] = wrap_optional_content_arg(attribute) + continue + + return disable_source_hooks + + @property + def disable_requirement_hooks(self) -> Mapping[type[Requirement], DisableRequirementHook]: + """All hooks to call when a requirement is created to check if it has to be disabled by this feature.""" + disable_requirement_hooks = {} + for attribute_name in dir(self): + if not attribute_name.startswith("_disable_") or not callable((attribute := getattr(self, attribute_name))): + continue + + sig = inspect.signature(attribute) + + requirement_param = sig.parameters.get("requirement") + if requirement_param is not None: + requirement_type = requirement_param.annotation + disable_requirement_hooks[requirement_type] = wrap_optional_content_arg(attribute) + continue + + return disable_requirement_hooks diff --git a/worlds/stardew_valley/content/feature/booksanity.py b/worlds/stardew_valley/content/feature/booksanity.py index 5eade5932535..b5e7319432e1 100644 --- a/worlds/stardew_valley/content/feature/booksanity.py +++ b/worlds/stardew_valley/content/feature/booksanity.py @@ -1,6 +1,8 @@ from abc import ABC, abstractmethod -from typing import ClassVar, Optional, Iterable +from collections.abc import Iterable +from typing import ClassVar +from .base import FeatureBase from ...data.game_item import GameItem, ItemTag from ...strings.book_names import ordered_lost_books @@ -16,14 +18,14 @@ def to_location_name(book: str) -> str: return location_prefix + book -def extract_book_from_location_name(location_name: str) -> Optional[str]: +def extract_book_from_location_name(location_name: str) -> str | None: if not location_name.startswith(location_prefix): return None return location_name[len(location_prefix):] -class BooksanityFeature(ABC): +class BooksanityFeature(FeatureBase, ABC): is_enabled: ClassVar[bool] to_item_name = staticmethod(to_item_name) diff --git a/worlds/stardew_valley/content/feature/building_progression.py b/worlds/stardew_valley/content/feature/building_progression.py index 0d317aa0187a..43202cd6df30 100644 --- a/worlds/stardew_valley/content/feature/building_progression.py +++ b/worlds/stardew_valley/content/feature/building_progression.py @@ -1,7 +1,8 @@ from abc import ABC from dataclasses import dataclass -from typing import ClassVar, Set, Tuple +from typing import ClassVar +from .base import FeatureBase from ...strings.building_names import Building progressive_house = "Progressive House" @@ -15,7 +16,7 @@ } -def to_progressive_item(building: str) -> Tuple[str, int]: +def to_progressive_item(building: str) -> tuple[str, int]: """Return the name of the progressive item and its quantity required to unlock the building. """ if building in [Building.coop, Building.barn, Building.shed]: @@ -35,9 +36,9 @@ def to_location_name(building: str) -> str: @dataclass(frozen=True) -class BuildingProgressionFeature(ABC): +class BuildingProgressionFeature(FeatureBase, ABC): is_progressive: ClassVar[bool] - starting_buildings: Set[str] + starting_buildings: set[str] to_progressive_item = staticmethod(to_progressive_item) progressive_house = progressive_house diff --git a/worlds/stardew_valley/content/feature/cropsanity.py b/worlds/stardew_valley/content/feature/cropsanity.py index 18ef370815ee..7ca8c7370c25 100644 --- a/worlds/stardew_valley/content/feature/cropsanity.py +++ b/worlds/stardew_valley/content/feature/cropsanity.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod -from typing import ClassVar, Optional +from typing import ClassVar +from .base import FeatureBase from ...data.game_item import GameItem, ItemTag location_prefix = "Harvest " @@ -10,14 +11,14 @@ def to_location_name(crop: str) -> str: return location_prefix + crop -def extract_crop_from_location_name(location_name: str) -> Optional[str]: +def extract_crop_from_location_name(location_name: str) -> str | None: if not location_name.startswith(location_prefix): return None return location_name[len(location_prefix):] -class CropsanityFeature(ABC): +class CropsanityFeature(FeatureBase, ABC): is_enabled: ClassVar[bool] to_location_name = staticmethod(to_location_name) diff --git a/worlds/stardew_valley/content/feature/fishsanity.py b/worlds/stardew_valley/content/feature/fishsanity.py index 02f9a632a873..6f8da513aa4f 100644 --- a/worlds/stardew_valley/content/feature/fishsanity.py +++ b/worlds/stardew_valley/content/feature/fishsanity.py @@ -1,7 +1,8 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import ClassVar, Optional +from typing import ClassVar +from .base import FeatureBase from ...data.fish_data import FishItem from ...strings.fish_names import Fish @@ -12,7 +13,7 @@ def to_location_name(fish: str) -> str: return location_prefix + fish -def extract_fish_from_location_name(location_name: str) -> Optional[str]: +def extract_fish_from_location_name(location_name: str) -> str | None: if not location_name.startswith(location_prefix): return None @@ -20,7 +21,7 @@ def extract_fish_from_location_name(location_name: str) -> Optional[str]: @dataclass(frozen=True) -class FishsanityFeature(ABC): +class FishsanityFeature(FeatureBase, ABC): is_enabled: ClassVar[bool] randomization_ratio: float = 1 diff --git a/worlds/stardew_valley/content/feature/friendsanity.py b/worlds/stardew_valley/content/feature/friendsanity.py index 3e1581b4e2f1..117c474c8a38 100644 --- a/worlds/stardew_valley/content/feature/friendsanity.py +++ b/worlds/stardew_valley/content/feature/friendsanity.py @@ -1,8 +1,9 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from functools import lru_cache -from typing import Optional, Tuple, ClassVar +from typing import ClassVar +from .base import FeatureBase from ...data.villagers_data import Villager from ...strings.villager_names import NPC @@ -21,14 +22,14 @@ def to_location_name(npc_name: str, heart: int) -> str: pet_heart_item_name = to_item_name(NPC.pet) -def extract_npc_from_item_name(item_name: str) -> Optional[str]: +def extract_npc_from_item_name(item_name: str) -> str | None: if not item_name.endswith(suffix): return None return item_name[:-len(suffix)] -def extract_npc_from_location_name(location_name: str) -> Tuple[Optional[str], int]: +def extract_npc_from_location_name(location_name: str) -> tuple[str | None, int]: if not location_name.endswith(suffix): return None, 0 @@ -38,12 +39,12 @@ def extract_npc_from_location_name(location_name: str) -> Tuple[Optional[str], i @lru_cache(maxsize=32) # Should not go pass 32 values if every friendsanity options are in the multi world -def get_heart_steps(max_heart: int, heart_size: int) -> Tuple[int, ...]: +def get_heart_steps(max_heart: int, heart_size: int) -> tuple[int, ...]: return tuple(range(heart_size, max_heart + 1, heart_size)) + ((max_heart,) if max_heart % heart_size else ()) @dataclass(frozen=True) -class FriendsanityFeature(ABC): +class FriendsanityFeature(FeatureBase, ABC): is_enabled: ClassVar[bool] heart_size: int @@ -55,7 +56,7 @@ class FriendsanityFeature(ABC): extract_npc_from_location_name = staticmethod(extract_npc_from_location_name) @abstractmethod - def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: + def get_randomized_hearts(self, villager: Villager) -> tuple[int, ...]: ... @property @@ -63,7 +64,7 @@ def is_pet_randomized(self): return bool(self.get_pet_randomized_hearts()) @abstractmethod - def get_pet_randomized_hearts(self) -> Tuple[int, ...]: + def get_pet_randomized_hearts(self) -> tuple[int, ...]: ... @@ -73,10 +74,10 @@ class FriendsanityNone(FriendsanityFeature): def __init__(self): super().__init__(1) - def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: + def get_randomized_hearts(self, villager: Villager) -> tuple[int, ...]: return () - def get_pet_randomized_hearts(self) -> Tuple[int, ...]: + def get_pet_randomized_hearts(self) -> tuple[int, ...]: return () @@ -84,13 +85,13 @@ def get_pet_randomized_hearts(self) -> Tuple[int, ...]: class FriendsanityBachelors(FriendsanityFeature): is_enabled = True - def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: + def get_randomized_hearts(self, villager: Villager) -> tuple[int, ...]: if not villager.bachelor: return () return get_heart_steps(8, self.heart_size) - def get_pet_randomized_hearts(self) -> Tuple[int, ...]: + def get_pet_randomized_hearts(self) -> tuple[int, ...]: return () @@ -98,7 +99,7 @@ def get_pet_randomized_hearts(self) -> Tuple[int, ...]: class FriendsanityStartingNpc(FriendsanityFeature): is_enabled = True - def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: + def get_randomized_hearts(self, villager: Villager) -> tuple[int, ...]: if not villager.available: return () @@ -107,7 +108,7 @@ def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: return get_heart_steps(10, self.heart_size) - def get_pet_randomized_hearts(self) -> Tuple[int, ...]: + def get_pet_randomized_hearts(self) -> tuple[int, ...]: return get_heart_steps(5, self.heart_size) @@ -115,13 +116,13 @@ def get_pet_randomized_hearts(self) -> Tuple[int, ...]: class FriendsanityAll(FriendsanityFeature): is_enabled = True - def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: + def get_randomized_hearts(self, villager: Villager) -> tuple[int, ...]: if villager.bachelor: return get_heart_steps(8, self.heart_size) return get_heart_steps(10, self.heart_size) - def get_pet_randomized_hearts(self) -> Tuple[int, ...]: + def get_pet_randomized_hearts(self) -> tuple[int, ...]: return get_heart_steps(5, self.heart_size) @@ -129,11 +130,11 @@ def get_pet_randomized_hearts(self) -> Tuple[int, ...]: class FriendsanityAllWithMarriage(FriendsanityFeature): is_enabled = True - def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]: + def get_randomized_hearts(self, villager: Villager) -> tuple[int, ...]: if villager.bachelor: return get_heart_steps(14, self.heart_size) return get_heart_steps(10, self.heart_size) - def get_pet_randomized_hearts(self) -> Tuple[int, ...]: + def get_pet_randomized_hearts(self) -> tuple[int, ...]: return get_heart_steps(5, self.heart_size) diff --git a/worlds/stardew_valley/content/feature/hatsanity.py b/worlds/stardew_valley/content/feature/hatsanity.py new file mode 100644 index 000000000000..13dd41a9bbcc --- /dev/null +++ b/worlds/stardew_valley/content/feature/hatsanity.py @@ -0,0 +1,52 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import ClassVar + +from .base import FeatureBase +from ...data.hats_data import HatItem + +wear_prefix = "Wear " + + +def to_location_name(hat: str | HatItem) -> str: + if isinstance(hat, HatItem): + hat = hat.name + return f"{wear_prefix}{hat}" + + +def extract_hat_from_location_name(location_name: str) -> str | None: + if not location_name.startswith(wear_prefix): + return None + + return location_name[len(wear_prefix):] + + +@dataclass(frozen=True) +class HatsanityFeature(FeatureBase, ABC): + is_enabled: ClassVar[bool] + + to_location_name = staticmethod(to_location_name) + extract_hat_from_location_name = staticmethod(extract_hat_from_location_name) + + @abstractmethod + def is_included(self, hat: HatItem) -> bool: + ... + + +class HatsanityNone(HatsanityFeature): + is_enabled = False + + def is_included(self, hat: HatItem) -> bool: + return False + + +@dataclass(frozen=True) +class HatsanityHats(HatsanityFeature): + is_enabled = True + enabled_hats: frozenset[str] + + def is_included(self, hat: HatItem) -> bool: + for difficulty in hat.difficulty: + if difficulty not in self.enabled_hats: + return False + return True diff --git a/worlds/stardew_valley/content/feature/museumsanity.py b/worlds/stardew_valley/content/feature/museumsanity.py new file mode 100644 index 000000000000..6cde0c6f36cf --- /dev/null +++ b/worlds/stardew_valley/content/feature/museumsanity.py @@ -0,0 +1,38 @@ +from abc import ABC +from dataclasses import dataclass +from typing import ClassVar + +from .base import FeatureBase +from ...data.requirement import MuseumCompletionRequirement + + +@dataclass(frozen=True) +class MuseumsanityFeature(FeatureBase, ABC): + is_enabled: ClassVar[bool] + + +class MuseumsanityNone(MuseumsanityFeature): + is_enabled = False + + @staticmethod + def _disable_museum_completion_requirement(requirement: MuseumCompletionRequirement) -> bool: + return True + + +@dataclass(frozen=True) +class MuseumsanityMilestones(MuseumsanityFeature): + is_enabled = True + + +@dataclass(frozen=True) +class MuseumsanityRandomized(MuseumsanityFeature): + is_enabled = True + amount_of_randomized_donations: int = 40 + + def _disable_museum_completion_requirement(self, requirement: MuseumCompletionRequirement) -> bool: + return requirement.number_donated > self.amount_of_randomized_donations + + +@dataclass(frozen=True) +class MuseumsanityAll(MuseumsanityFeature): + is_enabled = True diff --git a/worlds/stardew_valley/content/feature/skill_progression.py b/worlds/stardew_valley/content/feature/skill_progression.py index 1325d4b35ff2..805728259fb5 100644 --- a/worlds/stardew_valley/content/feature/skill_progression.py +++ b/worlds/stardew_valley/content/feature/skill_progression.py @@ -1,15 +1,17 @@ from abc import ABC, abstractmethod -from typing import ClassVar, Iterable, Tuple +from collections.abc import Iterable +from typing import ClassVar +from .base import FeatureBase from ...data.skill import Skill -class SkillProgressionFeature(ABC): +class SkillProgressionFeature(FeatureBase, ABC): is_progressive: ClassVar[bool] are_masteries_shuffled: ClassVar[bool] @abstractmethod - def get_randomized_level_names_by_level(self, skill: Skill) -> Iterable[Tuple[int, str]]: + def get_randomized_level_names_by_level(self, skill: Skill) -> Iterable[tuple[int, str]]: ... @abstractmethod @@ -21,7 +23,7 @@ class SkillProgressionVanilla(SkillProgressionFeature): is_progressive = False are_masteries_shuffled = False - def get_randomized_level_names_by_level(self, skill: Skill) -> Iterable[Tuple[int, str]]: + def get_randomized_level_names_by_level(self, skill: Skill) -> Iterable[tuple[int, str]]: return () def is_mastery_randomized(self, skill: Skill) -> bool: @@ -32,7 +34,7 @@ class SkillProgressionProgressive(SkillProgressionFeature): is_progressive = True are_masteries_shuffled = False - def get_randomized_level_names_by_level(self, skill: Skill) -> Iterable[Tuple[int, str]]: + def get_randomized_level_names_by_level(self, skill: Skill) -> Iterable[tuple[int, str]]: return skill.level_names_by_level def is_mastery_randomized(self, skill: Skill) -> bool: diff --git a/worlds/stardew_valley/content/feature/special_order_locations.py b/worlds/stardew_valley/content/feature/special_order_locations.py new file mode 100644 index 000000000000..8dfe143fe236 --- /dev/null +++ b/worlds/stardew_valley/content/feature/special_order_locations.py @@ -0,0 +1,5 @@ +def get_qi_gem_amount(item_name: str) -> int: + amount = item_name.replace(" Qi Gems", "") + if amount.isdigit(): + return int(amount) + return 0 diff --git a/worlds/stardew_valley/content/feature/tool_progression.py b/worlds/stardew_valley/content/feature/tool_progression.py index d5fe5cef99de..fb15064a6ee3 100644 --- a/worlds/stardew_valley/content/feature/tool_progression.py +++ b/worlds/stardew_valley/content/feature/tool_progression.py @@ -6,14 +6,19 @@ from types import MappingProxyType from typing import ClassVar +from .base import FeatureBase from ...strings.tool_names import Tool -def to_progressive_item(tool: str) -> str: +def to_progressive_item_name(tool: str) -> str: """Return the name of the progressive item.""" return f"Progressive {tool}" +def to_upgrade_location_name(tool: str, material: str) -> str: + return f"{material} {tool} Upgrade" + + # The golden scythe is always randomized VANILLA_TOOL_DISTRIBUTION = MappingProxyType({ Tool.scythe: 1, @@ -29,6 +34,17 @@ def to_progressive_item(tool: str) -> str: Tool.fishing_rod: 4, }) +VANILLA_STARTING_TOOLS_DISTRIBUTION = MappingProxyType({ + Tool.scythe: 1, + Tool.axe: 1, + Tool.hoe: 1, + Tool.pickaxe: 1, + Tool.pan: 0, + Tool.trash_can: 1, + Tool.watering_can: 1, + Tool.fishing_rod: 0, +}) + # Masteries add another tier to the scythe and the fishing rod SKILL_MASTERIES_TOOL_DISTRIBUTION = MappingProxyType({ Tool.scythe: 1, @@ -37,8 +53,10 @@ def to_progressive_item(tool: str) -> str: @cache -def get_tools_distribution(progressive_tools_enabled: bool, skill_masteries_enabled: bool) -> Mapping[str, int]: +def get_tools_distribution(progressive_tools_enabled: bool, skill_masteries_enabled: bool, no_starting_tools_enabled: bool) \ + -> tuple[Mapping[str, int], Mapping[str, int]]: distribution = Counter(VANILLA_TOOL_DISTRIBUTION) + starting = Counter(VANILLA_STARTING_TOOLS_DISTRIBUTION) if progressive_tools_enabled: distribution += PROGRESSIVE_TOOL_DISTRIBUTION @@ -46,21 +64,31 @@ def get_tools_distribution(progressive_tools_enabled: bool, skill_masteries_enab if skill_masteries_enabled: distribution += SKILL_MASTERIES_TOOL_DISTRIBUTION - return MappingProxyType(distribution) + if no_starting_tools_enabled: + distribution += VANILLA_STARTING_TOOLS_DISTRIBUTION + starting.clear() + + return MappingProxyType(starting), MappingProxyType(distribution) @dataclass(frozen=True) -class ToolProgressionFeature(ABC): +class ToolProgressionFeature(FeatureBase, ABC): is_progressive: ClassVar[bool] + starting_tools: Mapping[str, int] tool_distribution: Mapping[str, int] - to_progressive_item = staticmethod(to_progressive_item) + to_progressive_item_name = staticmethod(to_progressive_item_name) + to_upgrade_location_name = staticmethod(to_upgrade_location_name) + + def get_tools_distribution(self) -> tuple[Mapping[str, int], Mapping[str, int]]: + return self.starting_tools, self.tool_distribution @dataclass(frozen=True) class ToolProgressionVanilla(ToolProgressionFeature): is_progressive = False # FIXME change the default_factory to a simple default when python 3.11 is no longer supported + starting_tools: Mapping[str, int] = field(default_factory=lambda: VANILLA_STARTING_TOOLS_DISTRIBUTION) tool_distribution: Mapping[str, int] = field(default_factory=lambda: VANILLA_TOOL_DISTRIBUTION) diff --git a/worlds/stardew_valley/content/feature/walnutsanity.py b/worlds/stardew_valley/content/feature/walnutsanity.py new file mode 100644 index 000000000000..279d0a38bbea --- /dev/null +++ b/worlds/stardew_valley/content/feature/walnutsanity.py @@ -0,0 +1,8 @@ +def get_walnut_amount(item_name: str) -> int: + if item_name == "Golden Walnut": + return 1 + if item_name == "3 Golden Walnuts": + return 3 + if item_name == "5 Golden Walnuts": + return 5 + return 0 diff --git a/worlds/stardew_valley/content/game_content.py b/worlds/stardew_valley/content/game_content.py index c3f8e9f8e951..11e26976be72 100644 --- a/worlds/stardew_valley/content/game_content.py +++ b/worlds/stardew_valley/content/game_content.py @@ -1,13 +1,18 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Iterable, Set, Any, Mapping, Type, Tuple, Union +from functools import cached_property +from types import MappingProxyType +from typing import Iterable, Set, Any, Mapping, Generator -from .feature import booksanity, cropsanity, fishsanity, friendsanity, skill_progression, building_progression, tool_progression +from .feature import BooksanityFeature, BuildingProgressionFeature, CropsanityFeature, FishsanityFeature, FriendsanityFeature, HatsanityFeature, \ + MuseumsanityFeature, SkillProgressionFeature, ToolProgressionFeature +from .feature.base import DisableSourceHook, DisableRequirementHook, FeatureBase from ..data.animal import Animal from ..data.building import Building from ..data.fish_data import FishItem -from ..data.game_item import GameItem, Source, ItemTag +from ..data.game_item import GameItem, Source, ItemTag, Requirement +from ..data.hats_data import HatItem from ..data.skill import Skill from ..data.villagers_data import Villager @@ -26,16 +31,25 @@ class StardewContent: animals: dict[str, Animal] = field(default_factory=dict) skills: dict[str, Skill] = field(default_factory=dict) quests: dict[str, Any] = field(default_factory=dict) + hats: dict[str, HatItem] = field(default_factory=dict) - def find_sources_of_type(self, types: Union[Type[Source], Tuple[Type[Source]]]) -> Iterable[Source]: + def find_sources_of_type(self, *types: type[Source]) -> Iterable[Source]: for item in self.game_items.values(): for source in item.sources: if isinstance(source, types): yield source - def source_item(self, item_name: str, *sources: Source): + def source_item(self, item_name: str, *sources: Source) -> GameItem: + filtered_sources = list(self._filter_sources(sources)) + item = self.game_items.setdefault(item_name, GameItem(item_name)) - item.add_sources(sources) + item.add_sources(filtered_sources) + + for source in filtered_sources: + for requirement_name, tags in source.requirement_tags.items(): + self.tag_item(requirement_name, *tags) + + return item def tag_item(self, item_name: str, *tags: ItemTag): item = self.game_items.setdefault(item_name, GameItem(item_name)) @@ -44,22 +58,79 @@ def tag_item(self, item_name: str, *tags: ItemTag): def untag_item(self, item_name: str, tag: ItemTag): self.game_items[item_name].tags.remove(tag) - def find_tagged_items(self, tag: ItemTag) -> Iterable[GameItem]: + def find_tagged_items(self, tag: ItemTag) -> Generator[GameItem]: # TODO might be worth caching this, but it need to only be cached once the content is finalized... for item in self.game_items.values(): if tag in item.tags: yield item + def are_all_enabled(self, content_packs: frozenset[str]) -> bool: + return content_packs.issubset(self.registered_packs) + + def is_enabled(self, content_pack: str | ContentPack) -> bool: + if isinstance(content_pack, ContentPack): + content_pack = content_pack.name + + return content_pack in self.registered_packs + + def _filter_sources(self, sources: Iterable[Source]) -> Generator[Source]: + """Filters out sources that are disabled by features.""" + for source in sources: + if not self._is_disabled(source): + yield source + + def _is_disabled(self, source: Source) -> bool: + """Checks if a source is disabled by any feature.""" + try: + hook = self.features.disable_source_hooks[type(source)] + if hook(source, content=self): + return True + except KeyError: + pass + + for requirement in source.all_requirements: + try: + hook = self.features.disable_requirement_hooks[type(requirement)] + if hook(requirement, content=self): + return True + except KeyError: + pass + + return False + @dataclass(frozen=True) class StardewFeatures: - booksanity: booksanity.BooksanityFeature - building_progression: building_progression.BuildingProgressionFeature - cropsanity: cropsanity.CropsanityFeature - fishsanity: fishsanity.FishsanityFeature - friendsanity: friendsanity.FriendsanityFeature - skill_progression: skill_progression.SkillProgressionFeature - tool_progression: tool_progression.ToolProgressionFeature + booksanity: BooksanityFeature + building_progression: BuildingProgressionFeature + cropsanity: CropsanityFeature + fishsanity: FishsanityFeature + friendsanity: FriendsanityFeature + hatsanity: HatsanityFeature + museumsanity: MuseumsanityFeature + skill_progression: SkillProgressionFeature + tool_progression: ToolProgressionFeature + + @cached_property + def all_features(self) -> Iterable[FeatureBase]: + return (self.booksanity, self.building_progression, self.cropsanity, self.fishsanity, self.friendsanity, self.hatsanity, self.museumsanity, + self.skill_progression, self.tool_progression) + + @cached_property + def disable_source_hooks(self) -> Mapping[type[Source], DisableSourceHook]: + """Returns a set of source types that are disabled by features. Need to be exact types, subclasses are not considered.""" + hooks = {} + for feature in self.all_features: + hooks.update(feature.disable_source_hooks) + return MappingProxyType(hooks) + + @cached_property + def disable_requirement_hooks(self) -> Mapping[type[Requirement], DisableRequirementHook]: + """Returns a set of requirement types that are disabled by features. Need to be exact types, subclasses are not considered.""" + hooks = {} + for feature in self.all_features: + hooks.update(feature.disable_requirement_hooks) + return MappingProxyType(hooks) @dataclass(frozen=True) @@ -125,6 +196,12 @@ def skill_hook(self, content: StardewContent): def quest_hook(self, content: StardewContent): ... + ... + + hat_sources: Mapping[HatItem, Iterable[Source]] = field(default_factory=dict) + + def hat_source_hook(self, content: StardewContent): + ... def finalize_hook(self, content: StardewContent): """Last hook called on the pack, once all other content packs have been registered. diff --git a/worlds/stardew_valley/content/mods/archeology.py b/worlds/stardew_valley/content/mods/archeology.py index 5eb8af4cfc38..fbde3ae21dd6 100644 --- a/worlds/stardew_valley/content/mods/archeology.py +++ b/worlds/stardew_valley/content/mods/archeology.py @@ -1,34 +1,46 @@ from ..game_content import ContentPack, StardewContent from ..mod_registry import register_mod_content_pack from ...data.artisan import MachineSource +from ...data.game_item import ItemTag, Tag +from ...data.harvest import ArtifactSpotSource +from ...data.requirement import SkillRequirement from ...data.skill import Skill from ...mods.mod_data import ModNames +from ...strings.ap_names.mods.mod_items import ModBooks from ...strings.craftable_names import ModMachine from ...strings.fish_names import ModTrash -from ...strings.metal_names import all_artifacts, all_fossils +from ...strings.metal_names import all_artifacts, all_fossils, Fossil from ...strings.skill_names import ModSkill +def source_display_items(item: str, content: StardewContent): + wood_display = f"Wooden Display: {item}" + hardwood_display = f"Hardwood Display: {item}" + if item == Fossil.trilobite: + wood_display = f"Wooden Display: Trilobite Fossil" + hardwood_display = f"Hardwood Display: Trilobite Fossil" + content.source_item(wood_display, MachineSource(item=str(item), machine=ModMachine.preservation_chamber)) + content.source_item(hardwood_display, MachineSource(item=str(item), machine=ModMachine.hardwood_preservation_chamber)) + + class ArchaeologyContentPack(ContentPack): def artisan_good_hook(self, content: StardewContent): # Done as honestly there are too many display items to put into the initial registration traditionally. display_items = all_artifacts + all_fossils for item in display_items: - self.source_display_items(item, content) + source_display_items(item, content) content.source_item(ModTrash.rusty_scrap, *(MachineSource(item=artifact, machine=ModMachine.grinder) for artifact in all_artifacts)) - def source_display_items(self, item: str, content: StardewContent): - wood_display = f"Wooden Display: {item}" - hardwood_display = f"Hardwood Display: {item}" - if item == "Trilobite": - wood_display = f"Wooden Display: Trilobite Fossil" - hardwood_display = f"Hardwood Display: Trilobite Fossil" - content.source_item(wood_display, MachineSource(item=str(item), machine=ModMachine.preservation_chamber)) - content.source_item(hardwood_display, MachineSource(item=str(item), machine=ModMachine.hardwood_preservation_chamber)) - register_mod_content_pack(ArchaeologyContentPack( ModNames.archaeology, skills=(Skill(name=ModSkill.archaeology, has_mastery=False),), + harvest_sources={ + ModBooks.digging_like_worms: ( + Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), + ArtifactSpotSource(amount=22, # I'm just copying Jack Be Nimble's chances for now -reptar + other_requirements=(SkillRequirement(ModSkill.archaeology, 2),)), + ) + } )) diff --git a/worlds/stardew_valley/content/mods/distant_lands.py b/worlds/stardew_valley/content/mods/distant_lands.py index c5614d130250..d9e5044d25bf 100644 --- a/worlds/stardew_valley/content/mods/distant_lands.py +++ b/worlds/stardew_valley/content/mods/distant_lands.py @@ -39,4 +39,4 @@ def harvest_source_hook(self, content: StardewContent): DistantLandsSeed.vile_ancient_fruit: (ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.CorruptedCropsTask),)),), DistantLandsCrop.vile_ancient_fruit: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=DistantLandsSeed.vile_ancient_fruit, seasons=(Season.spring, Season.summer, Season.fall)),) } -)) +)) \ No newline at end of file diff --git a/worlds/stardew_valley/content/mods/sve.py b/worlds/stardew_valley/content/mods/sve.py index 378472373749..dbbd9dc0eb1e 100644 --- a/worlds/stardew_valley/content/mods/sve.py +++ b/worlds/stardew_valley/content/mods/sve.py @@ -5,7 +5,7 @@ from ...data import villagers_data, fish_data from ...data.game_item import ItemTag, Tag from ...data.harvest import ForagingSource, HarvestCropSource -from ...data.requirement import YearRequirement, CombatRequirement, RelationshipRequirement, ToolRequirement, SkillRequirement, FishingRequirement +from ...data.requirement import YearRequirement, CombatRequirement, SpecificFriendRequirement, ToolRequirement, SkillRequirement, FishingRequirement from ...data.shop import ShopSource from ...mods.mod_data import ModNames from ...strings.craftable_names import ModEdible @@ -82,18 +82,18 @@ def finalize_hook(self, content: StardewContent): ModNames.jasper, # To override Marlon and Gunther ), shop_sources={ - SVEGift.aged_blue_moon_wine: (ShopSource(money_price=28000, shop_region=SVERegion.blue_moon_vineyard),), - SVEGift.blue_moon_wine: (ShopSource(money_price=3000, shop_region=SVERegion.blue_moon_vineyard),), - ModEdible.lightning_elixir: (ShopSource(money_price=12000, shop_region=SVERegion.galmoran_outpost),), - ModEdible.barbarian_elixir: (ShopSource(money_price=22000, shop_region=SVERegion.galmoran_outpost),), - ModEdible.gravity_elixir: (ShopSource(money_price=4000, shop_region=SVERegion.galmoran_outpost),), - SVEMeal.grampleton_orange_chicken: (ShopSource(money_price=650, + SVEGift.aged_blue_moon_wine: (ShopSource(price=28000, shop_region=SVERegion.blue_moon_vineyard),), + SVEGift.blue_moon_wine: (ShopSource(price=3000, shop_region=SVERegion.blue_moon_vineyard),), + ModEdible.lightning_elixir: (ShopSource(price=12000, shop_region=SVERegion.galmoran_outpost),), + ModEdible.barbarian_elixir: (ShopSource(price=22000, shop_region=SVERegion.galmoran_outpost),), + ModEdible.gravity_elixir: (ShopSource(price=4000, shop_region=SVERegion.galmoran_outpost),), + SVEMeal.grampleton_orange_chicken: (ShopSource(price=650, shop_region=Region.saloon, - other_requirements=(RelationshipRequirement(ModNPC.sophia, 6),)),), - ModEdible.hero_elixir: (ShopSource(money_price=8000, shop_region=SVERegion.isaac_shop),), - ModEdible.aegis_elixir: (ShopSource(money_price=28000, shop_region=SVERegion.galmoran_outpost),), - SVEBeverage.sports_drink: (ShopSource(money_price=750, shop_region=Region.hospital),), - SVEMeal.stamina_capsule: (ShopSource(money_price=4000, shop_region=Region.hospital),), + other_requirements=(SpecificFriendRequirement(ModNPC.sophia, 6),)),), + ModEdible.hero_elixir: (ShopSource(price=8000, shop_region=SVERegion.isaac_shop),), + ModEdible.aegis_elixir: (ShopSource(price=28000, shop_region=SVERegion.galmoran_outpost),), + SVEBeverage.sports_drink: (ShopSource(price=750, shop_region=Region.hospital),), + SVEMeal.stamina_capsule: (ShopSource(price=4000, shop_region=Region.hospital),), }, harvest_sources={ Mushroom.red: ( diff --git a/worlds/stardew_valley/content/mods/tractor.py b/worlds/stardew_valley/content/mods/tractor.py index a672bc2bf940..d16e69f78441 100644 --- a/worlds/stardew_valley/content/mods/tractor.py +++ b/worlds/stardew_valley/content/mods/tractor.py @@ -16,7 +16,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=150_000, + price=150_000, items_price=((20, MetalBar.iron), (5, MetalBar.iridium), (1, ArtisanGood.battery_pack)), ), ), diff --git a/worlds/stardew_valley/content/unpacking.py b/worlds/stardew_valley/content/unpacking.py index faa7cb5399a9..7615f2113fcd 100644 --- a/worlds/stardew_valley/content/unpacking.py +++ b/worlds/stardew_valley/content/unpacking.py @@ -5,7 +5,7 @@ from .game_content import StardewContent, ContentPack, StardewFeatures from .vanilla.base import base_game as base_game_content_pack -from ..data.game_item import GameItem, Source +from ..data.game_item import Source def unpack_content(features: StardewFeatures, packs: Iterable[ContentPack]) -> StardewContent: @@ -73,6 +73,13 @@ def register_pack(content: StardewContent, pack: ContentPack): content.skills[skill.name] = skill pack.skill_hook(content) + for hat, sources in pack.hat_sources.items(): + item = content.source_item(hat.clarified_name, *sources) + # Some sources may be filtered out. We don't want to register a hat without source. + if item.sources: + content.hats[hat.name] = hat + pack.hat_source_hook(content) + # register_quests # ... @@ -84,14 +91,7 @@ def register_sources_and_call_hook(content: StardewContent, sources_by_item_name: Mapping[str, Iterable[Source]], hook: Callable[[StardewContent], None]): for item_name, sources in sources_by_item_name.items(): - item = content.game_items.setdefault(item_name, GameItem(item_name)) - item.add_sources(sources) - - for source in sources: - for requirement_name, tags in source.requirement_tags.items(): - requirement_item = content.game_items.setdefault(requirement_name, GameItem(requirement_name)) - requirement_item.add_tags(tags) - + content.source_item(item_name, *sources) hook(content) diff --git a/worlds/stardew_valley/content/vanilla/base.py b/worlds/stardew_valley/content/vanilla/base.py index 2215215c395b..1297f3c9cef0 100644 --- a/worlds/stardew_valley/content/vanilla/base.py +++ b/worlds/stardew_valley/content/vanilla/base.py @@ -1,20 +1,30 @@ from ..game_content import ContentPack, StardewContent from ...data.artisan import MachineSource -from ...data.game_item import ItemTag, CustomRuleSource, GameItem +from ...data.game_item import ItemTag, CustomRuleSource, GameItem, Tag from ...data.harvest import HarvestFruitTreeSource, HarvestCropSource +from ...data.hats_data import Hats +from ...data.requirement import ToolRequirement, TotalEarningsRequirement, ShipOneCropRequirement, CraftedItemsRequirement, CookedRecipesRequirement, \ + CaughtFishRequirement +from ...data.shop import HatMouseSource from ...data.skill import Skill +from ...logic.tailoring_logic import TailoringSource +from ...strings.animal_product_names import AnimalProduct from ...strings.artisan_good_names import ArtisanGood -from ...strings.craftable_names import WildSeeds +from ...strings.craftable_names import WildSeeds, Edible, Consumable, Lighting from ...strings.crop_names import Fruit, Vegetable +from ...strings.fish_names import Fish, WaterChest from ...strings.flower_names import Flower -from ...strings.food_names import Beverage +from ...strings.food_names import Beverage, Meal from ...strings.forageable_names import all_edible_mushrooms, Mushroom, Forageable from ...strings.fruit_tree_names import Sapling +from ...strings.gift_names import Gift from ...strings.machine_names import Machine +from ...strings.metal_names import Fossil, Artifact from ...strings.monster_names import Monster from ...strings.season_names import Season -from ...strings.seed_names import Seed +from ...strings.seed_names import Seed, TreeSeed from ...strings.skill_names import Skill as SkillName +from ...strings.tool_names import Tool, ToolMaterial all_fruits = ( Fruit.ancient_fruit, Fruit.apple, Fruit.apricot, Fruit.banana, Forageable.blackberry, Fruit.blueberry, Forageable.cactus_fruit, Fruit.cherry, @@ -110,16 +120,19 @@ def finalize_hook(self, content: StardewContent): Vegetable.cauliflower: (HarvestCropSource(seed=Seed.cauliflower, seasons=(Season.spring,)),), Vegetable.potato: (HarvestCropSource(seed=Seed.potato, seasons=(Season.spring,)),), Flower.tulip: (HarvestCropSource(seed=Seed.tulip, seasons=(Season.spring,)),), - Vegetable.kale: (HarvestCropSource(seed=Seed.kale, seasons=(Season.spring,)),), + Vegetable.kale: (HarvestCropSource(seed=Seed.kale, seasons=(Season.spring,), + other_requirements=(ToolRequirement(Tool.scythe),)),), Flower.blue_jazz: (HarvestCropSource(seed=Seed.jazz, seasons=(Season.spring,)),), Vegetable.garlic: (HarvestCropSource(seed=Seed.garlic, seasons=(Season.spring,)),), - Vegetable.unmilled_rice: (HarvestCropSource(seed=Seed.rice, seasons=(Season.spring,)),), + Vegetable.unmilled_rice: (HarvestCropSource(seed=Seed.rice, seasons=(Season.spring,), + other_requirements=(ToolRequirement(Tool.scythe),)),), Fruit.melon: (HarvestCropSource(seed=Seed.melon, seasons=(Season.summer,)),), Vegetable.tomato: (HarvestCropSource(seed=Seed.tomato, seasons=(Season.summer,)),), Fruit.blueberry: (HarvestCropSource(seed=Seed.blueberry, seasons=(Season.summer,)),), Fruit.hot_pepper: (HarvestCropSource(seed=Seed.pepper, seasons=(Season.summer,)),), - Vegetable.wheat: (HarvestCropSource(seed=Seed.wheat, seasons=(Season.summer, Season.fall)),), + Vegetable.wheat: (HarvestCropSource(seed=Seed.wheat, seasons=(Season.summer, Season.fall), + other_requirements=(ToolRequirement(Tool.scythe),)),), Vegetable.radish: (HarvestCropSource(seed=Seed.radish, seasons=(Season.summer,)),), Flower.poppy: (HarvestCropSource(seed=Seed.poppy, seasons=(Season.summer,)),), Flower.summer_spangle: (HarvestCropSource(seed=Seed.spangle, seasons=(Season.summer,)),), @@ -134,7 +147,8 @@ def finalize_hook(self, content: StardewContent): Vegetable.yam: (HarvestCropSource(seed=Seed.yam, seasons=(Season.fall,)),), Fruit.cranberries: (HarvestCropSource(seed=Seed.cranberry, seasons=(Season.fall,)),), Flower.fairy_rose: (HarvestCropSource(seed=Seed.fairy, seasons=(Season.fall,)),), - Vegetable.amaranth: (HarvestCropSource(seed=Seed.amaranth, seasons=(Season.fall,)),), + Vegetable.amaranth: (HarvestCropSource(seed=Seed.amaranth, seasons=(Season.fall,), + other_requirements=(ToolRequirement(Tool.scythe),)),), Fruit.grape: (HarvestCropSource(seed=Seed.grape, seasons=(Season.fall,)),), Vegetable.artichoke: (HarvestCropSource(seed=Seed.artichoke, seasons=(Season.fall,)),), @@ -147,18 +161,18 @@ def finalize_hook(self, content: StardewContent): Fruit.sweet_gem_berry: (HarvestCropSource(seed=Seed.rare_seed, seasons=(Season.fall,)),), Fruit.ancient_fruit: (HarvestCropSource(seed=WildSeeds.ancient, seasons=(Season.spring, Season.summer, Season.fall,)),), - Seed.coffee_starter: (CustomRuleSource(lambda logic: logic.traveling_merchant.has_days(3) & logic.monster.can_kill_many(Monster.dust_sprite)),), + Seed.coffee_starter: (CustomRuleSource(create_rule=lambda logic: logic.traveling_merchant.has_days(3) & logic.monster.can_kill_many(Monster.dust_sprite)),), Seed.coffee: (HarvestCropSource(seed=Seed.coffee_starter, seasons=(Season.spring, Season.summer,)),), Vegetable.tea_leaves: ( - CustomRuleSource(lambda logic: logic.has(WildSeeds.tea_sapling) & logic.time.has_lived_months(2) & logic.season.has_any_not_winter()),), + CustomRuleSource(create_rule=lambda logic: logic.has(WildSeeds.tea_sapling) & logic.time.has_lived_months(2) & logic.season.has_any_not_winter()),), }, artisan_good_sources={ Beverage.beer: (MachineSource(item=Vegetable.wheat, machine=Machine.keg),), # Ingredient.vinegar: (MachineSource(item=Ingredient.rice, machine=Machine.keg),), Beverage.coffee: (MachineSource(item=Seed.coffee, machine=Machine.keg), - CustomRuleSource(lambda logic: logic.has(Machine.coffee_maker)), - CustomRuleSource(lambda logic: logic.has("Hot Java Ring"))), + CustomRuleSource(create_rule=lambda logic: logic.has(Machine.coffee_maker)), + CustomRuleSource(create_rule=lambda logic: logic.has("Hot Java Ring"))), ArtisanGood.green_tea: (MachineSource(item=Vegetable.tea_leaves, machine=Machine.keg),), ArtisanGood.mead: (MachineSource(item=ArtisanGood.honey, machine=Machine.keg),), ArtisanGood.pale_ale: (MachineSource(item=Vegetable.hops, machine=Machine.keg),), @@ -169,5 +183,48 @@ def finalize_hook(self, content: StardewContent): Skill(SkillName.fishing, has_mastery=True), Skill(SkillName.mining, has_mastery=True), Skill(SkillName.combat, has_mastery=True), - ) + ), + hat_sources={ + Hats.good_ol_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(TotalEarningsRequirement(15000),)),), + Hats.lucky_bow: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(TotalEarningsRequirement(50000),)),), + Hats.cool_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(TotalEarningsRequirement(250000),)),), + Hats.bowler: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(TotalEarningsRequirement(1000000),)),), + Hats.sombrero: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(TotalEarningsRequirement(10000000),)),), + Hats.delicate_bow: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CookedRecipesRequirement(10),)),), + Hats.plum_chapeau: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CookedRecipesRequirement(25),)),), + Hats.daisy: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CraftedItemsRequirement(15),)),), + Hats.trucker_hat: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CraftedItemsRequirement(30),)),), + Hats.souwester: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CaughtFishRequirement(10, unique=True),)),), + Hats.official_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CaughtFishRequirement(24, unique=True),)),), + Hats.watermelon_band: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CaughtFishRequirement(100, unique=False),)),), + Hats.cowgal_hat: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(ShipOneCropRequirement(300),)),), + Hats.living_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.grind.can_grind_weeds(100000)),), + Hats.spotted_headscarf: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Mushroom.red,)),), + Hats.beanie: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(TreeSeed.acorn, TreeSeed.mahogany, TreeSeed.maple, TreeSeed.pine,)),), + Hats.blobfish_mask: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Fish.blobfish,)),), + Hats.bridal_veil: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Gift.pearl,)),), + Hats.dinosaur_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(AnimalProduct.dinosaur_egg,)),), + Hats.fashion_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(ArtisanGood.caviar,)),), + Hats.flat_topped_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Meal.cranberry_sauce, Meal.stuffing,)),), + Hats.floppy_beanie: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(ArtisanGood.maple_syrup, ArtisanGood.oak_resin, ArtisanGood.pine_tar,)),), + Hats.goggles: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Edible.bug_steak,)),), + Hats.hair_bone: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Fossil.prehistoric_tibia,)),), + Hats.party_hat_blue: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Meal.chocolate_cake, Consumable.fireworks_purple,)),), + Hats.party_hat_green: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Consumable.fireworks_green, Meal.fish_taco,)),), + Hats.party_hat_red: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Consumable.fireworks_red, Meal.pizza,)),), + Hats.pirate_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(WaterChest.treasure,)),), + Hats.propeller_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Meal.miners_treat,)),), + Hats.pumpkin_mask: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Lighting.jack_o_lantern,)),), + Hats.wearable_dwarf_helm: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Artifact.dwarf_gadget, Artifact.dwarvish_helm,)),), + Hats.white_turban: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Fruit.sweet_gem_berry,)),), + Hats.witch_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Gift.golden_pumpkin,)),), + Hats.totem_mask: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Consumable.rain_totem, Consumable.warp_totem_farm, + Consumable.warp_totem_mountains, Consumable.warp_totem_beach, + Consumable.warp_totem_desert, Consumable.treasure_totem)),), + + Hats.copper_pan_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.tool.has_pan(ToolMaterial.copper)),), + Hats.steel_pan_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.tool.has_pan(ToolMaterial.iron)),), + Hats.gold_pan_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.tool.has_pan(ToolMaterial.gold)),), + Hats.iridium_pan_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.tool.has_pan(ToolMaterial.iridium)),), + }, ) diff --git a/worlds/stardew_valley/content/vanilla/ginger_island.py b/worlds/stardew_valley/content/vanilla/ginger_island.py index edb135ea30b1..82c6788ad383 100644 --- a/worlds/stardew_valley/content/vanilla/ginger_island.py +++ b/worlds/stardew_valley/content/vanilla/ginger_island.py @@ -2,22 +2,34 @@ from ..game_content import ContentPack, StardewContent from ...data import villagers_data, fish_data from ...data.animal import Animal, AnimalName, OstrichIncubatorSource +from ...data.fish_data import FishingSource from ...data.game_item import ItemTag, Tag, CustomRuleSource from ...data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource -from ...data.requirement import WalnutRequirement -from ...data.shop import ShopSource +from ...data.hats_data import Hats +from ...data.monster_data import MonsterSource +from ...data.requirement import WalnutRequirement, ForgeInfinityWeaponRequirement, CookedRecipesRequirement, \ + CaughtFishRequirement, FullShipmentRequirement, RegionRequirement, \ + AllAchievementsRequirement, PerfectionPercentRequirement, ReadAllBooksRequirement, HasItemRequirement, ToolRequirement +from ...data.shop import ShopSource, HatMouseSource +from ...logic.tailoring_logic import TailoringSource +from ...logic.time_logic import MAX_MONTHS from ...strings.animal_product_names import AnimalProduct from ...strings.book_names import Book from ...strings.building_names import Building from ...strings.crop_names import Fruit, Vegetable +from ...strings.currency_names import Currency from ...strings.fish_names import Fish from ...strings.forageable_names import Forageable, Mushroom from ...strings.fruit_tree_names import Sapling from ...strings.generic_names import Generic +from ...strings.geode_names import Geode +from ...strings.material_names import Material from ...strings.metal_names import Fossil, Mineral +from ...strings.monster_names import Monster from ...strings.region_names import Region, LogicRegion from ...strings.season_names import Season -from ...strings.seed_names import Seed +from ...strings.seed_names import Seed, TreeSeed +from ...strings.tool_names import Tool class GingerIslandContentPack(ContentPack): @@ -38,12 +50,16 @@ def harvest_source_hook(self, content: StardewContent): harvest_sources={ # Foraging Forageable.dragon_tooth: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.volcano_floor_10,)), ), Forageable.ginger: ( - ForagingSource(regions=(Region.island_west,)), + Tag(ItemTag.FORAGE), + ForagingSource(regions=(Region.island_west,), + other_requirements=(ToolRequirement(Tool.hoe),)), ), Mushroom.magma_cap: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.volcano_floor_5,)), ), @@ -76,8 +92,7 @@ def harvest_source_hook(self, content: StardewContent): ), Book.queen_of_sauce_cookbook: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=50000, shop_region=LogicRegion.bookseller_2, other_requirements=(WalnutRequirement(100),)),), # Worst book ever - + ShopSource(price=50000, shop_region=LogicRegion.bookseller_permanent, other_requirements=(WalnutRequirement(100),)),), # Worst book ever }, fishes=( # TODO override region so no need to add inaccessible regions in logic @@ -99,5 +114,40 @@ def harvest_source_hook(self, content: StardewContent): sources=( OstrichIncubatorSource(AnimalProduct.ostrich_egg_starter), )), - ) + ), + hat_sources={ + Hats.infinity_crown: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(ForgeInfinityWeaponRequirement(),)),), + Hats.archers_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CookedRecipesRequirement(9999),)),), + Hats.chef_hat: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CookedRecipesRequirement(9999),)),), + Hats.eye_patch: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CaughtFishRequirement(9999, unique=True),)),), + Hats.cowpoke_hat: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(FullShipmentRequirement(),)),), + Hats.goblin_mask: (Tag(ItemTag.HAT), HatMouseSource(price=10000, unlock_requirements=(FullShipmentRequirement(),)),), + Hats.elegant_turban: (Tag(ItemTag.HAT), HatMouseSource(price=50000, unlock_requirements=(AllAchievementsRequirement(),)),), + Hats.junimo_hat: (Tag(ItemTag.HAT), HatMouseSource(price=25000, unlock_requirements=(PerfectionPercentRequirement(100),)),), + Hats.paper_hat: (Tag(ItemTag.HAT), HatMouseSource(price=10000, unlock_requirements=(RegionRequirement(Region.island_south),)),), + Hats.pageboy_cap: (Tag(ItemTag.HAT), HatMouseSource(price=5000, unlock_requirements=(ReadAllBooksRequirement(),)),), + + Hats.concerned_ape_mask: (Tag(ItemTag.HAT), ShopSource(price=10000, shop_region=LogicRegion.lost_items_shop, + other_requirements=(PerfectionPercentRequirement(100), RegionRequirement(Region.volcano_floor_10))),), + Hats.golden_helmet: (Tag(ItemTag.HAT), ShopSource(price=10000, shop_region=LogicRegion.lost_items_shop, + other_requirements=(RegionRequirement(Region.blacksmith), HasItemRequirement(Geode.golden_coconut),)),), + Hats.bluebird_mask: (Tag(ItemTag.HAT), ShopSource(price=30, currency=Vegetable.taro_root, shop_region=Region.island_trader),), + Hats.deluxe_cowboy_hat: (Tag(ItemTag.HAT), ShopSource(price=30, currency=Vegetable.taro_root, shop_region=Region.island_trader),), + Hats.small_cap: (Tag(ItemTag.HAT), ShopSource(price=30, currency=Vegetable.taro_root, shop_region=Region.island_trader),), + Hats.mr_qis_hat: (Tag(ItemTag.HAT), ShopSource(price=5, currency=Currency.qi_gem, shop_region=Region.qi_walnut_room),), + Hats.pink_bow: (Tag(ItemTag.HAT), ShopSource(price=10000, shop_region=Region.volcano_dwarf_shop),), + + Hats.tiger_hat: (Tag(ItemTag.HAT), MonsterSource(monsters=(Monster.tiger_slime,), amount_tier=MAX_MONTHS, + other_requirements=(RegionRequirement(region=Region.adventurer_guild),)),), + Hats.deluxe_pirate_hat: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.volcano, Region.volcano_floor_5, Region.volcano_floor_10,), + require_all_regions=True),), + + Hats.foragers_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Forageable.ginger,)),), + Hats.sunglasses: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Material.cinder_shard,)),), + Hats.swashbuckler_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Forageable.dragon_tooth,)),), + Hats.warrior_helmet: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(AnimalProduct.ostrich_egg,)),), + Hats.star_helmet: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(TreeSeed.mushroom,)),), + + Hats.frog_hat: (Tag(ItemTag.HAT), FishingSource(region=Region.gourmand_frog_cave,),), + }, ) diff --git a/worlds/stardew_valley/content/vanilla/pelican_town.py b/worlds/stardew_valley/content/vanilla/pelican_town.py index d1d024b54c55..3c2fb1a2da23 100644 --- a/worlds/stardew_valley/content/vanilla/pelican_town.py +++ b/worlds/stardew_valley/content/vanilla/pelican_town.py @@ -1,99 +1,131 @@ from ..game_content import ContentPack from ...data import villagers_data, fish_data from ...data.building import Building -from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource +from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource, AllRegionsSource from ...data.harvest import ForagingSource, SeasonalForagingSource, ArtifactSpotSource -from ...data.requirement import ToolRequirement, BookRequirement, SkillRequirement, YearRequirement -from ...data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource +from ...data.hats_data import Hats +from ...data.monster_data import MonsterSource +from ...data.requirement import ToolRequirement, BookRequirement, SkillRequirement, YearRequirement, \ + GrangeDisplayRequirement, EggHuntRequirement, MuseumCompletionRequirement, BuildingRequirement, \ + NumberOfFriendsRequirement, HelpWantedRequirement, FishingCompetitionRequirement, MovieRequirement, LuauDelightRequirementRequirement, \ + ReceivedRaccoonsRequirement, \ + PrizeMachineRequirement, SpecificFriendRequirement, RegionRequirement, CatalogueRequirement +from ...data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, \ + FishingTreasureChestSource, HatMouseSource +from ...logic.tailoring_logic import TailoringSource +from ...logic.time_logic import MAX_MONTHS from ...strings.artisan_good_names import ArtisanGood from ...strings.book_names import Book from ...strings.building_names import Building as BuildingNames +from ...strings.catalogue_names import Catalogue +from ...strings.craftable_names import Furniture from ...strings.crop_names import Fruit -from ...strings.fish_names import WaterItem +from ...strings.currency_names import Currency +from ...strings.fish_names import WaterItem, Fish from ...strings.food_names import Beverage, Meal from ...strings.forageable_names import Forageable, Mushroom from ...strings.fruit_tree_names import Sapling from ...strings.generic_names import Generic from ...strings.material_names import Material from ...strings.metal_names import MetalBar +from ...strings.monster_names import Monster from ...strings.region_names import Region, LogicRegion from ...strings.season_names import Season from ...strings.seed_names import Seed, TreeSeed from ...strings.skill_names import Skill from ...strings.tool_names import Tool, ToolMaterial +from ...strings.villager_names import NPC pelican_town = ContentPack( "Pelican Town (Vanilla)", harvest_sources={ # Spring Forageable.daffodil: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring,), regions=(Region.bus_stop, Region.town, Region.railroad)), ), Forageable.dandelion: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring,), regions=(Region.bus_stop, Region.forest, Region.railroad)), ), Forageable.leek: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.railroad)), ), Forageable.wild_horseradish: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring,), regions=(Region.backwoods, Region.mountain, Region.forest, Region.secret_woods)), ), Forageable.salmonberry: ( + Tag(ItemTag.FORAGE), SeasonalForagingSource(season=Season.spring, days=(15, 16, 17, 18), regions=(Region.backwoods, Region.mountain, Region.town, Region.forest, Region.tunnel_entrance, Region.railroad)), ), Forageable.spring_onion: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring,), regions=(Region.forest,)), ), # Summer Fruit.grape: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.summer,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.railroad)), ), Forageable.spice_berry: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.summer,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.forest, Region.railroad)), ), Forageable.sweet_pea: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.summer,), regions=(Region.bus_stop, Region.town, Region.forest, Region.railroad)), ), Forageable.fiddlehead_fern: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.summer,), regions=(Region.secret_woods,)), ), # Fall Forageable.blackberry: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.fall,), regions=(Region.backwoods, Region.town, Region.forest, Region.railroad)), SeasonalForagingSource(season=Season.fall, days=(8, 9, 10, 11), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.tunnel_entrance, Region.railroad)), ), Forageable.hazelnut: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.fall,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.railroad)), ), Forageable.wild_plum: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.fall,), regions=(Region.mountain, Region.bus_stop, Region.railroad)), ), # Winter Forageable.crocus: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.winter,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.secret_woods)), ), Forageable.crystal_fruit: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.winter,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.railroad)), ), Forageable.holly: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.winter,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.railroad)), ), Forageable.snow_yam: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.winter,), regions=(Region.farm, Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.railroad, Region.secret_woods, Region.beach), other_requirements=(ToolRequirement(Tool.hoe),)), ), Forageable.winter_root: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.winter,), regions=(Region.farm, Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.railroad, Region.secret_woods, Region.beach), @@ -102,31 +134,39 @@ # Mushrooms Mushroom.common: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring,), regions=(Region.secret_woods,)), ForagingSource(seasons=(Season.fall,), regions=(Region.backwoods, Region.mountain, Region.forest)), ), Mushroom.chanterelle: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.fall,), regions=(Region.secret_woods,)), ), Mushroom.morel: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.spring, Season.fall), regions=(Region.secret_woods,)), ), Mushroom.red: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.summer, Season.fall), regions=(Region.secret_woods,)), ), # Beach WaterItem.coral: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.tide_pools,)), SeasonalForagingSource(season=Season.summer, days=(12, 13, 14), regions=(Region.beach,)), ), WaterItem.nautilus_shell: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.winter,), regions=(Region.beach,)), ), Forageable.rainbow_shell: ( + Tag(ItemTag.FORAGE), ForagingSource(seasons=(Season.summer,), regions=(Region.beach,)), ), WaterItem.sea_urchin: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.tide_pools,)), ), @@ -149,150 +189,159 @@ }, shop_sources={ # Saplings - Sapling.apple: (ShopSource(money_price=4000, shop_region=Region.pierre_store),), - Sapling.apricot: (ShopSource(money_price=2000, shop_region=Region.pierre_store),), - Sapling.cherry: (ShopSource(money_price=3400, shop_region=Region.pierre_store),), - Sapling.orange: (ShopSource(money_price=4000, shop_region=Region.pierre_store),), - Sapling.peach: (ShopSource(money_price=6000, shop_region=Region.pierre_store),), - Sapling.pomegranate: (ShopSource(money_price=6000, shop_region=Region.pierre_store),), + Sapling.apple: (ShopSource(price=4000, shop_region=Region.pierre_store),), + Sapling.apricot: (ShopSource(price=2000, shop_region=Region.pierre_store),), + Sapling.cherry: (ShopSource(price=3400, shop_region=Region.pierre_store),), + Sapling.orange: (ShopSource(price=4000, shop_region=Region.pierre_store),), + Sapling.peach: (ShopSource(price=6000, shop_region=Region.pierre_store),), + Sapling.pomegranate: (ShopSource(price=6000, shop_region=Region.pierre_store),), # Crop seeds, assuming they are bought in season, otherwise price is different with missing stock list. - Seed.parsnip: (ShopSource(money_price=20, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.bean: (ShopSource(money_price=60, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.cauliflower: (ShopSource(money_price=80, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.potato: (ShopSource(money_price=50, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.tulip: (ShopSource(money_price=20, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.kale: (ShopSource(money_price=70, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.jazz: (ShopSource(money_price=30, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.garlic: (ShopSource(money_price=40, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.rice: (ShopSource(money_price=40, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.parsnip: (ShopSource(price=20, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.bean: (ShopSource(price=60, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.cauliflower: (ShopSource(price=80, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.potato: (ShopSource(price=50, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.tulip: (ShopSource(price=20, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.kale: (ShopSource(price=70, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.jazz: (ShopSource(price=30, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.garlic: (ShopSource(price=40, shop_region=Region.pierre_store, seasons=(Season.spring,)),), + Seed.rice: (ShopSource(price=40, shop_region=Region.pierre_store, seasons=(Season.spring,)),), - Seed.melon: (ShopSource(money_price=80, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.tomato: (ShopSource(money_price=50, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.blueberry: (ShopSource(money_price=80, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.pepper: (ShopSource(money_price=40, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.wheat: (ShopSource(money_price=10, shop_region=Region.pierre_store, seasons=(Season.summer, Season.fall)),), - Seed.radish: (ShopSource(money_price=40, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.poppy: (ShopSource(money_price=100, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.spangle: (ShopSource(money_price=50, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.hops: (ShopSource(money_price=60, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.corn: (ShopSource(money_price=150, shop_region=Region.pierre_store, seasons=(Season.summer, Season.fall)),), - Seed.sunflower: (ShopSource(money_price=200, shop_region=Region.pierre_store, seasons=(Season.summer, Season.fall)),), - Seed.red_cabbage: (ShopSource(money_price=100, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.melon: (ShopSource(price=80, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.tomato: (ShopSource(price=50, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.blueberry: (ShopSource(price=80, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.pepper: (ShopSource(price=40, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.wheat: (ShopSource(price=10, shop_region=Region.pierre_store, seasons=(Season.summer, Season.fall)),), + Seed.radish: (ShopSource(price=40, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.poppy: (ShopSource(price=100, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.spangle: (ShopSource(price=50, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.hops: (ShopSource(price=60, shop_region=Region.pierre_store, seasons=(Season.summer,)),), + Seed.corn: (ShopSource(price=150, shop_region=Region.pierre_store, seasons=(Season.summer, Season.fall)),), + Seed.sunflower: (ShopSource(price=200, shop_region=Region.pierre_store, seasons=(Season.summer, Season.fall)),), + Seed.red_cabbage: (ShopSource(price=100, shop_region=Region.pierre_store, seasons=(Season.summer,)),), - Seed.eggplant: (ShopSource(money_price=20, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.pumpkin: (ShopSource(money_price=100, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.bok_choy: (ShopSource(money_price=50, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.yam: (ShopSource(money_price=60, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.cranberry: (ShopSource(money_price=240, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.fairy: (ShopSource(money_price=200, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.amaranth: (ShopSource(money_price=70, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.grape: (ShopSource(money_price=60, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.artichoke: (ShopSource(money_price=30, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.eggplant: (ShopSource(price=20, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.pumpkin: (ShopSource(price=100, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.bok_choy: (ShopSource(price=50, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.yam: (ShopSource(price=60, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.cranberry: (ShopSource(price=240, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.fairy: (ShopSource(price=200, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.amaranth: (ShopSource(price=70, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.grape: (ShopSource(price=60, shop_region=Region.pierre_store, seasons=(Season.fall,)),), + Seed.artichoke: (ShopSource(price=30, shop_region=Region.pierre_store, seasons=(Season.fall,)),), - Seed.broccoli: (ShopSource(items_price=((5, Material.moss),), shop_region=LogicRegion.raccoon_shop),), - Seed.carrot: (ShopSource(items_price=((1, TreeSeed.maple),), shop_region=LogicRegion.raccoon_shop),), - Seed.powdermelon: (ShopSource(items_price=((2, TreeSeed.acorn),), shop_region=LogicRegion.raccoon_shop),), - Seed.summer_squash: (ShopSource(items_price=((15, Material.sap),), shop_region=LogicRegion.raccoon_shop),), + Seed.broccoli: (ShopSource(items_price=((5, Material.moss),), shop_region=LogicRegion.raccoon_shop_1),), + Seed.carrot: (ShopSource(items_price=((1, TreeSeed.maple),), shop_region=LogicRegion.raccoon_shop_1),), + Seed.powdermelon: (ShopSource(items_price=((2, TreeSeed.acorn),), shop_region=LogicRegion.raccoon_shop_1),), + Seed.summer_squash: (ShopSource(items_price=((15, Material.sap),), shop_region=LogicRegion.raccoon_shop_1),), - Seed.strawberry: (ShopSource(money_price=100, shop_region=LogicRegion.egg_festival, seasons=(Season.spring,)),), - Seed.rare_seed: (ShopSource(money_price=1000, shop_region=LogicRegion.traveling_cart, seasons=(Season.spring, Season.summer)),), + Seed.strawberry: (ShopSource(price=100, shop_region=LogicRegion.egg_festival, seasons=(Season.spring,)),), + Seed.rare_seed: (ShopSource(price=1000, shop_region=LogicRegion.traveling_cart, seasons=(Season.spring, Season.summer)),), # Saloon - Beverage.beer: (ShopSource(money_price=400, shop_region=Region.saloon),), - Meal.salad: (ShopSource(money_price=220, shop_region=Region.saloon),), - Meal.bread: (ShopSource(money_price=100, shop_region=Region.saloon),), - Meal.spaghetti: (ShopSource(money_price=240, shop_region=Region.saloon),), - Meal.pizza: (ShopSource(money_price=600, shop_region=Region.saloon),), - Beverage.coffee: (ShopSource(money_price=300, shop_region=Region.saloon),), + Beverage.beer: (ShopSource(price=400, shop_region=Region.saloon),), + Meal.salad: (ShopSource(price=220, shop_region=Region.saloon),), + Meal.bread: (ShopSource(price=100, shop_region=Region.saloon),), + Meal.spaghetti: (ShopSource(price=240, shop_region=Region.saloon),), + Meal.pizza: (ShopSource(price=600, shop_region=Region.saloon),), + Beverage.coffee: (ShopSource(price=300, shop_region=Region.saloon),), # Books Book.animal_catalogue: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=5000, shop_region=Region.ranch, other_requirements=(YearRequirement(2),)),), + ShopSource(price=5000, shop_region=Region.ranch, other_requirements=(YearRequirement(2),)),), Book.book_of_mysteries: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - MysteryBoxSource(amount=38),), # After 38 boxes, there are 49.99% chances player received the book. + MysteryBoxSource(amount=50),), # After 38 boxes, there are 49.99% chances player received the book. Book.dwarvish_safety_manual: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=4000, shop_region=LogicRegion.mines_dwarf_shop), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=4000, shop_region=LogicRegion.mines_dwarf_shop),), + # ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), # Repeatable, so no need for bookseller Book.friendship_101: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), PrizeMachineSource(amount=9), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.horse_the_book: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=25000, shop_region=LogicRegion.bookseller_2),), + ShopSource(price=25000, shop_region=LogicRegion.bookseller_permanent),), Book.jack_be_nimble_jack_be_thick: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.jewels_of_the_sea: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - FishingTreasureChestSource(amount=21), # After 21 chests, there are 49.44% chances player received the book. - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + FishingTreasureChestSource(amount=25), # After 21 chests, there are 49.44% chances player received the book. + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.mapping_cave_systems: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=(Region.adventurer_guild_bedroom,)), + AllRegionsSource(regions=(Region.adventurer_guild_bedroom, LogicRegion.bookseller_rare,)), # Disabling the shop source for better game design. - # ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3), + # ShopSource(price=20000, shop_region=LogicRegion.bookseller_3), ), Book.monster_compendium: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), CustomRuleSource(create_rule=lambda logic: logic.monster.can_kill_many(Generic.any)), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.ol_slitherlegs: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=25000, shop_region=LogicRegion.bookseller_2),), + ShopSource(price=25000, shop_region=LogicRegion.bookseller_permanent),), Book.price_catalogue: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=3000, shop_region=LogicRegion.bookseller_2),), + ShopSource(price=3000, shop_region=LogicRegion.bookseller_permanent),), Book.the_alleyway_buffet: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), GenericSource(regions=(Region.town,), other_requirements=(ToolRequirement(Tool.axe, ToolMaterial.iron), ToolRequirement(Tool.pickaxe, ToolMaterial.iron))), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.the_art_o_crabbing: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), CustomRuleSource(create_rule=lambda logic: logic.festival.has_squidfest_day_1_iridium_reward()), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.treasure_appraisal_guide: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ArtifactTroveSource(amount=18), # After 18 troves, there is 49,88% chances player received the book. - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ArtifactTroveSource(amount=20), # After 18 troves, there is 49,88% chances player received the book. + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), Book.raccoon_journal: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3), - ShopSource(items_price=((999, Material.fiber),), shop_region=LogicRegion.raccoon_shop),), + # ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare), # Repeatable, so no need for bookseller + ShopSource(items_price=((999, Material.fiber),), shop_region=LogicRegion.raccoon_shop_2),), Book.way_of_the_wind_pt_1: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=15000, shop_region=LogicRegion.bookseller_2),), + ShopSource(price=15000, shop_region=LogicRegion.bookseller_permanent),), Book.way_of_the_wind_pt_2: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=35000, shop_region=LogicRegion.bookseller_2, other_requirements=(BookRequirement(Book.way_of_the_wind_pt_1),)),), + ShopSource(price=35000, shop_region=LogicRegion.bookseller_permanent, other_requirements=(BookRequirement(Book.way_of_the_wind_pt_1),)),), Book.woodys_secret: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), + ShopSource(price=20000, shop_region=LogicRegion.bookseller_rare),), # Experience Books Book.book_of_stars: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), + ShopSource(price=5000, shop_region=LogicRegion.bookseller_permanent),), Book.bait_and_bobber: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), + ShopSource(price=5000, shop_region=LogicRegion.bookseller_experience),), Book.combat_quarterly: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), + ShopSource(price=5000, shop_region=LogicRegion.bookseller_experience),), Book.mining_monthly: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), + ShopSource(price=5000, shop_region=LogicRegion.bookseller_experience),), Book.stardew_valley_almanac: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), + ShopSource(price=5000, shop_region=LogicRegion.bookseller_experience),), Book.woodcutters_weekly: ( Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),), + ShopSource(price=5000, shop_region=LogicRegion.bookseller_experience),), + + # Catalogues + Catalogue.wizard: (ShopSource(price=150000, shop_region=Region.sewer, other_requirements=(CatalogueRequirement(Catalogue.wizard),)),), + Catalogue.furniture: (ShopSource(price=200000, shop_region=Region.carpenter, other_requirements=(CatalogueRequirement(Catalogue.furniture),BuildingRequirement(BuildingNames.kitchen),)),), + + # Furniture + Furniture.single_bed: (ShopSource(price=500, shop_region=Region.carpenter),), + Furniture.crane_game_house_plant: (ShopSource(price=500, shop_region=Region.movie_theater),), + Furniture.cursed_mannequin: (MonsterSource(monsters=(Monster.haunted_skull,), amount_tier=MAX_MONTHS),), }, fishes=( fish_data.albacore, @@ -396,7 +445,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=6000, + price=6000, items_price=((350, Material.wood), (150, Material.stone)) ), ), @@ -406,7 +455,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=12_000, + price=12_000, items_price=((450, Material.wood), (200, Material.stone)) ), ), @@ -417,7 +466,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=25_000, + price=25_000, items_price=((550, Material.wood), (300, Material.stone)) ), ), @@ -428,7 +477,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=4000, + price=4000, items_price=((300, Material.wood), (100, Material.stone)) ), ), @@ -438,7 +487,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=10_000, + price=10_000, items_price=((400, Material.wood), (150, Material.stone)) ), ), @@ -449,7 +498,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=20_000, + price=20_000, items_price=((500, Material.wood), (200, Material.stone)) ), ), @@ -460,7 +509,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=5000, + price=5000, items_price=((200, Material.stone), (5, WaterItem.seaweed), (5, WaterItem.green_algae)) ), ), @@ -470,7 +519,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=2500, + price=2500, items_price=((50, Material.stone), (150, Material.wood), (4, ArtisanGood.cloth)) ), ), @@ -480,7 +529,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=15_000, + price=15_000, items_price=((300, Material.wood),) ), ), @@ -490,7 +539,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=20_000, + price=20_000, items_price=((550, Material.wood), (300, Material.stone)) ), ), @@ -501,7 +550,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=100, + price=100, items_price=((100, Material.stone), (10, Material.clay), (5, MetalBar.copper)) ), ), @@ -511,7 +560,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=10_000, + price=10_000, items_price=((500, Material.stone), (10, MetalBar.quartz), (1, MetalBar.iridium)) ), ), @@ -521,7 +570,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=10_000, + price=10_000, items_price=((100, Material.hardwood), (5, MetalBar.iron)) ), ), @@ -531,7 +580,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=1000, + price=1000, items_price=((75, Material.stone),) ), ), @@ -541,7 +590,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=250, + price=250, items_price=((150, Material.wood),) ), ), @@ -551,7 +600,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=5000, + price=5000, items_price=((25, Material.hardwood),) ), ), @@ -561,7 +610,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=10_000, + price=10_000, items_price=((450, Material.wood),) ), ), @@ -572,7 +621,7 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=65_000, + price=65_000, items_price=((100, Material.hardwood),) ), ), @@ -583,10 +632,115 @@ sources=( ShopSource( shop_region=Region.carpenter, - money_price=100_000, + price=100_000, ), ), upgrade_from=BuildingNames.kids_room, ), - ) + # Building( + # WizardBuilding.earth_obelisk, + # sources=( + # ShopSource( + # shop_region=Region.wizard_tower, + # price=500_000, + # items_price=((10, MetalBar.iridium), (10, Mineral.earth_crystal),) + # ), + # ), + # ), + # Building( + # WizardBuilding.water_obelisk, + # sources=( + # ShopSource( + # shop_region=Region.wizard_tower, + # price=500_000, + # items_price=((5, MetalBar.iridium), (10, Fish.clam), (10, WaterItem.coral),) + # ), + # ), + # ), + # Building( + # WizardBuilding.desert_obelisk, + # sources=( + # ShopSource( + # shop_region=Region.wizard_tower, + # price=1_000_000, + # items_price=((20, MetalBar.iridium), (10, Forageable.coconut), (10, Forageable.cactus_fruit),) + # ), + # ), + # ), + # Building( + # WizardBuilding.island_obelisk, + # sources=( + # ShopSource( + # shop_region=Region.wizard_tower, + # price=1_000_000, + # items_price=((10, MetalBar.iridium), (10, Forageable.dragon_tooth), (10, Fruit.banana),) + # ), + # ), + # ), + # Building( + # WizardBuilding.junimo_hut, + # sources=( + # ShopSource( + # shop_region=Region.wizard_tower, + # price=20_000, + # items_price=((200, Material.stone), (9, Fruit.starfruit), (100, Material.fiber),) + # ), + # ), + # ), + # Building( + # WizardBuilding.gold_clock, + # sources=( + # ShopSource( + # shop_region=Region.wizard_tower, + # price=10_000_000, + # ), + # ), + # ), + ), + hat_sources={ + # Hats from the Hat Mouse + Hats.blue_ribbon: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(GrangeDisplayRequirement(),)),), + Hats.blue_bonnet: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(MuseumCompletionRequirement(40),)),), + Hats.cowboy: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(MuseumCompletionRequirement(),)),), + Hats.butterfly_bow: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(NumberOfFriendsRequirement(1, 5),)),), + Hats.mouse_ears: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(NumberOfFriendsRequirement(1, 10),)),), + Hats.cat_ears: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(NumberOfFriendsRequirement(8, 10),)),), + Hats.tiara: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(NumberOfFriendsRequirement(4, 5),)),), + Hats.santa_hat: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(NumberOfFriendsRequirement(10, 5),)),), + Hats.earmuffs: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(NumberOfFriendsRequirement(20, 5),)),), + Hats.tropiclip: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(BuildingRequirement(BuildingNames.kitchen),)),), + Hats.hunters_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(BuildingRequirement(BuildingNames.cellar),)),), + Hats.polka_bow: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(HelpWantedRequirement(10),)),), + Hats.chicken_mask: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(HelpWantedRequirement(40),)),), + Hats.straw: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(EggHuntRequirement(),)),), + Hats.sailors_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(FishingCompetitionRequirement(),)),), + Hats.jester_hat: (Tag(ItemTag.HAT), HatMouseSource(price=25000, unlock_requirements=(MovieRequirement(),)),), + Hats.governors_hat: (Tag(ItemTag.HAT), HatMouseSource(price=5000, unlock_requirements=(LuauDelightRequirementRequirement(),)),), + Hats.white_bow: (Tag(ItemTag.HAT), HatMouseSource(price=5000, unlock_requirements=(ReceivedRaccoonsRequirement(8),)),), + Hats.sports_cap: (Tag(ItemTag.HAT), HatMouseSource(price=5000, unlock_requirements=(PrizeMachineRequirement(11),)),), + + Hats.emilys_magic_hat: (Tag(ItemTag.HAT), ShopSource(price=10000, shop_region=LogicRegion.lost_items_shop, + other_requirements=( + SpecificFriendRequirement(NPC.emily, 14), RegionRequirement(Region.farm))),), + Hats.fedora: (Tag(ItemTag.HAT), ShopSource(price=500, currency=Currency.star_token, shop_region=LogicRegion.fair),), + Hats.cone_hat: (Tag(ItemTag.HAT), ShopSource(price=5000, shop_region=LogicRegion.night_market),), + Hats.red_fez: (Tag(ItemTag.HAT), ShopSource(price=8000, shop_region=LogicRegion.traveling_cart),), + + Hats.garbage_hat: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.town,), grind_months=12),), + Hats.mystery_hat: (Tag(ItemTag.HAT), MysteryBoxSource(amount=100),), + + Hats.fishing_hat: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Fish.stonefish, Fish.ice_pip, Fish.scorpion_carp, Fish.spook_fish, + Fish.midnight_squid, Fish.void_salmon, Fish.slimejack,)),), + Hats.bucket_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.hat.has_bucket_hat),), + + Hats.leprechaun_hat: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.forest,), seasons=(Season.spring,), ),), + Hats.mushroom_cap: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.farm,), seasons=(Season.fall,), + other_requirements=(ToolRequirement(Tool.axe),),),), + + Hats.raccoon_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.quest.has_raccoon_shop(3) & + logic.region.can_reach(LogicRegion.raccoon_shop_3)),), + + Hats.squid_hat: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.festival.can_squidfest_iridium_reward()),), + + } ) diff --git a/worlds/stardew_valley/content/vanilla/qi_board.py b/worlds/stardew_valley/content/vanilla/qi_board.py index e5f67c431953..88133bd207b0 100644 --- a/worlds/stardew_valley/content/vanilla/qi_board.py +++ b/worlds/stardew_valley/content/vanilla/qi_board.py @@ -2,9 +2,14 @@ from .pelican_town import pelican_town as pelican_town_content_pack from ..game_content import ContentPack, StardewContent from ...data import fish_data -from ...data.game_item import GenericSource, ItemTag +from ...data.game_item import GenericSource, ItemTag, Tag from ...data.harvest import HarvestCropSource +from ...data.hats_data import Hats +from ...data.requirement import DangerousMinesRequirement, CraftedItemsRequirement +from ...data.shop import HatMouseSource +from ...logic.tailoring_logic import TailoringSource from ...strings.crop_names import Fruit +from ...strings.metal_names import MetalBar from ...strings.region_names import Region from ...strings.seed_names import Seed @@ -31,5 +36,11 @@ def harvest_source_hook(self, content: StardewContent): fish_data.glacierfish_jr, fish_data.legend_ii, fish_data.radioactive_carp, - ) + ), + hat_sources={ + Hats.space_helmet: (HatMouseSource(price=20000, unlock_requirements=(DangerousMinesRequirement(120),)),), + Hats.qi_mask: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Fruit.qi_fruit,)),), + Hats.radioactive_goggles: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(MetalBar.radioactive,)),), + Hats.gnomes_cap: (Tag(ItemTag.HAT), HatMouseSource(price=1000, unlock_requirements=(CraftedItemsRequirement(9999),)),), + }, ) diff --git a/worlds/stardew_valley/content/vanilla/the_desert.py b/worlds/stardew_valley/content/vanilla/the_desert.py index a207e169ca46..0cd34bcfe0fd 100644 --- a/worlds/stardew_valley/content/vanilla/the_desert.py +++ b/worlds/stardew_valley/content/vanilla/the_desert.py @@ -1,13 +1,24 @@ from .pelican_town import pelican_town as pelican_town_content_pack from ..game_content import ContentPack from ...data import fish_data, villagers_data +from ...data.game_item import CustomRuleSource, ItemTag, Tag from ...data.harvest import ForagingSource, HarvestCropSource +from ...data.hats_data import Hats +from ...data.monster_data import MonsterSource +from ...data.requirement import RegionRequirement, MeetRequirement, MonsterKillRequirement from ...data.shop import ShopSource +from ...logic.tailoring_logic import TailoringSource +from ...logic.time_logic import MAX_MONTHS from ...strings.crop_names import Fruit, Vegetable +from ...strings.currency_names import Currency from ...strings.forageable_names import Forageable, Mushroom -from ...strings.region_names import Region +from ...strings.geode_names import Geode +from ...strings.metal_names import Artifact +from ...strings.monster_names import Monster +from ...strings.region_names import Region, LogicRegion from ...strings.season_names import Season from ...strings.seed_names import Seed +from ...strings.villager_names import NPC the_desert = ContentPack( "The Desert (Vanilla)", @@ -16,13 +27,16 @@ ), harvest_sources={ Forageable.cactus_fruit: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.desert,)), HarvestCropSource(seed=Seed.cactus, seasons=()) ), Forageable.coconut: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.desert,)), ), Mushroom.purple: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.skull_cavern_25,)), ), @@ -31,10 +45,10 @@ Vegetable.beet: (HarvestCropSource(seed=Seed.beet, seasons=(Season.fall,)),), }, shop_sources={ - Seed.cactus: (ShopSource(money_price=150, shop_region=Region.oasis),), - Seed.rhubarb: (ShopSource(money_price=100, shop_region=Region.oasis, seasons=(Season.spring,)),), - Seed.starfruit: (ShopSource(money_price=400, shop_region=Region.oasis, seasons=(Season.summer,)),), - Seed.beet: (ShopSource(money_price=20, shop_region=Region.oasis, seasons=(Season.fall,)),), + Seed.cactus: (ShopSource(price=150, shop_region=Region.oasis),), + Seed.rhubarb: (ShopSource(price=100, shop_region=Region.oasis, seasons=(Season.spring,)),), + Seed.starfruit: (ShopSource(price=400, shop_region=Region.oasis, seasons=(Season.summer,)),), + Seed.beet: (ShopSource(price=20, shop_region=Region.oasis, seasons=(Season.fall,)),), }, fishes=( fish_data.sandfish, @@ -43,4 +57,33 @@ villagers=( villagers_data.sandy, ), + hat_sources={ + Hats.top_hat: (Tag(ItemTag.HAT), ShopSource(price=8000, shop_region=Region.casino, currency=Currency.qi_coin),), + Hats.gils_hat: (Tag(ItemTag.HAT), ShopSource(price=10000, shop_region=LogicRegion.lost_items_shop, + other_requirements=( + RegionRequirement(Region.skull_cavern_100), RegionRequirement(LogicRegion.desert_festival),)),), + Hats.abigails_bow: (Tag(ItemTag.HAT), ShopSource(price=60, currency=Currency.calico_egg, shop_region=LogicRegion.desert_festival, + other_requirements=(MeetRequirement(NPC.abigail),)),), + Hats.tricorn: (Tag(ItemTag.HAT), ShopSource(price=100, currency=Currency.calico_egg, shop_region=LogicRegion.desert_festival, + other_requirements=(MeetRequirement(NPC.elliott),)),), + Hats.blue_bow: (Tag(ItemTag.HAT), ShopSource(price=60, currency=Currency.calico_egg, shop_region=LogicRegion.desert_festival),), + Hats.dark_velvet_bow: (Tag(ItemTag.HAT), ShopSource(price=75, currency=Currency.calico_egg, shop_region=LogicRegion.desert_festival),), + Hats.mummy_mask: (Tag(ItemTag.HAT), ShopSource(price=120, currency=Currency.calico_egg, shop_region=LogicRegion.desert_festival),), + Hats.arcane_hat: (Tag(ItemTag.HAT), ShopSource(price=20000, shop_region=Region.adventurer_guild, + other_requirements=(MonsterKillRequirement((Monster.mummy,), 100),)),), + Hats.green_turban: (Tag(ItemTag.HAT), ShopSource(price=50, currency=Geode.omni, shop_region=Region.desert,),), + Hats.magic_cowboy_hat: (Tag(ItemTag.HAT), ShopSource(price=333, currency=Geode.omni, shop_region=Region.desert,),), + Hats.magic_turban: (Tag(ItemTag.HAT), ShopSource(price=333, currency=Geode.omni, shop_region=Region.desert,),), + + Hats.laurel_wreath_crown: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.hat.can_get_unlikely_hat_at_outfit_services),), + Hats.joja_cap: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.hat.can_get_unlikely_hat_at_outfit_services),), + Hats.dark_ballcap: (Tag(ItemTag.HAT), CustomRuleSource(create_rule=lambda logic: logic.hat.can_get_unlikely_hat_at_outfit_services),), + Hats.dark_cowboy_hat: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.skull_cavern_100,)),), + Hats.blue_cowboy_hat: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.skull_cavern_100,))), + Hats.red_cowboy_hat: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.skull_cavern_100,))), + Hats.golden_mask: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Artifact.golden_mask,)),), + Hats.white_turban: (Tag(ItemTag.HAT), ForagingSource(regions=(Region.skull_cavern_100,))), + Hats.knights_helmet: (Tag(ItemTag.HAT), MonsterSource(monsters=(Monster.pepper_rex,), amount_tier=MAX_MONTHS, + other_requirements=(RegionRequirement(region=Region.adventurer_guild),)),), + } ) diff --git a/worlds/stardew_valley/content/vanilla/the_farm.py b/worlds/stardew_valley/content/vanilla/the_farm.py index 183025e43ffb..70265a8d6c23 100644 --- a/worlds/stardew_valley/content/vanilla/the_farm.py +++ b/worlds/stardew_valley/content/vanilla/the_farm.py @@ -1,6 +1,7 @@ from .pelican_town import pelican_town as pelican_town_content_pack from ..game_content import ContentPack from ...data.animal import IncubatorSource, Animal, AnimalName +from ...data.game_item import Tag, ItemTag from ...data.harvest import FruitBatsSource, MushroomCaveSource from ...data.shop import ShopSource from ...strings.animal_product_names import AnimalProduct @@ -16,32 +17,41 @@ harvest_sources={ # Fruit cave Forageable.blackberry: ( + Tag(ItemTag.FORAGE), FruitBatsSource(), ), Forageable.salmonberry: ( + Tag(ItemTag.FORAGE), FruitBatsSource(), ), Forageable.spice_berry: ( + Tag(ItemTag.FORAGE), FruitBatsSource(), ), Forageable.wild_plum: ( + Tag(ItemTag.FORAGE), FruitBatsSource(), ), # Mushrooms Mushroom.common: ( + Tag(ItemTag.FORAGE), MushroomCaveSource(), ), Mushroom.chanterelle: ( + Tag(ItemTag.FORAGE), MushroomCaveSource(), ), Mushroom.morel: ( + Tag(ItemTag.FORAGE), MushroomCaveSource(), ), Mushroom.purple: ( + Tag(ItemTag.FORAGE), MushroomCaveSource(), ), Mushroom.red: ( + Tag(ItemTag.FORAGE), MushroomCaveSource(), ), }, @@ -49,41 +59,41 @@ Animal(AnimalName.chicken, required_building=Building.coop, sources=( - ShopSource(shop_region=Region.ranch, money_price=800), + ShopSource(shop_region=Region.ranch, price=800), # For now there is no way to obtain the starter item, so this adds additional rules in the system for nothing. # IncubatorSource(AnimalProduct.egg_starter) )), Animal(AnimalName.cow, required_building=Building.barn, sources=( - ShopSource(shop_region=Region.ranch, money_price=1500), + ShopSource(shop_region=Region.ranch, price=1500), )), Animal(AnimalName.goat, required_building=Building.big_barn, sources=( - ShopSource(shop_region=Region.ranch, money_price=4000), + ShopSource(shop_region=Region.ranch, price=4000), )), Animal(AnimalName.duck, required_building=Building.big_coop, sources=( - ShopSource(shop_region=Region.ranch, money_price=1200), + ShopSource(shop_region=Region.ranch, price=1200), # For now there is no way to obtain the starter item, so this adds additional rules in the system for nothing. # IncubatorSource(AnimalProduct.duck_egg_starter) )), Animal(AnimalName.sheep, required_building=Building.deluxe_barn, sources=( - ShopSource(shop_region=Region.ranch, money_price=8000), + ShopSource(shop_region=Region.ranch, price=8000), )), Animal(AnimalName.rabbit, required_building=Building.deluxe_coop, sources=( - ShopSource(shop_region=Region.ranch, money_price=8000), + ShopSource(shop_region=Region.ranch, price=8000), )), Animal(AnimalName.pig, required_building=Building.deluxe_barn, sources=( - ShopSource(shop_region=Region.ranch, money_price=16000), + ShopSource(shop_region=Region.ranch, price=16000), )), Animal(AnimalName.void_chicken, required_building=Building.big_coop, diff --git a/worlds/stardew_valley/content/vanilla/the_mines.py b/worlds/stardew_valley/content/vanilla/the_mines.py index 729b195f7b06..193ea1e29ef9 100644 --- a/worlds/stardew_valley/content/vanilla/the_mines.py +++ b/worlds/stardew_valley/content/vanilla/the_mines.py @@ -1,9 +1,16 @@ from .pelican_town import pelican_town as pelican_town_content_pack from ..game_content import ContentPack from ...data import fish_data, villagers_data +from ...data.game_item import Tag, ItemTag from ...data.harvest import ForagingSource -from ...data.requirement import ToolRequirement +from ...data.hats_data import Hats +from ...data.monster_data import MonsterSource +from ...data.requirement import ToolRequirement, RegionRequirement +from ...logic.tailoring_logic import TailoringSource +from ...logic.time_logic import MAX_MONTHS +from ...strings.fish_names import Fish from ...strings.forageable_names import Forageable, Mushroom +from ...strings.monster_names import Monster from ...strings.region_names import Region from ...strings.tool_names import Tool @@ -14,12 +21,15 @@ ), harvest_sources={ Forageable.cave_carrot: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.mines_floor_10,), other_requirements=(ToolRequirement(Tool.hoe),)), ), Mushroom.red: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.mines_floor_95,)), ), Mushroom.purple: ( + Tag(ItemTag.FORAGE), ForagingSource(regions=(Region.mines_floor_95,)), ) }, @@ -32,4 +42,15 @@ villagers=( villagers_data.dwarf, ), + hat_sources={ + Hats.logo_cap: (Tag(ItemTag.HAT), TailoringSource(tailoring_items=(Fish.lava_eel,)),), + Hats.hard_hat: (Tag(ItemTag.HAT), MonsterSource(monsters=(Monster.duggy, Monster.duggy_dangerous, Monster.magma_duggy,), + amount_tier=3, + other_requirements=(RegionRequirement(region=Region.adventurer_guild),)),), + Hats.skeleton_mask: (Tag(ItemTag.HAT), MonsterSource(monsters=(Monster.skeleton, Monster.skeleton_mage, Monster.skeleton_dangerous,), + amount_tier=MAX_MONTHS, + other_requirements=(RegionRequirement(region=Region.adventurer_guild),)),), + Hats.squires_helmet: (Tag(ItemTag.HAT), MonsterSource(monsters=(Monster.metal_head,), + amount_tier=MAX_MONTHS),), + }, ) diff --git a/worlds/stardew_valley/data/bundle_data.py b/worlds/stardew_valley/data/bundle_data.py deleted file mode 100644 index 3f289d33cd1a..000000000000 --- a/worlds/stardew_valley/data/bundle_data.py +++ /dev/null @@ -1,906 +0,0 @@ -from ..bundles.bundle import BundleTemplate, IslandBundleTemplate, DeepBundleTemplate, CurrencyBundleTemplate, MoneyBundleTemplate, FestivalBundleTemplate -from ..bundles.bundle_item import BundleItem -from ..bundles.bundle_room import BundleRoomTemplate -from ..content import content_packs -from ..content.vanilla.base import all_fruits, all_vegetables, all_edible_mushrooms -from ..strings.animal_product_names import AnimalProduct -from ..strings.artisan_good_names import ArtisanGood -from ..strings.bundle_names import CCRoom, BundleName -from ..strings.craftable_names import Fishing, Craftable, Bomb, Consumable, Lighting -from ..strings.crop_names import Fruit, Vegetable -from ..strings.currency_names import Currency -from ..strings.fertilizer_names import Fertilizer, RetainingSoil, SpeedGro -from ..strings.fish_names import Fish, WaterItem, Trash -from ..strings.flower_names import Flower -from ..strings.food_names import Beverage, Meal -from ..strings.forageable_names import Forageable, Mushroom -from ..strings.geode_names import Geode -from ..strings.gift_names import Gift -from ..strings.ingredient_names import Ingredient -from ..strings.material_names import Material -from ..strings.metal_names import MetalBar, Artifact, Fossil, Ore, Mineral -from ..strings.monster_drop_names import Loot -from ..strings.quality_names import ForageQuality, ArtisanQuality, FishQuality -from ..strings.seed_names import Seed, TreeSeed - -wild_horseradish = BundleItem(Forageable.wild_horseradish) -daffodil = BundleItem(Forageable.daffodil) -leek = BundleItem(Forageable.leek) -dandelion = BundleItem(Forageable.dandelion) -morel = BundleItem(Mushroom.morel) -common_mushroom = BundleItem(Mushroom.common) -salmonberry = BundleItem(Forageable.salmonberry) -spring_onion = BundleItem(Forageable.spring_onion) - -grape = BundleItem(Fruit.grape) -spice_berry = BundleItem(Forageable.spice_berry) -sweet_pea = BundleItem(Forageable.sweet_pea) -red_mushroom = BundleItem(Mushroom.red) -fiddlehead_fern = BundleItem(Forageable.fiddlehead_fern) - -wild_plum = BundleItem(Forageable.wild_plum) -hazelnut = BundleItem(Forageable.hazelnut) -blackberry = BundleItem(Forageable.blackberry) -chanterelle = BundleItem(Mushroom.chanterelle) - -winter_root = BundleItem(Forageable.winter_root) -crystal_fruit = BundleItem(Forageable.crystal_fruit) -snow_yam = BundleItem(Forageable.snow_yam) -crocus = BundleItem(Forageable.crocus) -holly = BundleItem(Forageable.holly) - -coconut = BundleItem(Forageable.coconut) -cactus_fruit = BundleItem(Forageable.cactus_fruit) -cave_carrot = BundleItem(Forageable.cave_carrot) -purple_mushroom = BundleItem(Mushroom.purple) -maple_syrup = BundleItem(ArtisanGood.maple_syrup) -oak_resin = BundleItem(ArtisanGood.oak_resin) -pine_tar = BundleItem(ArtisanGood.pine_tar) -nautilus_shell = BundleItem(WaterItem.nautilus_shell) -coral = BundleItem(WaterItem.coral) -sea_urchin = BundleItem(WaterItem.sea_urchin) -rainbow_shell = BundleItem(Forageable.rainbow_shell) -clam = BundleItem(Fish.clam) -cockle = BundleItem(Fish.cockle) -mussel = BundleItem(Fish.mussel) -oyster = BundleItem(Fish.oyster) -seaweed = BundleItem(WaterItem.seaweed, can_have_quality=False) - -wood = BundleItem(Material.wood, 99) -stone = BundleItem(Material.stone, 99) -hardwood = BundleItem(Material.hardwood, 10) -clay = BundleItem(Material.clay, 10) -fiber = BundleItem(Material.fiber, 99) -moss = BundleItem(Material.moss, 10) - -mixed_seeds = BundleItem(Seed.mixed) -acorn = BundleItem(TreeSeed.acorn) -maple_seed = BundleItem(TreeSeed.maple) -pine_cone = BundleItem(TreeSeed.pine) -mahogany_seed = BundleItem(TreeSeed.mahogany) -mushroom_tree_seed = BundleItem(TreeSeed.mushroom, source=BundleItem.Sources.island) -mystic_tree_seed = BundleItem(TreeSeed.mystic, source=BundleItem.Sources.masteries) -mossy_seed = BundleItem(TreeSeed.mossy) - -strawberry_seeds = BundleItem(Seed.strawberry) - -blue_jazz = BundleItem(Flower.blue_jazz) -cauliflower = BundleItem(Vegetable.cauliflower) -green_bean = BundleItem(Vegetable.green_bean) -kale = BundleItem(Vegetable.kale) -parsnip = BundleItem(Vegetable.parsnip) -potato = BundleItem(Vegetable.potato) -strawberry = BundleItem(Fruit.strawberry, source=BundleItem.Sources.festival) -tulip = BundleItem(Flower.tulip) -unmilled_rice = BundleItem(Vegetable.unmilled_rice) -coffee_bean = BundleItem(Seed.coffee) -garlic = BundleItem(Vegetable.garlic) -blueberry = BundleItem(Fruit.blueberry) -corn = BundleItem(Vegetable.corn) -hops = BundleItem(Vegetable.hops) -hot_pepper = BundleItem(Fruit.hot_pepper) -melon = BundleItem(Fruit.melon) -poppy = BundleItem(Flower.poppy) -radish = BundleItem(Vegetable.radish) -summer_spangle = BundleItem(Flower.summer_spangle) -sunflower = BundleItem(Flower.sunflower) -tomato = BundleItem(Vegetable.tomato) -wheat = BundleItem(Vegetable.wheat) -hay = BundleItem(Forageable.hay) -amaranth = BundleItem(Vegetable.amaranth) -bok_choy = BundleItem(Vegetable.bok_choy) -cranberries = BundleItem(Fruit.cranberries) -eggplant = BundleItem(Vegetable.eggplant) -fairy_rose = BundleItem(Flower.fairy_rose) -pumpkin = BundleItem(Vegetable.pumpkin) -yam = BundleItem(Vegetable.yam) -sweet_gem_berry = BundleItem(Fruit.sweet_gem_berry) -rhubarb = BundleItem(Fruit.rhubarb) -beet = BundleItem(Vegetable.beet) -red_cabbage = BundleItem(Vegetable.red_cabbage) -starfruit = BundleItem(Fruit.starfruit) -artichoke = BundleItem(Vegetable.artichoke) -pineapple = BundleItem(Fruit.pineapple, source=BundleItem.Sources.content) -taro_root = BundleItem(Vegetable.taro_root, source=BundleItem.Sources.content) - -carrot = BundleItem(Vegetable.carrot) -summer_squash = BundleItem(Vegetable.summer_squash) -broccoli = BundleItem(Vegetable.broccoli) -powdermelon = BundleItem(Fruit.powdermelon) - -egg = BundleItem(AnimalProduct.egg) -large_egg = BundleItem(AnimalProduct.large_egg) -brown_egg = BundleItem(AnimalProduct.brown_egg) -large_brown_egg = BundleItem(AnimalProduct.large_brown_egg) -wool = BundleItem(AnimalProduct.wool) -milk = BundleItem(AnimalProduct.milk) -large_milk = BundleItem(AnimalProduct.large_milk) -goat_milk = BundleItem(AnimalProduct.goat_milk) -large_goat_milk = BundleItem(AnimalProduct.large_goat_milk) -truffle = BundleItem(AnimalProduct.truffle) -duck_feather = BundleItem(AnimalProduct.duck_feather) -duck_egg = BundleItem(AnimalProduct.duck_egg) -rabbit_foot = BundleItem(AnimalProduct.rabbit_foot) -dinosaur_egg = BundleItem(AnimalProduct.dinosaur_egg) -void_egg = BundleItem(AnimalProduct.void_egg) -ostrich_egg = BundleItem(AnimalProduct.ostrich_egg, source=BundleItem.Sources.content) -golden_egg = BundleItem(AnimalProduct.golden_egg) - -truffle_oil = BundleItem(ArtisanGood.truffle_oil) -cloth = BundleItem(ArtisanGood.cloth) -goat_cheese = BundleItem(ArtisanGood.goat_cheese) -cheese = BundleItem(ArtisanGood.cheese) -honey = BundleItem(ArtisanGood.honey) -beer = BundleItem(Beverage.beer) -juice = BundleItem(ArtisanGood.juice) -mead = BundleItem(ArtisanGood.mead) -pale_ale = BundleItem(ArtisanGood.pale_ale) -wine = BundleItem(ArtisanGood.wine) -jelly = BundleItem(ArtisanGood.jelly) -pickles = BundleItem(ArtisanGood.pickles) -caviar = BundleItem(ArtisanGood.caviar) -aged_roe = BundleItem(ArtisanGood.aged_roe) -roe = BundleItem(AnimalProduct.roe) -squid_ink = BundleItem(AnimalProduct.squid_ink) -coffee = BundleItem(Beverage.coffee) -green_tea = BundleItem(ArtisanGood.green_tea) -apple = BundleItem(Fruit.apple) -apricot = BundleItem(Fruit.apricot) -orange = BundleItem(Fruit.orange) -peach = BundleItem(Fruit.peach) -pomegranate = BundleItem(Fruit.pomegranate) -cherry = BundleItem(Fruit.cherry) -banana = BundleItem(Fruit.banana, source=BundleItem.Sources.content) -mango = BundleItem(Fruit.mango, source=BundleItem.Sources.content) - -basic_fertilizer = BundleItem(Fertilizer.basic, 100) -quality_fertilizer = BundleItem(Fertilizer.quality, 20) -deluxe_fertilizer = BundleItem(Fertilizer.deluxe, 5, source=BundleItem.Sources.island) -basic_retaining_soil = BundleItem(RetainingSoil.basic, 80) -quality_retaining_soil = BundleItem(RetainingSoil.quality, 50) -deluxe_retaining_soil = BundleItem(RetainingSoil.deluxe, 20, source=BundleItem.Sources.island) -speed_gro = BundleItem(SpeedGro.basic, 40) -deluxe_speed_gro = BundleItem(SpeedGro.deluxe, 20) -hyper_speed_gro = BundleItem(SpeedGro.hyper, 5, source=BundleItem.Sources.island) -tree_fertilizer = BundleItem(Fertilizer.tree, 20) - -lobster = BundleItem(Fish.lobster) -crab = BundleItem(Fish.crab) -shrimp = BundleItem(Fish.shrimp) -crayfish = BundleItem(Fish.crayfish) -snail = BundleItem(Fish.snail) -periwinkle = BundleItem(Fish.periwinkle) -trash = BundleItem(Trash.trash) -driftwood = BundleItem(Trash.driftwood) -soggy_newspaper = BundleItem(Trash.soggy_newspaper) -broken_cd = BundleItem(Trash.broken_cd) -broken_glasses = BundleItem(Trash.broken_glasses) - -chub = BundleItem(Fish.chub) -catfish = BundleItem(Fish.catfish) -rainbow_trout = BundleItem(Fish.rainbow_trout) -lingcod = BundleItem(Fish.lingcod) -walleye = BundleItem(Fish.walleye) -perch = BundleItem(Fish.perch) -pike = BundleItem(Fish.pike) -bream = BundleItem(Fish.bream) -salmon = BundleItem(Fish.salmon) -sunfish = BundleItem(Fish.sunfish) -tiger_trout = BundleItem(Fish.tiger_trout) -shad = BundleItem(Fish.shad) -smallmouth_bass = BundleItem(Fish.smallmouth_bass) -dorado = BundleItem(Fish.dorado) -carp = BundleItem(Fish.carp) -midnight_carp = BundleItem(Fish.midnight_carp) -largemouth_bass = BundleItem(Fish.largemouth_bass) -sturgeon = BundleItem(Fish.sturgeon) -bullhead = BundleItem(Fish.bullhead) -tilapia = BundleItem(Fish.tilapia) -pufferfish = BundleItem(Fish.pufferfish) -tuna = BundleItem(Fish.tuna) -super_cucumber = BundleItem(Fish.super_cucumber) -flounder = BundleItem(Fish.flounder) -anchovy = BundleItem(Fish.anchovy) -sardine = BundleItem(Fish.sardine) -red_mullet = BundleItem(Fish.red_mullet) -herring = BundleItem(Fish.herring) -eel = BundleItem(Fish.eel) -octopus = BundleItem(Fish.octopus) -red_snapper = BundleItem(Fish.red_snapper) -squid = BundleItem(Fish.squid) -sea_cucumber = BundleItem(Fish.sea_cucumber) -albacore = BundleItem(Fish.albacore) -halibut = BundleItem(Fish.halibut) -scorpion_carp = BundleItem(Fish.scorpion_carp) -sandfish = BundleItem(Fish.sandfish) -woodskip = BundleItem(Fish.woodskip) -lava_eel = BundleItem(Fish.lava_eel) -ice_pip = BundleItem(Fish.ice_pip) -stonefish = BundleItem(Fish.stonefish) -ghostfish = BundleItem(Fish.ghostfish) - -bouquet = BundleItem(Gift.bouquet) -wilted_bouquet = BundleItem(Gift.wilted_bouquet) -copper_bar = BundleItem(MetalBar.copper) -iron_Bar = BundleItem(MetalBar.iron) -gold_bar = BundleItem(MetalBar.gold) -iridium_bar = BundleItem(MetalBar.iridium) -refined_quartz = BundleItem(MetalBar.quartz) -coal = BundleItem(Material.coal, 5) -iridium_ore = BundleItem(Ore.iridium) -gold_ore = BundleItem(Ore.gold) -iron_ore = BundleItem(Ore.iron) -copper_ore = BundleItem(Ore.copper) -battery_pack = BundleItem(ArtisanGood.battery_pack) - -quartz = BundleItem(Mineral.quartz) -fire_quartz = BundleItem(Mineral.fire_quartz) -frozen_tear = BundleItem(Mineral.frozen_tear) -earth_crystal = BundleItem(Mineral.earth_crystal) -emerald = BundleItem(Mineral.emerald) -aquamarine = BundleItem(Mineral.aquamarine) -ruby = BundleItem(Mineral.ruby) -amethyst = BundleItem(Mineral.amethyst) -topaz = BundleItem(Mineral.topaz) -jade = BundleItem(Mineral.jade) - -slime = BundleItem(Loot.slime, 99) -bug_meat = BundleItem(Loot.bug_meat, 10) -bat_wing = BundleItem(Loot.bat_wing, 10) -solar_essence = BundleItem(Loot.solar_essence) -void_essence = BundleItem(Loot.void_essence) - -petrified_slime = BundleItem(Mineral.petrified_slime) -blue_slime_egg = BundleItem(AnimalProduct.slime_egg_blue) -red_slime_egg = BundleItem(AnimalProduct.slime_egg_red) -purple_slime_egg = BundleItem(AnimalProduct.slime_egg_purple) -green_slime_egg = BundleItem(AnimalProduct.slime_egg_green) -tiger_slime_egg = BundleItem(AnimalProduct.slime_egg_tiger, source=BundleItem.Sources.island) - -cherry_bomb = BundleItem(Bomb.cherry_bomb, 5) -bomb = BundleItem(Bomb.bomb, 2) -mega_bomb = BundleItem(Bomb.mega_bomb) -explosive_ammo = BundleItem(Craftable.explosive_ammo, 5) - -maki_roll = BundleItem(Meal.maki_roll) -fried_egg = BundleItem(Meal.fried_egg) -omelet = BundleItem(Meal.omelet) -pizza = BundleItem(Meal.pizza) -hashbrowns = BundleItem(Meal.hashbrowns) -pancakes = BundleItem(Meal.pancakes) -bread = BundleItem(Meal.bread) -tortilla = BundleItem(Meal.tortilla) -triple_shot_espresso = BundleItem(Beverage.triple_shot_espresso) -farmer_s_lunch = BundleItem(Meal.farmer_lunch) -survival_burger = BundleItem(Meal.survival_burger) -dish_o_the_sea = BundleItem(Meal.dish_o_the_sea) -miner_s_treat = BundleItem(Meal.miners_treat) -roots_platter = BundleItem(Meal.roots_platter) -salad = BundleItem(Meal.salad) -cheese_cauliflower = BundleItem(Meal.cheese_cauliflower) -parsnip_soup = BundleItem(Meal.parsnip_soup) -fried_mushroom = BundleItem(Meal.fried_mushroom) -salmon_dinner = BundleItem(Meal.salmon_dinner) -pepper_poppers = BundleItem(Meal.pepper_poppers) -spaghetti = BundleItem(Meal.spaghetti) -sashimi = BundleItem(Meal.sashimi) -blueberry_tart = BundleItem(Meal.blueberry_tart) -algae_soup = BundleItem(Meal.algae_soup) -pale_broth = BundleItem(Meal.pale_broth) -chowder = BundleItem(Meal.chowder) -cookie = BundleItem(Meal.cookie) -ancient_doll = BundleItem(Artifact.ancient_doll) -ice_cream = BundleItem(Meal.ice_cream) -cranberry_candy = BundleItem(Meal.cranberry_candy) -ginger_ale = BundleItem(Beverage.ginger_ale, source=BundleItem.Sources.island) -pink_cake = BundleItem(Meal.pink_cake) -plum_pudding = BundleItem(Meal.plum_pudding) -chocolate_cake = BundleItem(Meal.chocolate_cake) -rhubarb_pie = BundleItem(Meal.rhubarb_pie) -shrimp_cocktail = BundleItem(Meal.shrimp_cocktail) -pina_colada = BundleItem(Beverage.pina_colada, source=BundleItem.Sources.island) -stuffing = BundleItem(Meal.stuffing) -magic_rock_candy = BundleItem(Meal.magic_rock_candy) -spicy_eel = BundleItem(Meal.spicy_eel) -crab_cakes = BundleItem(Meal.crab_cakes) -eggplant_parmesan = BundleItem(Meal.eggplant_parmesan) -pumpkin_soup = BundleItem(Meal.pumpkin_soup) -lucky_lunch = BundleItem(Meal.lucky_lunch) - -green_algae = BundleItem(WaterItem.green_algae) -white_algae = BundleItem(WaterItem.white_algae) -geode = BundleItem(Geode.geode) -frozen_geode = BundleItem(Geode.frozen) -magma_geode = BundleItem(Geode.magma) -omni_geode = BundleItem(Geode.omni) -sap = BundleItem(Material.sap) - -dwarf_scroll_1 = BundleItem(Artifact.dwarf_scroll_i) -dwarf_scroll_2 = BundleItem(Artifact.dwarf_scroll_ii) -dwarf_scroll_3 = BundleItem(Artifact.dwarf_scroll_iii) -dwarf_scroll_4 = BundleItem(Artifact.dwarf_scroll_iv) -elvish_jewelry = BundleItem(Artifact.elvish_jewelry) -ancient_drum = BundleItem(Artifact.ancient_drum) -dried_starfish = BundleItem(Fossil.dried_starfish) -bone_fragment = BundleItem(Fossil.bone_fragment) - -golden_mask = BundleItem(Artifact.golden_mask) -golden_relic = BundleItem(Artifact.golden_relic) -dwarf_gadget = BundleItem(Artifact.dwarf_gadget) -dwarvish_helm = BundleItem(Artifact.dwarvish_helm) -prehistoric_handaxe = BundleItem(Artifact.prehistoric_handaxe) -bone_flute = BundleItem(Artifact.bone_flute) -anchor = BundleItem(Artifact.anchor) -prehistoric_tool = BundleItem(Artifact.prehistoric_tool) -chicken_statue = BundleItem(Artifact.chicken_statue) -rusty_cog = BundleItem(Artifact.rusty_cog) -rusty_spur = BundleItem(Artifact.rusty_spur) -rusty_spoon = BundleItem(Artifact.rusty_spoon) -ancient_sword = BundleItem(Artifact.ancient_sword) -ornamental_fan = BundleItem(Artifact.ornamental_fan) -chipped_amphora = BundleItem(Artifact.chipped_amphora) - -prehistoric_scapula = BundleItem(Fossil.prehistoric_scapula) -prehistoric_tibia = BundleItem(Fossil.prehistoric_tibia) -prehistoric_skull = BundleItem(Fossil.prehistoric_skull) -skeletal_hand = BundleItem(Fossil.skeletal_hand) -prehistoric_rib = BundleItem(Fossil.prehistoric_rib) -prehistoric_vertebra = BundleItem(Fossil.prehistoric_vertebra) -skeletal_tail = BundleItem(Fossil.skeletal_tail) -nautilus_fossil = BundleItem(Fossil.nautilus_fossil) -amphibian_fossil = BundleItem(Fossil.amphibian_fossil) -palm_fossil = BundleItem(Fossil.palm_fossil) -trilobite = BundleItem(Fossil.trilobite) - -dinosaur_mayo = BundleItem(ArtisanGood.dinosaur_mayonnaise) -void_mayo = BundleItem(ArtisanGood.void_mayonnaise) -prismatic_shard = BundleItem(Mineral.prismatic_shard) -diamond = BundleItem(Mineral.diamond) -ancient_fruit = BundleItem(Fruit.ancient_fruit) -void_salmon = BundleItem(Fish.void_salmon) -tea_leaves = BundleItem(Vegetable.tea_leaves) -blobfish = BundleItem(Fish.blobfish) -spook_fish = BundleItem(Fish.spook_fish) -lionfish = BundleItem(Fish.lionfish, source=BundleItem.Sources.island) -blue_discus = BundleItem(Fish.blue_discus, source=BundleItem.Sources.island) -stingray = BundleItem(Fish.stingray, source=BundleItem.Sources.island) -spookfish = BundleItem(Fish.spookfish) -midnight_squid = BundleItem(Fish.midnight_squid) - -angler = BundleItem(Fish.angler) -crimsonfish = BundleItem(Fish.crimsonfish) -mutant_carp = BundleItem(Fish.mutant_carp) -glacierfish = BundleItem(Fish.glacierfish) -legend = BundleItem(Fish.legend) - -spinner = BundleItem(Fishing.spinner) -dressed_spinner = BundleItem(Fishing.dressed_spinner) -trap_bobber = BundleItem(Fishing.trap_bobber) -sonar_bobber = BundleItem(Fishing.sonar_bobber) -cork_bobber = BundleItem(Fishing.cork_bobber) -lead_bobber = BundleItem(Fishing.lead_bobber) -treasure_hunter = BundleItem(Fishing.treasure_hunter) -barbed_hook = BundleItem(Fishing.barbed_hook) -curiosity_lure = BundleItem(Fishing.curiosity_lure) -quality_bobber = BundleItem(Fishing.quality_bobber) -bait = BundleItem(Fishing.bait, 100) -deluxe_bait = BundleItem(Fishing.deluxe_bait, 50) -magnet = BundleItem(Fishing.magnet) -wild_bait = BundleItem(Fishing.wild_bait, 20) -magic_bait = BundleItem(Fishing.magic_bait, 10, source=BundleItem.Sources.island) -pearl = BundleItem(Gift.pearl) -challenge_bait = BundleItem(Fishing.challenge_bait, 25, source=BundleItem.Sources.masteries) -targeted_bait = BundleItem(ArtisanGood.targeted_bait, 25, source=BundleItem.Sources.content) - -ginger = BundleItem(Forageable.ginger, source=BundleItem.Sources.content) -magma_cap = BundleItem(Mushroom.magma_cap, source=BundleItem.Sources.content) - -wheat_flour = BundleItem(Ingredient.wheat_flour) -sugar = BundleItem(Ingredient.sugar) -vinegar = BundleItem(Ingredient.vinegar) - -jack_o_lantern = BundleItem(Lighting.jack_o_lantern) -prize_ticket = BundleItem(Currency.prize_ticket) -mystery_box = BundleItem(Consumable.mystery_box) -gold_mystery_box = BundleItem(Consumable.gold_mystery_box, source=BundleItem.Sources.masteries) -calico_egg = BundleItem(Currency.calico_egg) - -raccoon_crab_pot_fish_items = [periwinkle.as_amount(5), snail.as_amount(5), crayfish.as_amount(5), mussel.as_amount(5), - oyster.as_amount(5), cockle.as_amount(5), clam.as_amount(5)] -raccoon_smoked_fish_items = [BundleItem(ArtisanGood.smoked_fish, flavor=fish) for fish in - [Fish.largemouth_bass, Fish.bream, Fish.bullhead, Fish.chub, Fish.ghostfish, Fish.flounder, Fish.shad, - Fish.rainbow_trout, Fish.tilapia, Fish.red_mullet, Fish.tuna, Fish.midnight_carp, Fish.salmon, Fish.perch]] -raccoon_fish_items_flat = [*raccoon_crab_pot_fish_items, *raccoon_smoked_fish_items] -raccoon_fish_items_deep = [raccoon_crab_pot_fish_items, raccoon_smoked_fish_items] -raccoon_fish_bundle_vanilla = DeepBundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_fish, raccoon_fish_items_deep, 2, 2) -raccoon_fish_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_fish, raccoon_fish_items_flat, 3, 2) - -all_specific_jellies = [BundleItem(ArtisanGood.jelly, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits] -all_specific_pickles = [BundleItem(ArtisanGood.pickles, flavor=vegetable, source=BundleItem.Sources.content) for vegetable in all_vegetables] -all_specific_dried_fruits = [*[BundleItem(ArtisanGood.dried_fruit, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits], - BundleItem(ArtisanGood.raisins, source=BundleItem.Sources.content)] -all_specific_juices = [BundleItem(ArtisanGood.juice, flavor=vegetable, source=BundleItem.Sources.content) for vegetable in all_vegetables] -raccoon_artisan_items = [*all_specific_jellies, *all_specific_pickles, *all_specific_dried_fruits, *all_specific_juices] -raccoon_artisan_bundle_vanilla = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_artisan, raccoon_artisan_items, 2, 2) -raccoon_artisan_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_artisan, raccoon_artisan_items, 3, 2) - -all_specific_dried_mushrooms = [BundleItem(ArtisanGood.dried_mushroom, flavor=mushroom, source=BundleItem.Sources.content) for mushroom in all_edible_mushrooms] -raccoon_food_items = [egg.as_amount(5), cave_carrot.as_amount(5), white_algae.as_amount(5)] -raccoon_food_items_vanilla = [all_specific_dried_mushrooms, raccoon_food_items] -raccoon_food_items_thematic = [*all_specific_dried_mushrooms, *raccoon_food_items, brown_egg.as_amount(5), large_egg.as_amount(2), large_brown_egg.as_amount(2), - green_algae.as_amount(10)] -raccoon_food_bundle_vanilla = DeepBundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_food, raccoon_food_items_vanilla, 2, 2) -raccoon_food_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_food, raccoon_food_items_thematic, 3, 2) - -raccoon_foraging_items = [moss, rusty_spoon, trash.as_amount(5), slime.as_amount(99), bat_wing.as_amount(10), geode.as_amount(8), - frozen_geode.as_amount(5), magma_geode.as_amount(3), coral.as_amount(4), sea_urchin.as_amount(2), bug_meat.as_amount(10), - diamond, topaz.as_amount(3), ghostfish.as_amount(3)] -raccoon_foraging_bundle_vanilla = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_foraging, raccoon_foraging_items, 2, 2) -raccoon_foraging_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_foraging, raccoon_foraging_items, 3, 2) - -raccoon_bundles_vanilla = [raccoon_fish_bundle_vanilla, raccoon_artisan_bundle_vanilla, raccoon_food_bundle_vanilla, raccoon_foraging_bundle_vanilla] -raccoon_bundles_thematic = [raccoon_fish_bundle_thematic, raccoon_artisan_bundle_thematic, raccoon_food_bundle_thematic, raccoon_foraging_bundle_thematic] -raccoon_bundles_remixed = raccoon_bundles_thematic -raccoon_vanilla = BundleRoomTemplate(CCRoom.raccoon_requests, raccoon_bundles_vanilla, 8) -raccoon_thematic = BundleRoomTemplate(CCRoom.raccoon_requests, raccoon_bundles_thematic, 8) -raccoon_remixed = BundleRoomTemplate(CCRoom.raccoon_requests, raccoon_bundles_remixed, 8) - -# Crafts Room -spring_foraging_items_vanilla = [wild_horseradish, daffodil, leek, dandelion] -spring_foraging_items_thematic = [*spring_foraging_items_vanilla, spring_onion, salmonberry, morel] -spring_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.spring_foraging, spring_foraging_items_vanilla, 4, 4) -spring_foraging_bundle_thematic = BundleTemplate.extend_from(spring_foraging_bundle_vanilla, spring_foraging_items_thematic) - -summer_foraging_items_vanilla = [grape, spice_berry, sweet_pea] -summer_foraging_items_thematic = [*summer_foraging_items_vanilla, fiddlehead_fern, red_mushroom, rainbow_shell] -summer_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.summer_foraging, summer_foraging_items_vanilla, 3, 3) -summer_foraging_bundle_thematic = BundleTemplate.extend_from(summer_foraging_bundle_vanilla, summer_foraging_items_thematic) - -fall_foraging_items_vanilla = [common_mushroom, wild_plum, hazelnut, blackberry] -fall_foraging_items_thematic = [*fall_foraging_items_vanilla, chanterelle] -fall_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.fall_foraging, fall_foraging_items_vanilla, 4, 4) -fall_foraging_bundle_thematic = BundleTemplate.extend_from(fall_foraging_bundle_vanilla, fall_foraging_items_thematic) - -winter_foraging_items_vanilla = [winter_root, crystal_fruit, snow_yam, crocus] -winter_foraging_items_thematic = [*winter_foraging_items_vanilla, holly, nautilus_shell] -winter_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.winter_foraging, winter_foraging_items_vanilla, 4, 4) -winter_foraging_bundle_thematic = BundleTemplate.extend_from(winter_foraging_bundle_vanilla, winter_foraging_items_thematic) - -construction_items_vanilla = [wood, stone, hardwood] -construction_items_thematic = [*construction_items_vanilla, clay, fiber, sap.as_amount(50)] -construction_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.construction, construction_items_vanilla, 4, 4) -construction_bundle_thematic = BundleTemplate.extend_from(construction_bundle_vanilla, construction_items_thematic) - -exotic_foraging_items_vanilla = [coconut, cactus_fruit, cave_carrot, red_mushroom, purple_mushroom, maple_syrup, oak_resin, pine_tar, morel] -exotic_foraging_items_thematic = [*exotic_foraging_items_vanilla, coral, sea_urchin, clam, cockle, mussel, oyster, seaweed] -exotic_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.exotic_foraging, exotic_foraging_items_vanilla, 9, 5) -exotic_foraging_bundle_thematic = BundleTemplate.extend_from(exotic_foraging_bundle_vanilla, exotic_foraging_items_thematic) - -beach_foraging_items = [nautilus_shell, coral, sea_urchin, rainbow_shell, clam, cockle, mussel, oyster, seaweed] -beach_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.beach_foraging, beach_foraging_items, 4, 4) - -mines_foraging_items = [quartz, earth_crystal, frozen_tear, fire_quartz, red_mushroom, purple_mushroom, cave_carrot] -mines_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.mines_foraging, mines_foraging_items, 4, 4) - -desert_foraging_items = [cactus_fruit.as_quality(ForageQuality.gold), cactus_fruit.as_amount(5), coconut.as_quality(ForageQuality.gold), coconut.as_amount(5)] -desert_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.desert_foraging, desert_foraging_items, 2, 2) - -island_foraging_items = [ginger.as_amount(5), magma_cap.as_quality(ForageQuality.gold), magma_cap.as_amount(5), - fiddlehead_fern.as_quality(ForageQuality.gold), fiddlehead_fern.as_amount(5)] -island_foraging_bundle = IslandBundleTemplate(CCRoom.crafts_room, BundleName.island_foraging, island_foraging_items, 2, 2) - -sticky_items = [sap.as_amount(500), sap.as_amount(500)] -sticky_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.sticky, sticky_items, 1, 1) - -forest_items = [moss, fiber.as_amount(200), acorn.as_amount(10), maple_seed.as_amount(10), pine_cone.as_amount(10), mahogany_seed, - mushroom_tree_seed, mossy_seed.as_amount(5), mystic_tree_seed] -forest_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.forest, forest_items, 4, 2) - -wild_medicine_items = [item.as_amount(5) for item in [purple_mushroom, fiddlehead_fern, white_algae, hops, blackberry, dandelion]] -wild_medicine_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.wild_medicine, wild_medicine_items, 4, 3) - -quality_foraging_items = sorted({item.as_quality(ForageQuality.gold).as_amount(3) - for item in - [*spring_foraging_items_thematic, *summer_foraging_items_thematic, *fall_foraging_items_thematic, - *winter_foraging_items_thematic, *beach_foraging_items, *desert_foraging_items, magma_cap] if item.can_have_quality}) -quality_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.quality_foraging, quality_foraging_items, 4, 3) - -green_rain_items = [moss.as_amount(200), fiber.as_amount(200), mossy_seed.as_amount(20), fiddlehead_fern.as_amount(10)] -green_rain_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.green_rain, green_rain_items, 4, 3) - -crafts_room_bundles_vanilla = [spring_foraging_bundle_vanilla, summer_foraging_bundle_vanilla, fall_foraging_bundle_vanilla, - winter_foraging_bundle_vanilla, construction_bundle_vanilla, exotic_foraging_bundle_vanilla] -crafts_room_bundles_thematic = [spring_foraging_bundle_thematic, summer_foraging_bundle_thematic, fall_foraging_bundle_thematic, - winter_foraging_bundle_thematic, construction_bundle_thematic, exotic_foraging_bundle_thematic] -crafts_room_bundles_remixed = [*crafts_room_bundles_thematic, beach_foraging_bundle, mines_foraging_bundle, desert_foraging_bundle, - island_foraging_bundle, sticky_bundle, forest_bundle, wild_medicine_bundle, quality_foraging_bundle, green_rain_bundle] -crafts_room_vanilla = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_vanilla, 6) -crafts_room_thematic = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_thematic, 6) -crafts_room_remixed = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_remixed, 6) - -# Pantry -spring_crops_items_vanilla = [parsnip, green_bean, cauliflower, potato] -spring_crops_items_thematic = [*spring_crops_items_vanilla, blue_jazz, coffee_bean, garlic, kale, rhubarb, strawberry, tulip, unmilled_rice, carrot] -spring_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.spring_crops, spring_crops_items_vanilla, 4, 4) -spring_crops_bundle_thematic = BundleTemplate.extend_from(spring_crops_bundle_vanilla, spring_crops_items_thematic) - -summer_crops_items_vanilla = [tomato, hot_pepper, blueberry, melon] -summer_crops_items_thematic = [*summer_crops_items_vanilla, corn, hops, poppy, radish, red_cabbage, starfruit, summer_spangle, sunflower, wheat, summer_squash] -summer_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.summer_crops, summer_crops_items_vanilla, 4, 4) -summer_crops_bundle_thematic = BundleTemplate.extend_from(summer_crops_bundle_vanilla, summer_crops_items_thematic) - -fall_crops_items_vanilla = [corn, eggplant, pumpkin, yam] -fall_crops_items_thematic = [*fall_crops_items_vanilla, amaranth, artichoke, beet, bok_choy, cranberries, fairy_rose, grape, - sunflower, wheat, sweet_gem_berry, broccoli] -fall_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.fall_crops, fall_crops_items_vanilla, 4, 4) -fall_crops_bundle_thematic = BundleTemplate.extend_from(fall_crops_bundle_vanilla, fall_crops_items_thematic) - -all_crops_items = sorted({*spring_crops_items_thematic, *summer_crops_items_thematic, *fall_crops_items_thematic, powdermelon}) - -quality_crops_items_vanilla = [item.as_quality_crop() for item in [parsnip, melon, pumpkin, corn]] -quality_crops_items_thematic = [item.as_quality_crop() for item in all_crops_items] -quality_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.quality_crops, quality_crops_items_vanilla, 4, 3) -quality_crops_bundle_thematic = BundleTemplate.extend_from(quality_crops_bundle_vanilla, quality_crops_items_thematic) - -animal_items_vanilla = [large_milk, large_brown_egg, large_egg, large_goat_milk, wool, duck_egg] -animal_items_thematic = [*animal_items_vanilla, egg, brown_egg, milk, goat_milk, truffle, - duck_feather, rabbit_foot, dinosaur_egg, void_egg, golden_egg, ostrich_egg] -animal_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.animal, animal_items_vanilla, 6, 5) -animal_bundle_thematic = BundleTemplate.extend_from(animal_bundle_vanilla, animal_items_thematic) - -artisan_items_vanilla = [truffle_oil, cloth, goat_cheese, cheese, honey, jelly, apple, apricot, orange, peach, pomegranate, cherry] -artisan_items_thematic = [*artisan_items_vanilla, beer, juice, mead, pale_ale, wine, pickles, caviar, aged_roe, coffee, green_tea, banana, mango] -artisan_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.artisan, artisan_items_vanilla, 12, 6) -artisan_bundle_thematic = BundleTemplate.extend_from(artisan_bundle_vanilla, artisan_items_thematic) - -rare_crops_items = [ancient_fruit, sweet_gem_berry] -rare_crops_bundle = BundleTemplate(CCRoom.pantry, BundleName.rare_crops, rare_crops_items, 2, 2) - -# all_specific_roes = [BundleItem(AnimalProduct.roe, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fish] -fish_farmer_items = [roe.as_amount(15), aged_roe.as_amount(5), squid_ink, caviar.as_amount(5)] -fish_farmer_bundle = BundleTemplate(CCRoom.pantry, BundleName.fish_farmer, fish_farmer_items, 3, 2) - -garden_items = [tulip, blue_jazz, summer_spangle, sunflower, fairy_rose, poppy, bouquet] -garden_bundle = BundleTemplate(CCRoom.pantry, BundleName.garden, garden_items, 5, 4) - -brewer_items = [mead, pale_ale, wine, juice, green_tea, beer] -brewer_bundle = BundleTemplate(CCRoom.pantry, BundleName.brewer, brewer_items, 5, 4) - -orchard_items = [apple, apricot, orange, peach, pomegranate, cherry, banana, mango] -orchard_bundle = BundleTemplate(CCRoom.pantry, BundleName.orchard, orchard_items, 6, 4) - -island_crops_items = [pineapple, taro_root, banana, mango] -island_crops_bundle = IslandBundleTemplate(CCRoom.pantry, BundleName.island_crops, island_crops_items, 3, 3) - -agronomist_items = [basic_fertilizer, quality_fertilizer, deluxe_fertilizer, - basic_retaining_soil, quality_retaining_soil, deluxe_retaining_soil, - speed_gro, deluxe_speed_gro, hyper_speed_gro, tree_fertilizer] -agronomist_bundle = BundleTemplate(CCRoom.pantry, BundleName.agronomist, agronomist_items, 4, 3) - -slime_farmer_items = [slime.as_amount(99), petrified_slime.as_amount(10), blue_slime_egg, red_slime_egg, - purple_slime_egg, green_slime_egg, tiger_slime_egg] -slime_farmer_bundle = BundleTemplate(CCRoom.pantry, BundleName.slime_farmer, slime_farmer_items, 4, 3) - -sommelier_items = [BundleItem(ArtisanGood.wine, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits] -sommelier_bundle = BundleTemplate(CCRoom.pantry, BundleName.sommelier, sommelier_items, 6, 3) - -dry_items = [*[BundleItem(ArtisanGood.dried_fruit, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits], - *[BundleItem(ArtisanGood.dried_mushroom, flavor=mushroom, source=BundleItem.Sources.content) for mushroom in all_edible_mushrooms], - BundleItem(ArtisanGood.raisins, source=BundleItem.Sources.content)] -dry_bundle = BundleTemplate(CCRoom.pantry, BundleName.dry, dry_items, 6, 3) - -pantry_bundles_vanilla = [spring_crops_bundle_vanilla, summer_crops_bundle_vanilla, fall_crops_bundle_vanilla, - quality_crops_bundle_vanilla, animal_bundle_vanilla, artisan_bundle_vanilla] -pantry_bundles_thematic = [spring_crops_bundle_thematic, summer_crops_bundle_thematic, fall_crops_bundle_thematic, - quality_crops_bundle_thematic, animal_bundle_thematic, artisan_bundle_thematic] -pantry_bundles_remixed = [*pantry_bundles_thematic, rare_crops_bundle, fish_farmer_bundle, garden_bundle, - brewer_bundle, orchard_bundle, island_crops_bundle, agronomist_bundle, slime_farmer_bundle, sommelier_bundle, dry_bundle] -pantry_vanilla = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_vanilla, 6) -pantry_thematic = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_thematic, 6) -pantry_remixed = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_remixed, 6) - -# Fish Tank -river_fish_items_vanilla = [sunfish, catfish, shad, tiger_trout] -river_fish_items_thematic = [*river_fish_items_vanilla, chub, rainbow_trout, lingcod, walleye, perch, pike, bream, salmon, smallmouth_bass, dorado] -river_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.river_fish, river_fish_items_vanilla, 4, 4) -river_fish_bundle_thematic = BundleTemplate.extend_from(river_fish_bundle_vanilla, river_fish_items_thematic) - -lake_fish_items_vanilla = [largemouth_bass, carp, bullhead, sturgeon] -lake_fish_items_thematic = [*lake_fish_items_vanilla, chub, rainbow_trout, lingcod, walleye, perch, midnight_carp] -lake_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.lake_fish, lake_fish_items_vanilla, 4, 4) -lake_fish_bundle_thematic = BundleTemplate.extend_from(lake_fish_bundle_vanilla, lake_fish_items_thematic) - -ocean_fish_items_vanilla = [sardine, tuna, red_snapper, tilapia] -ocean_fish_items_thematic = [*ocean_fish_items_vanilla, pufferfish, super_cucumber, flounder, anchovy, red_mullet, - herring, eel, octopus, squid, sea_cucumber, albacore, halibut] -ocean_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.ocean_fish, ocean_fish_items_vanilla, 4, 4) -ocean_fish_bundle_thematic = BundleTemplate.extend_from(ocean_fish_bundle_vanilla, ocean_fish_items_thematic) - -night_fish_items_vanilla = [walleye, bream, eel] -night_fish_items_thematic = [*night_fish_items_vanilla, super_cucumber, squid, midnight_carp, midnight_squid] -night_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.night_fish, night_fish_items_vanilla, 3, 3) -night_fish_bundle_thematic = BundleTemplate.extend_from(night_fish_bundle_vanilla, night_fish_items_thematic) - -crab_pot_items_vanilla = [lobster, crayfish, crab, cockle, mussel, shrimp, snail, periwinkle, oyster, clam] -crab_pot_trash_items = [trash, driftwood, soggy_newspaper, broken_cd, broken_glasses] -crab_pot_items_thematic = [*crab_pot_items_vanilla, *crab_pot_trash_items] -crab_pot_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.crab_pot, crab_pot_items_vanilla, 10, 5) -crab_pot_bundle_thematic = BundleTemplate.extend_from(crab_pot_bundle_vanilla, crab_pot_items_thematic) -trash_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.trash, crab_pot_trash_items, 4, 4) - -specialty_fish_items_vanilla = [pufferfish, ghostfish, sandfish, woodskip] -specialty_fish_items_thematic = [*specialty_fish_items_vanilla, scorpion_carp, eel, octopus, lava_eel, ice_pip, - stonefish, void_salmon, stingray, spookfish, midnight_squid] -specialty_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.specialty_fish, specialty_fish_items_vanilla, 4, 4) -specialty_fish_bundle_thematic = BundleTemplate.extend_from(specialty_fish_bundle_vanilla, specialty_fish_items_thematic) - -spring_fish_items = [herring, halibut, shad, flounder, sunfish, sardine, catfish, anchovy, smallmouth_bass, eel, legend] -spring_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.spring_fish, spring_fish_items, 4, 4) - -summer_fish_items = [tuna, pike, red_mullet, sturgeon, red_snapper, super_cucumber, tilapia, pufferfish, rainbow_trout, - octopus, dorado, halibut, shad, flounder, sunfish, crimsonfish] -summer_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.summer_fish, summer_fish_items, 4, 4) - -fall_fish_items = [red_snapper, super_cucumber, tilapia, shad, sardine, catfish, anchovy, smallmouth_bass, eel, midnight_carp, - walleye, sea_cucumber, tiger_trout, albacore, salmon, angler] -fall_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.fall_fish, fall_fish_items, 4, 4) - -winter_fish_items = [perch, squid, lingcod, tuna, pike, red_mullet, sturgeon, red_snapper, herring, halibut, sardine, - midnight_carp, sea_cucumber, tiger_trout, albacore, glacierfish] -winter_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.winter_fish, winter_fish_items, 4, 4) - -rain_fish_items = [red_snapper, shad, catfish, eel, walleye] -rain_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.rain_fish, rain_fish_items, 3, 3) - -quality_fish_items = sorted({ - item.as_quality(FishQuality.gold).as_amount(2) - for item in [*river_fish_items_thematic, *lake_fish_items_thematic, *ocean_fish_items_thematic] -}) -quality_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.quality_fish, quality_fish_items, 4, 3) - -master_fisher_items = [lava_eel, scorpion_carp, octopus, blobfish, lingcod, ice_pip, super_cucumber, stingray, void_salmon, pufferfish] -master_fisher_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.master_fisher, master_fisher_items, 4, 2) - -legendary_fish_items = [angler, legend, mutant_carp, crimsonfish, glacierfish] -legendary_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.legendary_fish, legendary_fish_items, 4, 2) - -island_fish_items = [lionfish, blue_discus, stingray] -island_fish_bundle = IslandBundleTemplate(CCRoom.fish_tank, BundleName.island_fish, island_fish_items, 3, 3) - -tackle_items = [spinner, dressed_spinner, trap_bobber, sonar_bobber, cork_bobber, lead_bobber, treasure_hunter, barbed_hook, curiosity_lure, quality_bobber] -tackle_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.tackle, tackle_items, 3, 2) - -bait_items = [bait, magnet, wild_bait, magic_bait, challenge_bait, deluxe_bait, targeted_bait] -bait_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.bait, bait_items, 3, 2) - -# This bundle could change based on content packs, once the fish are properly in it. Until then, I'm not sure how, so pelican town only -specific_bait_items = [BundleItem(ArtisanGood.targeted_bait, flavor=fish.name).as_amount(10) for fish in content_packs.pelican_town.fishes] -specific_bait_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.specific_bait, specific_bait_items, 6, 3) - -deep_fishing_items = [blobfish, spook_fish, midnight_squid, sea_cucumber, super_cucumber, octopus, pearl, seaweed] -deep_fishing_bundle = FestivalBundleTemplate(CCRoom.fish_tank, BundleName.deep_fishing, deep_fishing_items, 4, 3) - -smokeable_fish = [Fish.largemouth_bass, Fish.bream, Fish.bullhead, Fish.chub, Fish.ghostfish, Fish.flounder, Fish.shad, Fish.rainbow_trout, Fish.tilapia, - Fish.red_mullet, Fish.tuna, Fish.midnight_carp, Fish.salmon, Fish.perch] -fish_smoker_items = [BundleItem(ArtisanGood.smoked_fish, flavor=fish) for fish in smokeable_fish] -fish_smoker_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.fish_smoker, fish_smoker_items, 6, 3) - -fish_tank_bundles_vanilla = [river_fish_bundle_vanilla, lake_fish_bundle_vanilla, ocean_fish_bundle_vanilla, - night_fish_bundle_vanilla, crab_pot_bundle_vanilla, specialty_fish_bundle_vanilla] -fish_tank_bundles_thematic = [river_fish_bundle_thematic, lake_fish_bundle_thematic, ocean_fish_bundle_thematic, - night_fish_bundle_thematic, crab_pot_bundle_thematic, specialty_fish_bundle_thematic] -fish_tank_bundles_remixed = [*fish_tank_bundles_thematic, spring_fish_bundle, summer_fish_bundle, fall_fish_bundle, winter_fish_bundle, trash_bundle, - rain_fish_bundle, quality_fish_bundle, master_fisher_bundle, legendary_fish_bundle, tackle_bundle, bait_bundle, - specific_bait_bundle, deep_fishing_bundle, fish_smoker_bundle] - -# In Remixed, the trash items are in the recycling bundle, so we don't use the thematic version of the crab pot bundle that added trash items to it -fish_tank_bundles_remixed.remove(crab_pot_bundle_thematic) -fish_tank_bundles_remixed.append(crab_pot_bundle_vanilla) -fish_tank_vanilla = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_vanilla, 6) -fish_tank_thematic = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_thematic, 6) -fish_tank_remixed = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_remixed, 6) - -# Boiler Room -blacksmith_items_vanilla = [copper_bar, iron_Bar, gold_bar] -blacksmith_items_thematic = [*blacksmith_items_vanilla, iridium_bar, refined_quartz.as_amount(3), wilted_bouquet] -blacksmith_bundle_vanilla = BundleTemplate(CCRoom.boiler_room, BundleName.blacksmith, blacksmith_items_vanilla, 3, 3) -blacksmith_bundle_thematic = BundleTemplate.extend_from(blacksmith_bundle_vanilla, blacksmith_items_thematic) - -geologist_items_vanilla = [quartz, earth_crystal, frozen_tear, fire_quartz] -geologist_items_thematic = [*geologist_items_vanilla, emerald, aquamarine, ruby, amethyst, topaz, jade, diamond] -geologist_bundle_vanilla = BundleTemplate(CCRoom.boiler_room, BundleName.geologist, geologist_items_vanilla, 4, 4) -geologist_bundle_thematic = BundleTemplate.extend_from(geologist_bundle_vanilla, geologist_items_thematic) - -adventurer_items_vanilla = [slime, bat_wing, solar_essence, void_essence] -adventurer_items_thematic = [*adventurer_items_vanilla, bug_meat, coal, bone_fragment.as_amount(10)] -adventurer_bundle_vanilla = BundleTemplate(CCRoom.boiler_room, BundleName.adventurer, adventurer_items_vanilla, 4, 2) -adventurer_bundle_thematic = BundleTemplate.extend_from(adventurer_bundle_vanilla, adventurer_items_thematic) - -# Where to put radioactive bar? -treasure_hunter_items = [emerald, aquamarine, ruby, amethyst, topaz, jade, diamond] -treasure_hunter_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.treasure_hunter, treasure_hunter_items, 6, 5) - -engineer_items = [iridium_ore.as_amount(5), battery_pack, refined_quartz.as_amount(5), diamond] -engineer_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.engineer, engineer_items, 3, 3) - -demolition_items = [cherry_bomb, bomb, mega_bomb, explosive_ammo] -demolition_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.demolition, demolition_items, 3, 3) - -recycling_items = [stone, coal, iron_ore, wood, cloth, refined_quartz] -recycling_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.recycling, recycling_items, 4, 4) - -archaeologist_items = [golden_mask, golden_relic, ancient_drum, dwarf_gadget, dwarvish_helm, prehistoric_handaxe, bone_flute, anchor, prehistoric_tool, - chicken_statue, rusty_cog, rusty_spur, rusty_spoon, ancient_sword, ornamental_fan, elvish_jewelry, ancient_doll, chipped_amphora] -archaeologist_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.archaeologist, archaeologist_items, 6, 3) - -paleontologist_items = [prehistoric_scapula, prehistoric_tibia, prehistoric_skull, skeletal_hand, prehistoric_rib, prehistoric_vertebra, skeletal_tail, - nautilus_fossil, amphibian_fossil, palm_fossil, trilobite] -paleontologist_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.paleontologist, paleontologist_items, 6, 3) - -boiler_room_bundles_vanilla = [blacksmith_bundle_vanilla, geologist_bundle_vanilla, adventurer_bundle_vanilla] -boiler_room_bundles_thematic = [blacksmith_bundle_thematic, geologist_bundle_thematic, adventurer_bundle_thematic] -boiler_room_bundles_remixed = [*boiler_room_bundles_thematic, treasure_hunter_bundle, engineer_bundle, - demolition_bundle, recycling_bundle, archaeologist_bundle, paleontologist_bundle] -boiler_room_vanilla = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_vanilla, 3) -boiler_room_thematic = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_thematic, 3) -boiler_room_remixed = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_remixed, 3) - -# Bulletin Board -chef_items_vanilla = [maple_syrup, fiddlehead_fern, truffle, poppy, maki_roll, fried_egg] -# More recipes? -chef_items_thematic = [maki_roll, fried_egg, omelet, pizza, hashbrowns, pancakes, bread, tortilla, - farmer_s_lunch, survival_burger, dish_o_the_sea, miner_s_treat, roots_platter, salad, - cheese_cauliflower, parsnip_soup, fried_mushroom, salmon_dinner, pepper_poppers, spaghetti, - sashimi, blueberry_tart, algae_soup, pale_broth, chowder] -chef_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.chef, chef_items_vanilla, 6, 6) -chef_bundle_thematic = BundleTemplate.extend_from(chef_bundle_vanilla, chef_items_thematic) - -dye_items_vanilla = [red_mushroom, sea_urchin, sunflower, duck_feather, aquamarine, red_cabbage] -dye_red_items = [cranberries, hot_pepper, radish, rhubarb, spaghetti, strawberry, tomato, tulip, red_mushroom] -dye_orange_items = [poppy, pumpkin, apricot, orange, spice_berry, winter_root] -dye_yellow_items = [corn, parsnip, summer_spangle, sunflower, starfruit] -dye_green_items = [fiddlehead_fern, kale, artichoke, bok_choy, green_bean, cactus_fruit, duck_feather, dinosaur_egg] -dye_blue_items = [blueberry, blue_jazz, blackberry, crystal_fruit, aquamarine] -dye_purple_items = [beet, crocus, eggplant, red_cabbage, sweet_pea, iridium_bar, sea_urchin, amaranth] -dye_items_thematic = [dye_red_items, dye_orange_items, dye_yellow_items, dye_green_items, dye_blue_items, dye_purple_items] -dye_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.dye, dye_items_vanilla, 6, 6) -dye_bundle_thematic = DeepBundleTemplate(CCRoom.bulletin_board, BundleName.dye, dye_items_thematic, 6, 6) - -field_research_items_vanilla = [purple_mushroom, nautilus_shell, chub, frozen_geode] -field_research_items_thematic = [*field_research_items_vanilla, geode, magma_geode, omni_geode, - rainbow_shell, amethyst, bream, carp] -field_research_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.field_research, field_research_items_vanilla, 4, 4) -field_research_bundle_thematic = BundleTemplate.extend_from(field_research_bundle_vanilla, field_research_items_thematic) - -fodder_items_vanilla = [wheat.as_amount(10), hay.as_amount(10), apple.as_amount(3)] -fodder_items_thematic = [*fodder_items_vanilla, kale.as_amount(3), corn.as_amount(3), green_bean.as_amount(3), - potato.as_amount(3), green_algae.as_amount(5), white_algae.as_amount(3)] -fodder_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.fodder, fodder_items_vanilla, 3, 3) -fodder_bundle_thematic = BundleTemplate.extend_from(fodder_bundle_vanilla, fodder_items_thematic) - -enchanter_items_vanilla = [oak_resin, wine, rabbit_foot, pomegranate] -enchanter_items_thematic = [*enchanter_items_vanilla, purple_mushroom, solar_essence, - super_cucumber, void_essence, fire_quartz, frozen_tear, jade] -enchanter_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.enchanter, enchanter_items_vanilla, 4, 4) -enchanter_bundle_thematic = BundleTemplate.extend_from(enchanter_bundle_vanilla, enchanter_items_thematic) - -children_items = [salmonberry.as_amount(10), cookie, ancient_doll, ice_cream, cranberry_candy, ginger_ale, - grape.as_amount(10), pink_cake, snail, fairy_rose, plum_pudding] -children_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.children, children_items, 4, 3) - -forager_items = [salmonberry.as_amount(50), blackberry.as_amount(50), wild_plum.as_amount(20), snow_yam.as_amount(20), - common_mushroom.as_amount(20), grape.as_amount(20), spring_onion.as_amount(20)] -forager_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.forager, forager_items, 3, 2) - -home_cook_items = [egg.as_amount(10), milk.as_amount(10), wheat_flour.as_amount(100), sugar.as_amount(100), vinegar.as_amount(100), - chocolate_cake, pancakes, rhubarb_pie] -home_cook_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.home_cook, home_cook_items, 3, 3) - -helper_items = [prize_ticket, mystery_box.as_amount(5), gold_mystery_box] -helper_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.helper, helper_items, 2, 2) - -spirit_eve_items = [jack_o_lantern, corn.as_amount(10), bat_wing.as_amount(10)] -spirit_eve_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.spirit_eve, spirit_eve_items, 3, 3) - -winter_star_items = [holly.as_amount(5), plum_pudding, stuffing, powdermelon.as_amount(5)] -winter_star_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.winter_star, winter_star_items, 2, 2) - -bartender_items = [shrimp_cocktail, triple_shot_espresso, ginger_ale, cranberry_candy, beer, pale_ale, pina_colada] -bartender_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.bartender, bartender_items, 3, 3) - -calico_items = [calico_egg.as_amount(200), calico_egg.as_amount(200), calico_egg.as_amount(200), calico_egg.as_amount(200), - magic_rock_candy, mega_bomb.as_amount(10), mystery_box.as_amount(10), mixed_seeds.as_amount(50), - strawberry_seeds.as_amount(20), - spicy_eel.as_amount(5), crab_cakes.as_amount(5), eggplant_parmesan.as_amount(5), - pumpkin_soup.as_amount(5), lucky_lunch.as_amount(5) ] -calico_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.calico, calico_items, 2, 2) - -raccoon_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.raccoon, raccoon_foraging_items, 4, 4) - -bulletin_board_bundles_vanilla = [chef_bundle_vanilla, dye_bundle_vanilla, field_research_bundle_vanilla, fodder_bundle_vanilla, enchanter_bundle_vanilla] -bulletin_board_bundles_thematic = [chef_bundle_thematic, dye_bundle_thematic, field_research_bundle_thematic, fodder_bundle_thematic, enchanter_bundle_thematic] -bulletin_board_bundles_remixed = [*bulletin_board_bundles_thematic, children_bundle, forager_bundle, home_cook_bundle, - helper_bundle, spirit_eve_bundle, winter_star_bundle, bartender_bundle, calico_bundle, raccoon_bundle] -bulletin_board_vanilla = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_vanilla, 5) -bulletin_board_thematic = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_thematic, 5) -bulletin_board_remixed = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_remixed, 5) - -missing_bundle_items_vanilla = [wine.as_quality(ArtisanQuality.silver), dinosaur_mayo, prismatic_shard, caviar, - ancient_fruit.as_quality_crop(), void_salmon.as_quality(FishQuality.gold)] -missing_bundle_items_thematic = [*missing_bundle_items_vanilla, pale_ale.as_quality(ArtisanQuality.silver), beer.as_quality(ArtisanQuality.silver), - mead.as_quality(ArtisanQuality.silver), - cheese.as_quality(ArtisanQuality.silver), goat_cheese.as_quality(ArtisanQuality.silver), void_mayo, cloth, green_tea, - truffle_oil, diamond, - sweet_gem_berry.as_quality_crop(), starfruit.as_quality_crop(), - tea_leaves.as_amount(5), lava_eel.as_quality(FishQuality.gold), scorpion_carp.as_quality(FishQuality.gold), - blobfish.as_quality(FishQuality.gold)] -missing_bundle_vanilla = BundleTemplate(CCRoom.abandoned_joja_mart, BundleName.missing_bundle, missing_bundle_items_vanilla, 6, 5) -missing_bundle_thematic = BundleTemplate.extend_from(missing_bundle_vanilla, missing_bundle_items_thematic) - -abandoned_joja_mart_bundles_vanilla = [missing_bundle_vanilla] -abandoned_joja_mart_bundles_thematic = [missing_bundle_thematic] -abandoned_joja_mart_vanilla = BundleRoomTemplate(CCRoom.abandoned_joja_mart, abandoned_joja_mart_bundles_vanilla, 1) -abandoned_joja_mart_thematic = BundleRoomTemplate(CCRoom.abandoned_joja_mart, abandoned_joja_mart_bundles_thematic, 1) -abandoned_joja_mart_remixed = abandoned_joja_mart_thematic - -vault_2500_gold = BundleItem.money_bundle(2500) -vault_5000_gold = BundleItem.money_bundle(5000) -vault_10000_gold = BundleItem.money_bundle(10000) -vault_25000_gold = BundleItem.money_bundle(25000) - -vault_2500_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_2500, vault_2500_gold) -vault_5000_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_5000, vault_5000_gold) -vault_10000_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_10000, vault_10000_gold) -vault_25000_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_25000, vault_25000_gold) - -vault_gambler_items = BundleItem(Currency.qi_coin, 10000) -vault_gambler_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.gambler, vault_gambler_items) - -vault_carnival_items = BundleItem(Currency.star_token, 2500, source=BundleItem.Sources.festival) -vault_carnival_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.carnival, vault_carnival_items) - -vault_walnut_hunter_items = BundleItem(Currency.golden_walnut, 25) -vault_walnut_hunter_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.walnut_hunter, vault_walnut_hunter_items) - -vault_qi_helper_items = BundleItem(Currency.qi_gem, 25, source=BundleItem.Sources.island) -vault_qi_helper_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.qi_helper, vault_qi_helper_items) - -vault_bundles_vanilla = [vault_2500_bundle, vault_5000_bundle, vault_10000_bundle, vault_25000_bundle] -vault_bundles_thematic = vault_bundles_vanilla -vault_bundles_remixed = [*vault_bundles_vanilla, vault_gambler_bundle, vault_qi_helper_bundle, vault_carnival_bundle] # , vault_walnut_hunter_bundle -vault_vanilla = BundleRoomTemplate(CCRoom.vault, vault_bundles_vanilla, 4) -vault_thematic = BundleRoomTemplate(CCRoom.vault, vault_bundles_thematic, 4) -vault_remixed = BundleRoomTemplate(CCRoom.vault, vault_bundles_remixed, 4) - -all_cc_remixed_bundles = [*crafts_room_bundles_remixed, *pantry_bundles_remixed, *fish_tank_bundles_remixed, - *boiler_room_bundles_remixed, *bulletin_board_bundles_remixed] -community_center_remixed_anywhere = BundleRoomTemplate("Community Center", all_cc_remixed_bundles, 26) - -all_bundle_items_except_money = [] -all_remixed_bundles = [*crafts_room_bundles_remixed, *pantry_bundles_remixed, *fish_tank_bundles_remixed, - *boiler_room_bundles_remixed, *bulletin_board_bundles_remixed, missing_bundle_thematic, - *raccoon_bundles_remixed] -for bundle in all_remixed_bundles: - all_bundle_items_except_money.extend(bundle.items) - -all_bundle_items_by_name = {item.item_name: item for item in all_bundle_items_except_money} diff --git a/worlds/stardew_valley/data/bundles_data/__init__.py b/worlds/stardew_valley/data/bundles_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/stardew_valley/data/bundles_data/bundle_data.py b/worlds/stardew_valley/data/bundles_data/bundle_data.py new file mode 100644 index 000000000000..11c0446073bb --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/bundle_data.py @@ -0,0 +1,10 @@ +from .remixed_bundles import * + +all_bundle_items_except_money = [] +all_remixed_bundles = [*crafts_room_bundles_remixed, *pantry_bundles_remixed, *fish_tank_bundles_remixed, + *boiler_room_bundles_remixed, *bulletin_board_bundles_remixed, missing_bundle_thematic, + *giant_stump_bundles_remixed] +for bundle in all_remixed_bundles: + all_bundle_items_except_money.extend(bundle.items) + +all_bundle_items_by_name = {item.item_name: item for item in all_bundle_items_except_money} diff --git a/worlds/stardew_valley/data/bundles_data/bundle_items_data.py b/worlds/stardew_valley/data/bundles_data/bundle_items_data.py new file mode 100644 index 000000000000..8093ab8927b4 --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/bundle_items_data.py @@ -0,0 +1,599 @@ +from ..hats_data import Hats +from ..shirt_data import Shirts +from ...bundles.bundle_item import BundleItem +from ...strings.animal_product_names import AnimalProduct +from ...strings.artisan_good_names import ArtisanGood +from ...strings.book_names import Book +from ...strings.boot_names import Boots +from ...strings.catalogue_names import CatalogueItem +from ...strings.craftable_names import Consumable, Lighting, Fishing, Craftable, Bomb, Furniture, Floor, Edible, Statue +from ...strings.crop_names import Vegetable, Fruit +from ...strings.currency_names import Currency +from ...strings.decoration_names import Decoration +from ...strings.fertilizer_names import Fertilizer, SpeedGro, RetainingSoil +from ...strings.fish_names import Fish, WaterItem, Trash +from ...strings.flower_names import Flower +from ...strings.food_names import Meal, Beverage +from ...strings.forageable_names import Forageable, Mushroom +from ...strings.fruit_tree_names import Sapling +from ...strings.geode_names import Geode +from ...strings.gift_names import Gift +from ...strings.ingredient_names import Ingredient +from ...strings.machine_names import Machine +from ...strings.material_names import Material +from ...strings.meme_item_names import MemeItem +from ...strings.metal_names import Fossil, Ore, MetalBar, Mineral, Artifact +from ...strings.monster_drop_names import Loot +from ...strings.seed_names import TreeSeed, Seed +from ...strings.special_item_names import SpecialItem, NotReallyAnItem + +wild_horseradish = BundleItem(Forageable.wild_horseradish) +daffodil = BundleItem(Forageable.daffodil) +leek = BundleItem(Forageable.leek) +dandelion = BundleItem(Forageable.dandelion) +morel = BundleItem(Mushroom.morel) +common_mushroom = BundleItem(Mushroom.common) +salmonberry = BundleItem(Forageable.salmonberry, can_have_quality=False) +spring_onion = BundleItem(Forageable.spring_onion) + +grape = BundleItem(Fruit.grape) +spice_berry = BundleItem(Forageable.spice_berry) +sweet_pea = BundleItem(Forageable.sweet_pea) +red_mushroom = BundleItem(Mushroom.red) +fiddlehead_fern = BundleItem(Forageable.fiddlehead_fern) + +wild_plum = BundleItem(Forageable.wild_plum) +hazelnut = BundleItem(Forageable.hazelnut) +blackberry = BundleItem(Forageable.blackberry) +chanterelle = BundleItem(Mushroom.chanterelle) + +winter_root = BundleItem(Forageable.winter_root) +crystal_fruit = BundleItem(Forageable.crystal_fruit) +snow_yam = BundleItem(Forageable.snow_yam) +crocus = BundleItem(Forageable.crocus) +holly = BundleItem(Forageable.holly) + +coconut = BundleItem(Forageable.coconut) +golden_coconut = BundleItem(Geode.golden_coconut, source=BundleItem.Sources.island) +cactus_fruit = BundleItem(Forageable.cactus_fruit) +cave_carrot = BundleItem(Forageable.cave_carrot) +purple_mushroom = BundleItem(Mushroom.purple) +maple_syrup = BundleItem(ArtisanGood.maple_syrup) +oak_resin = BundleItem(ArtisanGood.oak_resin) +pine_tar = BundleItem(ArtisanGood.pine_tar) +nautilus_shell = BundleItem(WaterItem.nautilus_shell) +coral = BundleItem(WaterItem.coral) +sea_urchin = BundleItem(WaterItem.sea_urchin) +rainbow_shell = BundleItem(Forageable.rainbow_shell) +clam = BundleItem(Fish.clam) +cockle = BundleItem(Fish.cockle) +mussel = BundleItem(Fish.mussel) +oyster = BundleItem(Fish.oyster) +seaweed = BundleItem(WaterItem.seaweed, can_have_quality=False) + +wood = BundleItem(Material.wood, 99) +stone = BundleItem(Material.stone, 99) +hardwood = BundleItem(Material.hardwood, 10) +clay = BundleItem(Material.clay) +fiber = BundleItem(Material.fiber) +moss = BundleItem(Material.moss) + +mixed_seeds = BundleItem(Seed.mixed) +acorn = BundleItem(TreeSeed.acorn) +maple_seed = BundleItem(TreeSeed.maple) +pine_cone = BundleItem(TreeSeed.pine) +mahogany_seed = BundleItem(TreeSeed.mahogany) +mushroom_tree_seed = BundleItem(TreeSeed.mushroom, source=BundleItem.Sources.island) +mystic_tree_seed = BundleItem(TreeSeed.mystic, source=BundleItem.Sources.masteries) +mossy_seed = BundleItem(TreeSeed.mossy) + +strawberry_seeds = BundleItem(Seed.strawberry) +sunflower_seeds = BundleItem(Seed.sunflower) + +blue_jazz = BundleItem(Flower.blue_jazz) +cauliflower = BundleItem(Vegetable.cauliflower) +green_bean = BundleItem(Vegetable.green_bean) +kale = BundleItem(Vegetable.kale) +parsnip = BundleItem(Vegetable.parsnip) +potato = BundleItem(Vegetable.potato) +strawberry = BundleItem(Fruit.strawberry, source=BundleItem.Sources.festival) +tulip = BundleItem(Flower.tulip) +unmilled_rice = BundleItem(Vegetable.unmilled_rice) +coffee_bean = BundleItem(Seed.coffee) +garlic = BundleItem(Vegetable.garlic) +blueberry = BundleItem(Fruit.blueberry) +corn = BundleItem(Vegetable.corn) +hops = BundleItem(Vegetable.hops) +hot_pepper = BundleItem(Fruit.hot_pepper) +melon = BundleItem(Fruit.melon) +poppy = BundleItem(Flower.poppy) +radish = BundleItem(Vegetable.radish) +summer_spangle = BundleItem(Flower.summer_spangle) +sunflower = BundleItem(Flower.sunflower) +tomato = BundleItem(Vegetable.tomato) +wheat = BundleItem(Vegetable.wheat) +hay = BundleItem(Forageable.hay) +amaranth = BundleItem(Vegetable.amaranth) +bok_choy = BundleItem(Vegetable.bok_choy) +cranberries = BundleItem(Fruit.cranberries) +eggplant = BundleItem(Vegetable.eggplant) +fairy_rose = BundleItem(Flower.fairy_rose) +pumpkin = BundleItem(Vegetable.pumpkin) +yam = BundleItem(Vegetable.yam) +sweet_gem_berry = BundleItem(Fruit.sweet_gem_berry) +rhubarb = BundleItem(Fruit.rhubarb) +beet = BundleItem(Vegetable.beet) +red_cabbage = BundleItem(Vegetable.red_cabbage) +starfruit = BundleItem(Fruit.starfruit) +artichoke = BundleItem(Vegetable.artichoke) +pineapple = BundleItem(Fruit.pineapple, source=BundleItem.Sources.content) +taro_root = BundleItem(Vegetable.taro_root, source=BundleItem.Sources.content) +dragon_tooth = BundleItem(Forageable.dragon_tooth, source=BundleItem.Sources.content) + +carrot = BundleItem(Vegetable.carrot) +summer_squash = BundleItem(Vegetable.summer_squash) +broccoli = BundleItem(Vegetable.broccoli) +powdermelon = BundleItem(Fruit.powdermelon) + +egg = BundleItem(AnimalProduct.egg) +large_egg = BundleItem(AnimalProduct.large_egg) +brown_egg = BundleItem(AnimalProduct.brown_egg) +large_brown_egg = BundleItem(AnimalProduct.large_brown_egg) +wool = BundleItem(AnimalProduct.wool) +milk = BundleItem(AnimalProduct.milk) +large_milk = BundleItem(AnimalProduct.large_milk) +goat_milk = BundleItem(AnimalProduct.goat_milk) +large_goat_milk = BundleItem(AnimalProduct.large_goat_milk) +truffle = BundleItem(AnimalProduct.truffle) +duck_feather = BundleItem(AnimalProduct.duck_feather) +duck_egg = BundleItem(AnimalProduct.duck_egg) +rabbit_foot = BundleItem(AnimalProduct.rabbit_foot) +dinosaur_egg = BundleItem(AnimalProduct.dinosaur_egg) +void_egg = BundleItem(AnimalProduct.void_egg) +ostrich_egg = BundleItem(AnimalProduct.ostrich_egg, source=BundleItem.Sources.content) +golden_egg = BundleItem(AnimalProduct.golden_egg) + +truffle_oil = BundleItem(ArtisanGood.truffle_oil) +cloth = BundleItem(ArtisanGood.cloth) +goat_cheese = BundleItem(ArtisanGood.goat_cheese) +cheese = BundleItem(ArtisanGood.cheese) +honey = BundleItem(ArtisanGood.honey) +beer = BundleItem(Beverage.beer) +mayonnaise = BundleItem(ArtisanGood.mayonnaise) +juice = BundleItem(ArtisanGood.juice) +mead = BundleItem(ArtisanGood.mead) +pale_ale = BundleItem(ArtisanGood.pale_ale) +wine = BundleItem(ArtisanGood.wine) +jelly = BundleItem(ArtisanGood.jelly) +pickles = BundleItem(ArtisanGood.pickles) +caviar = BundleItem(ArtisanGood.caviar) +aged_roe = BundleItem(ArtisanGood.aged_roe) +roe = BundleItem(AnimalProduct.roe) +squid_ink = BundleItem(AnimalProduct.squid_ink) +coffee = BundleItem(Beverage.coffee) +green_tea = BundleItem(ArtisanGood.green_tea) +apple = BundleItem(Fruit.apple) +apricot = BundleItem(Fruit.apricot) +orange = BundleItem(Fruit.orange) +peach = BundleItem(Fruit.peach) +pomegranate = BundleItem(Fruit.pomegranate) +cherry = BundleItem(Fruit.cherry) +banana = BundleItem(Fruit.banana, source=BundleItem.Sources.content) +mango = BundleItem(Fruit.mango, source=BundleItem.Sources.content) + +basic_fertilizer = BundleItem(Fertilizer.basic, 100) +quality_fertilizer = BundleItem(Fertilizer.quality, 20) +deluxe_fertilizer = BundleItem(Fertilizer.deluxe, 5, source=BundleItem.Sources.island) +basic_retaining_soil = BundleItem(RetainingSoil.basic, 80) +quality_retaining_soil = BundleItem(RetainingSoil.quality, 50) +deluxe_retaining_soil = BundleItem(RetainingSoil.deluxe, 20, source=BundleItem.Sources.island) +speed_gro = BundleItem(SpeedGro.basic, 40) +deluxe_speed_gro = BundleItem(SpeedGro.deluxe, 20) +hyper_speed_gro = BundleItem(SpeedGro.hyper, 5, source=BundleItem.Sources.qi_board) +tree_fertilizer = BundleItem(Fertilizer.tree, 20) + +lobster = BundleItem(Fish.lobster) +crab = BundleItem(Fish.crab) +shrimp = BundleItem(Fish.shrimp) +crayfish = BundleItem(Fish.crayfish) +snail = BundleItem(Fish.snail) +periwinkle = BundleItem(Fish.periwinkle) +trash = BundleItem(Trash.trash) +driftwood = BundleItem(Trash.driftwood) +soggy_newspaper = BundleItem(Trash.soggy_newspaper) +broken_cd = BundleItem(Trash.broken_cd) +broken_glasses = BundleItem(Trash.broken_glasses) + +chub = BundleItem(Fish.chub) +catfish = BundleItem(Fish.catfish) +rainbow_trout = BundleItem(Fish.rainbow_trout) +lingcod = BundleItem(Fish.lingcod) +walleye = BundleItem(Fish.walleye) +perch = BundleItem(Fish.perch) +pike = BundleItem(Fish.pike) +bream = BundleItem(Fish.bream) +salmon = BundleItem(Fish.salmon) +sunfish = BundleItem(Fish.sunfish) +tiger_trout = BundleItem(Fish.tiger_trout) +shad = BundleItem(Fish.shad) +smallmouth_bass = BundleItem(Fish.smallmouth_bass) +dorado = BundleItem(Fish.dorado) +carp = BundleItem(Fish.carp) +midnight_carp = BundleItem(Fish.midnight_carp) +largemouth_bass = BundleItem(Fish.largemouth_bass) +sturgeon = BundleItem(Fish.sturgeon) +bullhead = BundleItem(Fish.bullhead) +tilapia = BundleItem(Fish.tilapia) +pufferfish = BundleItem(Fish.pufferfish) +tuna = BundleItem(Fish.tuna) +super_cucumber = BundleItem(Fish.super_cucumber) +flounder = BundleItem(Fish.flounder) +anchovy = BundleItem(Fish.anchovy) +sardine = BundleItem(Fish.sardine) +red_mullet = BundleItem(Fish.red_mullet) +herring = BundleItem(Fish.herring) +eel = BundleItem(Fish.eel) +octopus = BundleItem(Fish.octopus) +red_snapper = BundleItem(Fish.red_snapper) +squid = BundleItem(Fish.squid) +sea_cucumber = BundleItem(Fish.sea_cucumber) +albacore = BundleItem(Fish.albacore) +halibut = BundleItem(Fish.halibut) +scorpion_carp = BundleItem(Fish.scorpion_carp) +sandfish = BundleItem(Fish.sandfish) +woodskip = BundleItem(Fish.woodskip) +lava_eel = BundleItem(Fish.lava_eel) +ice_pip = BundleItem(Fish.ice_pip) +stonefish = BundleItem(Fish.stonefish) +ghostfish = BundleItem(Fish.ghostfish) + +bouquet = BundleItem(Gift.bouquet) +wilted_bouquet = BundleItem(Gift.wilted_bouquet) +copper_bar = BundleItem(MetalBar.copper) +iron_bar = BundleItem(MetalBar.iron) +gold_bar = BundleItem(MetalBar.gold) +iridium_bar = BundleItem(MetalBar.iridium) +radioactive_bar = BundleItem(MetalBar.radioactive, source=BundleItem.Sources.island) +refined_quartz = BundleItem(MetalBar.quartz) +coal = BundleItem(Material.coal) +iridium_ore = BundleItem(Ore.iridium) +gold_ore = BundleItem(Ore.gold) +iron_ore = BundleItem(Ore.iron) +copper_ore = BundleItem(Ore.copper) +radioactive_ore = BundleItem(Ore.radioactive, source=BundleItem.Sources.qi_board) +battery_pack = BundleItem(ArtisanGood.battery_pack) + +quartz = BundleItem(Mineral.quartz) +fire_quartz = BundleItem(Mineral.fire_quartz) +frozen_tear = BundleItem(Mineral.frozen_tear) +earth_crystal = BundleItem(Mineral.earth_crystal) +emerald = BundleItem(Mineral.emerald) +aquamarine = BundleItem(Mineral.aquamarine) +ruby = BundleItem(Mineral.ruby) +amethyst = BundleItem(Mineral.amethyst) +topaz = BundleItem(Mineral.topaz) +jade = BundleItem(Mineral.jade) +obsidian = BundleItem(Mineral.obsidian) +jamborite = BundleItem(Mineral.jamborite) +tigerseye = BundleItem(Mineral.tigerseye) +opal = BundleItem(Mineral.opal) +thunder_egg = BundleItem(Mineral.thunder_egg) +ghost_crystal = BundleItem(Mineral.ghost_crystal) +kyanite = BundleItem(Mineral.kyanite) +lemon_stone = BundleItem(Mineral.lemon_stone) +mudstone = BundleItem(Mineral.mudstone) +limestone = BundleItem(Mineral.limestone) + +slime = BundleItem(Loot.slime, 99) +bug_meat = BundleItem(Loot.bug_meat, 10) +bat_wing = BundleItem(Loot.bat_wing, 10) +solar_essence = BundleItem(Loot.solar_essence) +void_essence = BundleItem(Loot.void_essence) + +petrified_slime = BundleItem(Mineral.petrified_slime) +blue_slime_egg = BundleItem(AnimalProduct.slime_egg_blue) +red_slime_egg = BundleItem(AnimalProduct.slime_egg_red) +purple_slime_egg = BundleItem(AnimalProduct.slime_egg_purple) +green_slime_egg = BundleItem(AnimalProduct.slime_egg_green) +tiger_slime_egg = BundleItem(AnimalProduct.slime_egg_tiger, source=BundleItem.Sources.island) + +cherry_bomb = BundleItem(Bomb.cherry_bomb, 5) +bomb = BundleItem(Bomb.bomb, 2) +mega_bomb = BundleItem(Bomb.mega_bomb) +explosive_ammo = BundleItem(Craftable.explosive_ammo, 5) + +maki_roll = BundleItem(Meal.maki_roll) +fried_egg = BundleItem(Meal.fried_egg) +omelet = BundleItem(Meal.omelet) +pizza = BundleItem(Meal.pizza) +hashbrowns = BundleItem(Meal.hashbrowns) +pancakes = BundleItem(Meal.pancakes) +bread = BundleItem(Meal.bread) +tortilla = BundleItem(Meal.tortilla) +triple_shot_espresso = BundleItem(Beverage.triple_shot_espresso) +farmer_s_lunch = BundleItem(Meal.farmer_lunch) +survival_burger = BundleItem(Meal.survival_burger) +dish_o_the_sea = BundleItem(Meal.dish_o_the_sea) +miner_s_treat = BundleItem(Meal.miners_treat) +roots_platter = BundleItem(Meal.roots_platter) +salad = BundleItem(Meal.salad) +cheese_cauliflower = BundleItem(Meal.cheese_cauliflower) +parsnip_soup = BundleItem(Meal.parsnip_soup) +fried_mushroom = BundleItem(Meal.fried_mushroom) +salmon_dinner = BundleItem(Meal.salmon_dinner) +pepper_poppers = BundleItem(Meal.pepper_poppers) +spaghetti = BundleItem(Meal.spaghetti) +sashimi = BundleItem(Meal.sashimi) +blueberry_tart = BundleItem(Meal.blueberry_tart) +algae_soup = BundleItem(Meal.algae_soup) +pale_broth = BundleItem(Meal.pale_broth) +chowder = BundleItem(Meal.chowder) +cookie = BundleItem(Meal.cookie) +ancient_doll = BundleItem(Artifact.ancient_doll) +ice_cream = BundleItem(Meal.ice_cream) +cranberry_candy = BundleItem(Meal.cranberry_candy) +ginger_ale = BundleItem(Beverage.ginger_ale, source=BundleItem.Sources.island) +pink_cake = BundleItem(Meal.pink_cake) +plum_pudding = BundleItem(Meal.plum_pudding) +chocolate_cake = BundleItem(Meal.chocolate_cake) +rhubarb_pie = BundleItem(Meal.rhubarb_pie) +shrimp_cocktail = BundleItem(Meal.shrimp_cocktail) +pina_colada = BundleItem(Beverage.pina_colada, source=BundleItem.Sources.island) +stuffing = BundleItem(Meal.stuffing) +magic_rock_candy = BundleItem(Meal.magic_rock_candy) +spicy_eel = BundleItem(Meal.spicy_eel) +crab_cakes = BundleItem(Meal.crab_cakes) +eggplant_parmesan = BundleItem(Meal.eggplant_parmesan) +pumpkin_soup = BundleItem(Meal.pumpkin_soup) +lucky_lunch = BundleItem(Meal.lucky_lunch) +joja_cola = BundleItem(Trash.joja_cola) +strange_bun = BundleItem(Meal.strange_bun) +moss_soup = BundleItem(Meal.moss_soup) +roasted_hazelnuts = BundleItem(Meal.roasted_hazelnuts) +maple_bar = BundleItem(Meal.maple_bar) + +green_algae = BundleItem(WaterItem.green_algae) +white_algae = BundleItem(WaterItem.white_algae) +geode = BundleItem(Geode.geode) +frozen_geode = BundleItem(Geode.frozen) +magma_geode = BundleItem(Geode.magma) +omni_geode = BundleItem(Geode.omni) +sap = BundleItem(Material.sap) + +dwarf_scroll_1 = BundleItem(Artifact.dwarf_scroll_i) +dwarf_scroll_2 = BundleItem(Artifact.dwarf_scroll_ii) +dwarf_scroll_3 = BundleItem(Artifact.dwarf_scroll_iii) +dwarf_scroll_4 = BundleItem(Artifact.dwarf_scroll_iv) +elvish_jewelry = BundleItem(Artifact.elvish_jewelry) +ancient_drum = BundleItem(Artifact.ancient_drum) +dried_starfish = BundleItem(Fossil.dried_starfish) +bone_fragment = BundleItem(Fossil.bone_fragment) + +golden_mask = BundleItem(Artifact.golden_mask) +golden_relic = BundleItem(Artifact.golden_relic) +dwarf_gadget = BundleItem(Artifact.dwarf_gadget) +dwarvish_helm = BundleItem(Artifact.dwarvish_helm) +prehistoric_handaxe = BundleItem(Artifact.prehistoric_handaxe) +bone_flute = BundleItem(Artifact.bone_flute) +anchor = BundleItem(Artifact.anchor) +prehistoric_tool = BundleItem(Artifact.prehistoric_tool) +chicken_statue = BundleItem(Artifact.chicken_statue) +rusty_cog = BundleItem(Artifact.rusty_cog) +rusty_spur = BundleItem(Artifact.rusty_spur) +rusty_spoon = BundleItem(Artifact.rusty_spoon) +ancient_sword = BundleItem(Artifact.ancient_sword) +ornamental_fan = BundleItem(Artifact.ornamental_fan) +chipped_amphora = BundleItem(Artifact.chipped_amphora) +strange_doll = BundleItem(Artifact.strange_doll) +strange_doll_green = BundleItem(Artifact.strange_doll_green) +ancient_seed = BundleItem(Artifact.ancient_seed) +rare_disc = BundleItem(Artifact.rare_disc) + +prehistoric_scapula = BundleItem(Fossil.prehistoric_scapula) +prehistoric_tibia = BundleItem(Fossil.prehistoric_tibia) +prehistoric_skull = BundleItem(Fossil.prehistoric_skull) +skeletal_hand = BundleItem(Fossil.skeletal_hand) +prehistoric_rib = BundleItem(Fossil.prehistoric_rib) +prehistoric_vertebra = BundleItem(Fossil.prehistoric_vertebra) +skeletal_tail = BundleItem(Fossil.skeletal_tail) +nautilus_fossil = BundleItem(Fossil.nautilus_fossil) +amphibian_fossil = BundleItem(Fossil.amphibian_fossil) +palm_fossil = BundleItem(Fossil.palm_fossil) +trilobite = BundleItem(Fossil.trilobite) +snake_vertebrae = BundleItem(Fossil.snake_vertebrae, source=BundleItem.Sources.island) +mummified_bat = BundleItem(Fossil.mummified_bat, source=BundleItem.Sources.island) +fossilized_tail = BundleItem(Fossil.fossilized_tail, source=BundleItem.Sources.island) + +dinosaur_mayo = BundleItem(ArtisanGood.dinosaur_mayonnaise) +void_mayo = BundleItem(ArtisanGood.void_mayonnaise) +prismatic_shard = BundleItem(Mineral.prismatic_shard) +diamond = BundleItem(Mineral.diamond) +ancient_fruit = BundleItem(Fruit.ancient_fruit) +void_salmon = BundleItem(Fish.void_salmon) +tea_leaves = BundleItem(Vegetable.tea_leaves) +blobfish = BundleItem(Fish.blobfish) +spook_fish = BundleItem(Fish.spook_fish) +lionfish = BundleItem(Fish.lionfish, source=BundleItem.Sources.island) +blue_discus = BundleItem(Fish.blue_discus, source=BundleItem.Sources.island) +stingray = BundleItem(Fish.stingray, source=BundleItem.Sources.island) +spookfish = BundleItem(Fish.spookfish) +midnight_squid = BundleItem(Fish.midnight_squid) +slimejack = BundleItem(Fish.slimejack) +goby = BundleItem(Fish.goby) + +angler = BundleItem(Fish.angler) +crimsonfish = BundleItem(Fish.crimsonfish) +mutant_carp = BundleItem(Fish.mutant_carp) +glacierfish = BundleItem(Fish.glacierfish) +legend = BundleItem(Fish.legend) + +spinner = BundleItem(Fishing.spinner) +dressed_spinner = BundleItem(Fishing.dressed_spinner) +trap_bobber = BundleItem(Fishing.trap_bobber) +sonar_bobber = BundleItem(Fishing.sonar_bobber) +cork_bobber = BundleItem(Fishing.cork_bobber) +lead_bobber = BundleItem(Fishing.lead_bobber) +treasure_hunter = BundleItem(Fishing.treasure_hunter) +barbed_hook = BundleItem(Fishing.barbed_hook) +curiosity_lure = BundleItem(Fishing.curiosity_lure) +quality_bobber = BundleItem(Fishing.quality_bobber) +bait = BundleItem(Fishing.bait, 100) +deluxe_bait = BundleItem(Fishing.deluxe_bait, 50) +magnet = BundleItem(Fishing.magnet) +wild_bait = BundleItem(Fishing.wild_bait, 20) +magic_bait = BundleItem(Fishing.magic_bait, 10, source=BundleItem.Sources.qi_board) +pearl = BundleItem(Gift.pearl) +challenge_bait = BundleItem(Fishing.challenge_bait, 25, source=BundleItem.Sources.masteries) +targeted_bait = BundleItem(ArtisanGood.targeted_bait, 25, source=BundleItem.Sources.content) +golden_bobber = BundleItem(Fishing.golden_bobber) + +ginger = BundleItem(Forageable.ginger, source=BundleItem.Sources.content) +magma_cap = BundleItem(Mushroom.magma_cap, source=BundleItem.Sources.content) + +wheat_flour = BundleItem(Ingredient.wheat_flour) +sugar = BundleItem(Ingredient.sugar) +vinegar = BundleItem(Ingredient.vinegar) + +jack_o_lantern = BundleItem(Lighting.jack_o_lantern) +prize_ticket = BundleItem(Currency.prize_ticket) +mystery_box = BundleItem(Consumable.mystery_box) +gold_mystery_box = BundleItem(Consumable.gold_mystery_box, source=BundleItem.Sources.masteries) +calico_egg = BundleItem(Currency.calico_egg) +golden_tag = BundleItem(Currency.golden_tag) +stardrop_tea = BundleItem(ArtisanGood.stardrop_tea) +rotten_plant = BundleItem(Decoration.rotten_plant) + +apple_slices = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.apple)) + +infinity_crown = BundleItem(Hats.infinity_crown.name, source=BundleItem.Sources.content) +bowler_hat = BundleItem(Hats.bowler.name, source=BundleItem.Sources.content) +sombrero = BundleItem(Hats.sombrero.name, source=BundleItem.Sources.content) +good_ol_cap = BundleItem(Hats.good_ol_cap.name, source=BundleItem.Sources.content) +living_hat = BundleItem(Hats.living_hat.name, source=BundleItem.Sources.content) +garbage_hat = BundleItem(Hats.garbage_hat.name, source=BundleItem.Sources.content) +golden_helmet = BundleItem(Hats.golden_helmet.name, source=BundleItem.Sources.content) +laurel_wreath_crown = BundleItem(Hats.laurel_wreath_crown.name, source=BundleItem.Sources.content) +joja_cap = BundleItem(Hats.joja_cap.name, source=BundleItem.Sources.content) +deluxe_pirate_hat = BundleItem(Hats.deluxe_pirate_hat.name, source=BundleItem.Sources.content) +dark_cowboy_hat = BundleItem(Hats.dark_cowboy_hat.name, source=BundleItem.Sources.content) +tiger_hat = BundleItem(Hats.tiger_hat.name, source=BundleItem.Sources.content) +mystery_hat = BundleItem(Hats.mystery_hat.name, source=BundleItem.Sources.content) +dark_ballcap = BundleItem(Hats.dark_ballcap.name, source=BundleItem.Sources.content) +goblin_mask = BundleItem(Hats.goblin_mask.name, source=BundleItem.Sources.island) + +vacation_shirt = BundleItem(Shirts.vacation.name) +green_jacket_shirt = BundleItem(Shirts.green_jacket.name) + +mermaid_boots = BundleItem(Boots.mermaid_boots) + +lucky_purple_shorts = BundleItem(SpecialItem.lucky_purple_shorts) +trimmed_purple_shorts = BundleItem(SpecialItem.trimmed_purple_shorts) + +ancient_fruit_wine = BundleItem(ArtisanGood.specific_wine(Fruit.ancient_fruit)) +dried_ancient_fruit = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.ancient_fruit)) +ancient_fruit_jelly = BundleItem(ArtisanGood.specific_jelly(Fruit.ancient_fruit)) +starfruit_wine = BundleItem(ArtisanGood.specific_wine(Fruit.starfruit)) +dried_starfruit = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.starfruit)) +starfruit_jelly = BundleItem(ArtisanGood.specific_jelly(Fruit.starfruit)) +rhubarb_wine = BundleItem(ArtisanGood.specific_wine(Fruit.rhubarb)) +dried_rhubarb = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.rhubarb)) +melon_wine = BundleItem(ArtisanGood.specific_wine(Fruit.melon)) +dried_melon = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.melon)) +pineapple_wine = BundleItem(ArtisanGood.specific_wine(Fruit.pineapple), source=BundleItem.Sources.content) +dried_pineapple = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.pineapple), source=BundleItem.Sources.content) +dried_banana = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.banana), source=BundleItem.Sources.content) +strawberry_wine = BundleItem(ArtisanGood.specific_wine(Fruit.strawberry)) +dried_strawberry = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.strawberry)) +pumpkin_juice = BundleItem(ArtisanGood.specific_juice(Vegetable.pumpkin)) +raisins = BundleItem(ArtisanGood.raisins) +dried_qi_fruit = BundleItem(ArtisanGood.specific_dried_fruit(Fruit.qi_fruit), source=BundleItem.Sources.content) + +aged_lava_eel_roe = BundleItem(ArtisanGood.specific_aged_roe(Fish.lava_eel)) +aged_crimsonfish_roe = BundleItem(ArtisanGood.specific_aged_roe(Fish.crimsonfish)) +aged_angler_roe = BundleItem(ArtisanGood.specific_aged_roe(Fish.angler)) +legend_roe = BundleItem(AnimalProduct.specific_roe(Fish.legend)) +aged_legend_roe = BundleItem(ArtisanGood.specific_aged_roe(Fish.legend)) +aged_glacierfish_roe = BundleItem(ArtisanGood.specific_aged_roe(Fish.glacierfish)) +aged_mutant_carp_roe = BundleItem(ArtisanGood.specific_aged_roe(Fish.mutant_carp)) +midnight_squid_roe = BundleItem(AnimalProduct.specific_roe(Fish.midnight_squid)) + +legend_bait = BundleItem(ArtisanGood.specific_bait(Fish.legend)) + +smoked_legend = BundleItem(ArtisanGood.specific_smoked_fish(Fish.legend)) + +mystic_syrup = BundleItem(ArtisanGood.mystic_syrup) +apple_sapling = BundleItem(Sapling.apple) +apricot_sapling = BundleItem(Sapling.apricot) +banana_sapling = BundleItem(Sapling.banana, source=BundleItem.Sources.content) +cherry_sapling = BundleItem(Sapling.cherry) +mango_sapling = BundleItem(Sapling.mango, source=BundleItem.Sources.content) +orange_sapling = BundleItem(Sapling.orange) +peach_sapling = BundleItem(Sapling.peach) +pomegranate_sapling = BundleItem(Sapling.pomegranate) + +cookout_kit = BundleItem(Craftable.cookout_kit) +tent_kit = BundleItem(Craftable.tent_kit) +bug_steak = BundleItem(Edible.bug_steak) + +tea_set = BundleItem(Gift.tea_set) +golden_pumpkin = BundleItem(Gift.golden_pumpkin) +mermaid_pendant = BundleItem(Gift.mermaid_pendant) +void_ghost_pendant = BundleItem(Gift.void_ghost_pendant) +advanced_tv_remote = BundleItem(SpecialItem.advanced_tv_remote) + +crystal_ball = BundleItem(CatalogueItem.crystal_ball) +amethyst_crystal_ball = BundleItem(CatalogueItem.amethyst_crystal_ball) +aquamarine_crystal_ball = BundleItem(CatalogueItem.aquamarine_crystal_ball) +emerald_crystal_ball = BundleItem(CatalogueItem.emerald_crystal_ball) +ruby_crystal_ball = BundleItem(CatalogueItem.ruby_crystal_ball) +topaz_crystal_ball = BundleItem(CatalogueItem.topaz_crystal_ball) +flute_block = BundleItem(Furniture.flute_block) +candle_lamp = BundleItem(Furniture.candle_lamp) +modern_lamp = BundleItem(Furniture.modern_lamp) +single_bed = BundleItem(Furniture.single_bed) +exotic_double_bed = BundleItem(Furniture.exotic_double_bed, source=BundleItem.Sources.qi_board) +statue_of_endless_fortune = BundleItem(Machine.statue_endless_fortune) +cursed_mannequin = BundleItem(Furniture.cursed_mannequin) +statue_of_blessings = BundleItem(Statue.blessings) +crane_house_plant = BundleItem(Furniture.crane_game_house_plant) + +wood_floor = BundleItem(Floor.wood) +rustic_plank_floor = BundleItem(Floor.rustic) +straw_floor = BundleItem(Floor.straw) +weathered_floor = BundleItem(Floor.weathered) +crystal_floor = BundleItem(Floor.crystal) +stone_floor = BundleItem(Floor.stone) +stone_walkway_floor = BundleItem(Floor.stone_walkway) +brick_floor = BundleItem(Floor.brick) +wood_path = BundleItem(Floor.wood_path) +gravel_path = BundleItem(Floor.gravel_path) +cobblestone_path = BundleItem(Floor.cobblestone_path) +stepping_stone_path = BundleItem(Floor.stepping_stone_path) +crystal_path = BundleItem(Floor.crystal_path) + +warp_totem_beach = BundleItem(Consumable.warp_totem_beach) +warp_totem_mountains = BundleItem(Consumable.warp_totem_mountains) +warp_totem_farm = BundleItem(Consumable.warp_totem_farm) +warp_totem_desert = BundleItem(Consumable.warp_totem_desert, source=BundleItem.Sources.content) +warp_totem_island = BundleItem(Consumable.warp_totem_island, source=BundleItem.Sources.island) +rain_totem = BundleItem(Consumable.rain_totem) +treasure_totem = BundleItem(Consumable.treasure_totem, source=BundleItem.Sources.masteries) + +book_of_mysteries = BundleItem(Book.book_of_mysteries) + +far_away_stone = BundleItem(SpecialItem.far_away_stone) + +death = BundleItem(NotReallyAnItem.death) + +camping_stove = BundleItem(MemeItem.camping_stove) +decorative_pot = BundleItem(MemeItem.decorative_pot) +slime_crate = BundleItem(MemeItem.slime_crate) +supply_crate = BundleItem(MemeItem.supply_crate) +warp_totem_qis_arena = BundleItem(MemeItem.warp_totem_qis_arena) +artifact_spot = BundleItem(MemeItem.artifact_spot) +twig = BundleItem(MemeItem.twig) +weeds = BundleItem(MemeItem.weeds) +lumber = BundleItem(MemeItem.lumber) +green_rain_weeds_0 = BundleItem(MemeItem.green_rain_weeds_0) +seed_spot = BundleItem(MemeItem.seed_spot) +pot_of_gold = BundleItem(MemeItem.pot_of_gold) diff --git a/worlds/stardew_valley/data/bundles_data/bundle_set.py b/worlds/stardew_valley/data/bundles_data/bundle_set.py new file mode 100644 index 000000000000..ac2f3c5ecf54 --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/bundle_set.py @@ -0,0 +1,28 @@ +from typing import Dict, List + +from .remixed_anywhere_bundles import community_center_remixed_anywhere +from .remixed_bundles import pantry_remixed, crafts_room_remixed, fish_tank_remixed, boiler_room_remixed, bulletin_board_remixed, vault_remixed, \ + abandoned_joja_mart_remixed, giant_stump_remixed +from .thematic_bundles import pantry_thematic, crafts_room_thematic, fish_tank_thematic, boiler_room_thematic, bulletin_board_thematic, vault_thematic, \ + abandoned_joja_mart_thematic, giant_stump_thematic +from .vanilla_bundles import crafts_room_vanilla, pantry_vanilla, fish_tank_vanilla, boiler_room_vanilla, \ + bulletin_board_vanilla, vault_vanilla, abandoned_joja_mart_vanilla, giant_stump_vanilla +from ...bundles.bundle_room import BundleRoomTemplate + + +class BundleSet: + bundles_by_room: Dict[str, BundleRoomTemplate] + + def __init__(self, bundle_rooms: List[BundleRoomTemplate]): + self.bundles_by_room = {bundle_room.name: bundle_room for bundle_room in bundle_rooms} + + +vanilla_bundles = BundleSet([pantry_vanilla, crafts_room_vanilla, fish_tank_vanilla, boiler_room_vanilla, bulletin_board_vanilla, + vault_vanilla, abandoned_joja_mart_vanilla, giant_stump_vanilla]) +thematic_bundles = BundleSet([pantry_thematic, crafts_room_thematic, fish_tank_thematic, boiler_room_thematic, bulletin_board_thematic, + vault_thematic, abandoned_joja_mart_thematic, giant_stump_thematic]) +remixed_bundles = BundleSet([pantry_remixed, crafts_room_remixed, fish_tank_remixed, boiler_room_remixed, bulletin_board_remixed, + vault_remixed, abandoned_joja_mart_remixed, giant_stump_remixed]) +remixed_anywhere_bundles = BundleSet([community_center_remixed_anywhere, abandoned_joja_mart_remixed, giant_stump_remixed]) + +# shuffled_bundles = BundleSet() diff --git a/worlds/stardew_valley/data/bundles_data/meme_bundles.py b/worlds/stardew_valley/data/bundles_data/meme_bundles.py new file mode 100644 index 000000000000..c3abdca129e8 --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/meme_bundles.py @@ -0,0 +1,378 @@ +from .bundle_data import all_bundle_items_by_name +from .meme_bundles_data.capitalist_bundle import capitalist_items +from .remixed_bundles import * +from ...bundles.bundle import BureaucracyBundleTemplate, RecursiveBundleTemplate, FixedPriceCurrencyBundleTemplate, \ + FixedPriceBundleTemplate, FixedPriceDeepBundleTemplate, FixedMultiplierBundleTemplate, FixedSlotsBundleTemplate +from ...strings.bundle_names import MemeBundleName +from ...strings.currency_names import MemeCurrency +from ...strings.flower_names import all_flowers +from ...strings.machine_names import Machine +from ...strings.meme_item_names import MemeItem +from ...strings.quality_names import AnimalProductQuality, CropQuality + +burger_king_items = [survival_burger, joja_cola, apple_slices, ice_cream, strange_doll, strange_doll_green, hashbrowns, infinity_crown] +burger_king_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.burger_king, burger_king_items, 6, 3) + +capitalist_bundle = FixedMultiplierBundleTemplate(CCRoom.vault, MemeBundleName.capitalist, capitalist_items, 12, 2) + +romance_items = [lucky_purple_shorts, truffle_oil, super_cucumber, good_ol_cap] +romance_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.romance, romance_items, 4, 4) + +hurricane_tortilla_items = [tortilla.as_amount(4)] +hurricane_tortilla_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.hurricane_tortilla, hurricane_tortilla_items, 6, 6) + +AAAA_items = [battery_pack.as_amount(12), battery_pack.as_amount(8), battery_pack.as_amount(6)] +AAAA_bundle = BundleTemplate(CCRoom.crafts_room, MemeBundleName.AAAA, AAAA_items, 3, 3) + +anything_for_beyonce_items = [beet] +anything_for_beyonce_bundle = BundleTemplate(CCRoom.crafts_room, MemeBundleName.anything_for_beyonce, anything_for_beyonce_items, 1, 1) + +crab_rave_items = [crab.as_amount(8)] +crab_rave_bundle = BundleTemplate(CCRoom.fish_tank, MemeBundleName.crab_rave, crab_rave_items, 12, 8) + +potato_items = [potato.as_amount(8)] +potato_bundle = BundleTemplate(CCRoom.crafts_room, MemeBundleName.potato, potato_items, 12, 8) + +look_at_chickens_items = [duck_egg.as_amount(2)] +look_at_chickens_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.look_at_chickens, look_at_chickens_items, 10, 4) + +lemonade_stand_items = [grape] +lemonade_stand_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.lemonade_stand, lemonade_stand_items, 3, 1) + +what_the_rock_is_cooking_items = [stone.as_amount(1), cookout_kit, strange_bun] +what_the_rock_is_cooking_bundle = FixedPriceBundleTemplate(CCRoom.pantry, MemeBundleName.what_the_rock_is_cooking, what_the_rock_is_cooking_items, 3, 3) + +amons_fall_items = [stone.as_amount(1)] +amons_fall_bundle = FixedPriceBundleTemplate(CCRoom.boiler_room, MemeBundleName.amons_fall, amons_fall_items, 7, 7) + +screw_you_items = [tea_set, ostrich_egg.as_quality(AnimalProductQuality.iridium), snake_vertebrae.as_amount(5), mummified_bat.as_amount(5)] +screw_you_bundle = BundleTemplate(CCRoom.boiler_room, MemeBundleName.screw_you, screw_you_items, 4, 4) + +sunmaid_items = [raisins.as_amount(28)] +sunmaid_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.sunmaid, sunmaid_items, 1, 1) + +rick_items = [pickles] +rick_bundle = FixedPriceBundleTemplate(CCRoom.boiler_room, MemeBundleName.rick, rick_items, 1, 1) + +minecraft_items = [coal, copper_ore, iron_ore, quartz, amethyst, emerald, gold_ore, diamond, obsidian] +minecraft_bundle = BundleTemplate(CCRoom.boiler_room, MemeBundleName.minecraft, minecraft_items, 9, 8) + +balls_items = [blue_jazz, cauliflower, blueberry, melon, red_cabbage, tomato, powdermelon, cranberries, fairy_rose, grape, pumpkin, ancient_fruit, + solar_essence, cherry_bomb, bomb, mega_bomb, coal, iridium_ore, aquamarine, jamborite, geode, magma_geode, ancient_seed, crystal_ball, + amethyst_crystal_ball, aquamarine_crystal_ball, emerald_crystal_ball, ruby_crystal_ball, topaz_crystal_ball, apple, pizza, explosive_ammo, peach, + orange, apricot, tigerseye, coconut, gold_ore, golden_coconut, pufferfish, lucky_lunch, salad, cactus_fruit, radioactive_ore, opal, broken_cd, + void_essence, wild_plum, pomegranate] +balls_items = [item.as_amount(1) for item in balls_items] +balls_bundle = BundleTemplate(CCRoom.boiler_room, MemeBundleName.balls, balls_items, 12, 6) + +tilesanity_items = [wood_floor.as_amount(100), rustic_plank_floor.as_amount(100), straw_floor.as_amount(100), weathered_floor.as_amount(100), + crystal_floor.as_amount(100), stone_floor.as_amount(100), stone_walkway_floor.as_amount(100), brick_floor.as_amount(100), + wood_path.as_amount(100), gravel_path.as_amount(100), cobblestone_path.as_amount(100), stepping_stone_path.as_amount(100), + crystal_path.as_amount(100)] +tilesanity_bundle = BundleTemplate(CCRoom.boiler_room, MemeBundleName.tilesanity, tilesanity_items, 4, 4) + +cap_items = [vacation_shirt, wood.as_amount(999), sap.as_amount(999), pine_cone.as_amount(100), acorn.as_amount(100), + maple_seed.as_amount(100), moss.as_amount(500), exotic_double_bed.as_amount(1)] +cap_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.cap, cap_items, 8, 4) + +big_grapes_items = [coconut] +big_grapes_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.big_grapes, big_grapes_items, 4, 4) + +obelisks_items = [earth_crystal.as_amount(10), clam.as_amount(10), coral.as_amount(10), coconut.as_amount(10), cactus_fruit.as_amount(10), + banana.as_amount(10), dragon_tooth.as_amount(10), iridium_bar.as_amount(45)] +obelisks_bundle = FixedPriceBundleTemplate(CCRoom.boiler_room, MemeBundleName.obelisks, obelisks_items, 8, 8) + +burger_king_revenge_items = [fossilized_tail, void_salmon, ostrich_egg.as_amount(3), tea_leaves.as_amount(10), purple_slime_egg, + moss_soup.as_amount(3), radioactive_ore.as_amount(5), mystic_syrup.as_amount(10), truffle, aged_crimsonfish_roe] +burger_king_revenge_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.burger_king_revenge, burger_king_revenge_items, 8, 8) + +trout_items = [golden_tag.as_amount(10), golden_tag.as_amount(20), golden_tag.as_amount(30)] +trout_bundle = BundleTemplate(CCRoom.fish_tank, MemeBundleName.trout, trout_items, 1, 1) + +eg_items = [egg, brown_egg, large_egg, large_brown_egg, duck_egg, void_egg, golden_egg, dinosaur_egg, fried_egg, ostrich_egg, + thunder_egg, calico_egg, green_slime_egg, blue_slime_egg, purple_slime_egg, tiger_slime_egg, roe, aged_roe] +eg_items = [item.as_amount(57) for item in eg_items] +eg_bundle = FixedPriceBundleTemplate(CCRoom.pantry, MemeBundleName.eg, eg_items, 8, 2) + +doctor_angler_items = [fish.as_quality(FishQuality.iridium) for fish in legendary_fish_items] +doctor_angler_bundle = BundleTemplate(CCRoom.fish_tank, MemeBundleName.doctor_angler, doctor_angler_items, 5, 5) + +smapi_items = [camping_stove, decorative_pot, slime_crate, supply_crate, warp_totem_qis_arena, + artifact_spot, twig, weeds, lumber, green_rain_weeds_0, seed_spot, pot_of_gold] +smapi_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.smapi, smapi_items, 4, 4) + +chaos_emerald_items = [diamond, emerald, ruby, limestone, obsidian, kyanite, lemon_stone] +chaos_emerald_bundle = FixedPriceBundleTemplate(CCRoom.crafts_room, MemeBundleName.chaos_emerald, chaos_emerald_items, 7, 7) + +not_the_bees_items = [BundleItem(ArtisanGood.specific_honey(flower)) for flower in all_flowers] +not_the_bees_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.not_the_bees, not_the_bees_items, 4, 4) + +sappy_items = [golden_pumpkin, magic_rock_candy, pearl, prismatic_shard, rabbit_foot, stardrop_tea] +sappy_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.sappy, sappy_items, 4, 4) + +honorable_items = [stone.as_amount(1), prismatic_shard.as_amount(1)] +honorable_bundle = FixedPriceBundleTemplate(CCRoom.boiler_room, MemeBundleName.honorable, honorable_items, 2, 1) + +caffeinated_items = [coffee_bean.as_amount(500)] +caffeinated_bundle = BundleTemplate(CCRoom.crafts_room, MemeBundleName.caffeinated, caffeinated_items, 1, 1) + +hats_off_to_you_items = [living_hat, garbage_hat, golden_helmet, laurel_wreath_crown, joja_cap, + deluxe_pirate_hat, dark_cowboy_hat, tiger_hat, mystery_hat, dark_ballcap] +hats_off_to_you_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.hats_off_to_you, hats_off_to_you_items, 8, 2) + +speedrunners_items = [parsnip, wine, cheese, sea_urchin, lucky_purple_shorts, mayonnaise] +speedrunners_bundle = FixedPriceBundleTemplate(CCRoom.pantry, MemeBundleName.speedrunners, speedrunners_items, 6, 6) + +snitch_items = [lucky_purple_shorts] +snitch_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.snitch, snitch_items, 1, 1) + +mermaid_items = [pearl, clam.as_amount(2), mermaid_pendant, mermaid_boots, flute_block.as_amount(5)] +mermaid_bundle = FixedPriceBundleTemplate(CCRoom.fish_tank, MemeBundleName.mermaid, mermaid_items, 5, 5) + +commitment_items = [bouquet, mermaid_pendant, wilted_bouquet, ancient_doll.as_amount(2)] +commitment_bundle_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.commitment, commitment_items, 4, 4) + +all_simple_items = [all_bundle_items_by_name[bundle_item_name] for bundle_item_name in all_bundle_items_by_name if + all_bundle_items_by_name[bundle_item_name].amount == 1 and + all_bundle_items_by_name[bundle_item_name].quality.startswith("Basic") and + all_bundle_items_by_name[bundle_item_name].flavor is None and + bundle_item_name != "Honey"] + +permit_a38_items = [*all_simple_items] +permit_a38_bundle = BureaucracyBundleTemplate(CCRoom.vault, MemeBundleName.permit_a38, permit_a38_items, 1, 8) + +journalist_items = [*all_simple_items] +journalist_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.journalist, journalist_items, 1, 1) + +trap_items = [BundleItem(MemeItem.trap)] +trap_bundle = FixedSlotsBundleTemplate(CCRoom.bulletin_board, MemeBundleName.trap, trap_items, 4, 4) + +off_your_back_items = [BundleItem(MemeItem.worn_hat), BundleItem(MemeItem.worn_shirt), BundleItem(MemeItem.worn_pants), + BundleItem(MemeItem.worn_boots), BundleItem(MemeItem.worn_left_ring), BundleItem(MemeItem.worn_right_ring)] +off_your_back_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.off_your_back, off_your_back_items, 6, 6) + +sisyphus_items = [stone.as_amount(1)] +sisyphus_bundle = FixedPriceBundleTemplate(CCRoom.boiler_room, MemeBundleName.sisyphus, sisyphus_items, 12, 12) + +vocaloid_items = [spring_onion, orange, banana, tuna, wine, ice_cream, carrot, bread, eggplant] +vocaloid_items = [item.as_amount(10) for item in vocaloid_items] +vocaloid_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.vocaloid, vocaloid_items, 6, 6) + +legendairy_items = [legend, legend_roe, legend_bait, smoked_legend, aged_legend_roe, + milk.as_amount(10), cheese.as_amount(10), goat_milk.as_amount(10), goat_cheese.as_amount(10)] +legendairy_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.legendairy, legendairy_items, 6, 4) + +kent_c_items = [broken_glasses.as_amount(5), refined_quartz.as_amount(10)] +kent_c_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.kent_c, kent_c_items, 2, 2) + +fruit_items = [tomato, pumpkin, summer_squash, eggplant, hot_pepper] +fruit_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.fruit, fruit_items, 5, 5) + +reverse_items = [*all_simple_items] +reverse_bundle = FixedSlotsBundleTemplate(CCRoom.crafts_room, MemeBundleName.reverse, reverse_items, 4, 4) + +bundle_items = [*all_simple_items] +bundle_bundle = RecursiveBundleTemplate(CCRoom.fish_tank, MemeBundleName.bundle, bundle_items, 16, 16, 4) + +bun_dle_items = [strange_bun, bread, tortilla, rabbit_foot] +bun_dle_bundle = BundleTemplate(CCRoom.pantry, MemeBundleName.bun_dle, bun_dle_items, 4, 4) + +celeste_items = [strawberry.as_amount(175), strawberry_seeds.as_amount(4), strawberry.as_quality(CropQuality.gold).as_amount(26)] +celeste_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.celeste, celeste_items, 3, 3) + +automation_items = [copper_bar.as_amount(15), iron_bar.as_amount(36), iron_bar.as_amount(20), copper_bar.as_amount(10)] +automation_bundle = FixedPriceBundleTemplate(CCRoom.boiler_room, MemeBundleName.automation, automation_items, 4, 4) + +animal_well_items = [rare_disc, bone_flute, ruby_crystal_ball, cherry_bomb.as_amount(1), candle_lamp, modern_lamp, advanced_tv_remote] +animal_well_bundle = FixedPriceBundleTemplate(CCRoom.pantry, MemeBundleName.animal_well, animal_well_items, 7, 7) + +schrodinger_items = [*all_simple_items] +schrodinger_bundle = FixedPriceBundleTemplate(CCRoom.fish_tank, MemeBundleName.schrodinger, schrodinger_items, 2, 1) + +ikea_craftables = [Machine.mayonnaise_machine, Machine.bee_house, Machine.preserves_jar, Machine.cheese_press, Machine.keg, Machine.fish_smoker, + Machine.crystalarium, Machine.worm_bin, Furniture.tub_o_flowers] +ikea_items = [BundleItem(craftable) for craftable in ikea_craftables] +ikea_bundle = FixedPriceBundleTemplate(CCRoom.crafts_room, MemeBundleName.ikea, ikea_items, 1, 1) + +this_is_fine_items = [coffee, fire_quartz, fire_quartz, fire_quartz, fire_quartz, fire_quartz, fire_quartz, fire_quartz] +this_is_fine_bundle = FixedPriceBundleTemplate(CCRoom.crafts_room, MemeBundleName.this_is_fine, this_is_fine_items, 8, 8) + +crap_pot_items = [clay, mudstone, truffle, sunflower_seeds, roasted_hazelnuts, plum_pudding, rotten_plant, taro_root] +crap_pot_bundle = BundleTemplate(CCRoom.boiler_room, MemeBundleName.crap_pot, crap_pot_items, 4, 4) + +emmalution_items = [garlic, bread, trash, goblin_mask, rain_totem] +emmalution_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.emmalution, emmalution_items, 5, 5) + +unused_balls = [fairy_rose, melon, grape, geode, ancient_seed, crystal_ball, peach] +yellow_pool_balls = [item.as_amount(1) for item in [solar_essence, topaz_crystal_ball, pizza, apricot, gold_ore, golden_coconut, pufferfish, lucky_lunch]] +blue_pool_balls = [item.as_amount(2) for item in [blue_jazz, blueberry, powdermelon, ancient_fruit, iridium_ore, aquamarine, opal, broken_cd]] +red_pool_balls = [item.as_amount(3) for item in [tomato, mega_bomb, magma_geode, apple, explosive_ammo, cranberries, cherry_bomb]] +purple_pool_balls = [item.as_amount(4) for item in [red_cabbage, pomegranate, void_essence, wild_plum]] +orange_pool_balls = [item.as_amount(5) for item in [pumpkin, orange, tigerseye]] +green_pool_balls = [item.as_amount(6) for item in [cauliflower, jamborite, salad, cactus_fruit, radioactive_ore]] +brown_pool_balls = [item.as_amount(7) for item in [acorn, coconut, hazelnut, maple_bar, maple_syrup, potato, truffle, yam]] +black_pool_balls = [item.as_amount(8) for item in [bomb, coal, void_egg]] +pool_items = [yellow_pool_balls, blue_pool_balls, red_pool_balls, purple_pool_balls, orange_pool_balls, green_pool_balls, brown_pool_balls, black_pool_balls] +pool_bundle = FixedPriceDeepBundleTemplate(CCRoom.boiler_room, MemeBundleName.pool, pool_items, 8, 8) + +argonmatrix_items = [radish.as_amount(32), radish.as_amount(87), melon.as_amount(127), chocolate_cake.as_amount(3), cactus_fruit.as_amount(1)] +argonmatrix_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.argonmatrix, argonmatrix_items, 5, 5) + +frazzleduck_items = [duck_egg, duck_feather, eggplant, green_jacket_shirt] +frazzleduck_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.frazzleduck, frazzleduck_items, 4, 4) + +loser_club_items = [tuna] +loser_club_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.loser_club, loser_club_items, 1, 1) + +ministry_items = [item.as_amount(999) for item in [trash, joja_cola, broken_glasses, broken_cd, soggy_newspaper]] +ministry_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.ministry_of_madness, ministry_items, 4, 1) + +pomnut_items = [pomegranate, hazelnut, carrot] +pomnut_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.pomnut, pomnut_items, 3, 3) + +blossom_garden_items = [banana.as_amount(18), pizza.as_amount(32), spaghetti, single_bed, pink_cake, wood_floor, triple_shot_espresso, maple_bar, bug_steak, void_essence.as_amount(10), crystal_ball, solar_essence.as_amount(10)] +blossom_garden_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.blossom_garden, blossom_garden_items, 12, 6) + +cooperation_items = [*all_simple_items] +cooperation_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.cooperation, cooperation_items, 4, 4) + +doctor_items = [apple.as_amount(365)] +doctor_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.doctor, doctor_items, 1, 1) + +very_sticky_items = [sap.as_amount(125), sap.as_amount(125), sap.as_amount(125), sap.as_amount(125)] +very_sticky_bundle = FixedPriceBundleTemplate(CCRoom.crafts_room, MemeBundleName.very_sticky, very_sticky_items, 4, 4) + +square_hole_items = [*all_simple_items] +square_hole_bundle = FixedPriceBundleTemplate(CCRoom.bulletin_board, MemeBundleName.square_hole, square_hole_items, 6, 6) + +distracted_items = [*all_simple_items] # (If you bring more than one item for it, the rest get sent home) +distracted_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.distracted, distracted_items, 4, 4) + +algorerhythm_items =[item.as_amount(2) for item in + [midnight_squid_roe, tea_set, statue_of_endless_fortune, golden_bobber, dried_qi_fruit, cursed_mannequin, + statue_of_blessings, crane_house_plant, book_of_mysteries, far_away_stone, void_ghost_pendant, trimmed_purple_shorts]] +algorerhythm_bundle = BundleTemplate(CCRoom.bulletin_board, MemeBundleName.algorerhythm, algorerhythm_items, 12, 4) + + +red_fish_items = [red_mullet, red_snapper, lava_eel, crimsonfish] +blue_fish_items = [anchovy, tuna, sardine, bream, squid, ice_pip, albacore, blue_discus, midnight_squid, spook_fish, glacierfish] +other_fish = [pufferfish, largemouth_bass, smallmouth_bass, rainbow_trout, walleye, perch, carp, catfish, pike, sunfish, herring, eel, octopus, sea_cucumber, + super_cucumber, ghostfish, stonefish, sandfish, scorpion_carp, flounder, midnight_carp, tigerseye, bullhead, tilapia, chub, dorado, shad, + lingcod, halibut, slimejack, stingray, goby, blobfish, angler, legend, mutant_carp] +dr_seuss_items = [other_fish, [fish.as_amount(2) for fish in other_fish], red_fish_items, blue_fish_items] +dr_seuss_bundle = FixedPriceDeepBundleTemplate(CCRoom.crafts_room, MemeBundleName.dr_seuss, dr_seuss_items, 4, 4) + +pollution_items = [trash, broken_cd, broken_glasses, joja_cola, soggy_newspaper, battery_pack] +pollution_bundle = BundleTemplate(CCRoom.fish_tank, MemeBundleName.pollution, pollution_items, 4, 4) + +all_fish_item_names = sorted(list(set([item.item_name for item in [*spring_fish_items, *summer_fish_items, *fall_fish_items, *winter_fish_items]]))) +all_fish_items = [BundleItem(item).as_amount(1).as_quality(FishQuality.basic) for item in all_fish_item_names] +catch_and_release_items = [*all_fish_items] +catch_and_release_bundle = BundleTemplate(CCRoom.fish_tank, MemeBundleName.catch_and_release, catch_and_release_items, 4, 4) + +vampire_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.vampire, BundleItem(MemeCurrency.blood, 200)) +exhaustion_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.exhaustion, BundleItem(MemeCurrency.energy, 400)) +tick_tock_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.tick_tock, BundleItem(MemeCurrency.time, 1440)) +archipela_go_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.archipela_go, BundleItem(MemeCurrency.steps, 20000)) +clique_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.clique, BundleItem(MemeCurrency.clic, 1)) +cookie_clicker_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.cookie_clicker, BundleItem(MemeCurrency.cookies, 200000)) +communism_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.communism, BundleItem.money_bundle(1)) +death_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.death, death) +flashbang_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.flashbang, BundleItem.money_bundle(0)) +connection_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.connection, BundleItem.money_bundle(0)) +reconnection_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.reconnection, BundleItem.money_bundle(0)) +nft_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.nft, BundleItem.money_bundle(0)) +firstborn_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.firstborn, BundleItem(MemeCurrency.child, 1)) +restraint_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.restraint, BundleItem.money_bundle(0)) +fast_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.fast, BundleItem(MemeCurrency.time_elapsed, 1000)) +floor_is_lava_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.floor_is_lava, BundleItem.money_bundle(0)) +joetg_bundle = CurrencyBundleTemplate(CCRoom.bulletin_board, MemeBundleName.joetg, BundleItem(MemeCurrency.dead_pumpkins, 750)) +bad_farmer_bundle = CurrencyBundleTemplate(CCRoom.pantry, MemeBundleName.bad_farmer, BundleItem(MemeCurrency.dead_crops, 400)) +bad_fisherman_bundle = CurrencyBundleTemplate(CCRoom.fish_tank, MemeBundleName.bad_fisherman, BundleItem(MemeCurrency.missed_fish, 20)) +honeywell_bundle = CurrencyBundleTemplate(CCRoom.bulletin_board, MemeBundleName.honeywell, BundleItem(MemeCurrency.honeywell, 1)) +gacha_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.gacha, BundleItem.money_bundle(10000)) +hibernation_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.hibernation, BundleItem(MemeCurrency.sleep_days, 60)) +crowdfunding_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.crowdfunding, BundleItem(MemeCurrency.bank_money, 10000)) +clickbait_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.clickbait, BundleItem.money_bundle(100)) +puzzle_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.puzzle, BundleItem.money_bundle(10)) +asmr_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.asmr, BundleItem.money_bundle(0)) +humble_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.humble, BundleItem.money_bundle(5000)) +deathlink_bundle = CurrencyBundleTemplate(CCRoom.boiler_room, MemeBundleName.deathlink, BundleItem(MemeCurrency.deathlinks, 10)) +investment_bundle = CurrencyBundleTemplate(CCRoom.vault, MemeBundleName.scam, BundleItem.money_bundle(10000)) +stanley_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.stanley, BundleItem.money_bundle(9999999)) +hairy_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.vault, MemeBundleName.hairy, BundleItem.money_bundle(0)) +# colored_crystals_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.boiler_room, MemeBundleName.colored_crystals, BundleItem.money_bundle(10)) +hint_bundle = FixedPriceCurrencyBundleTemplate(CCRoom.bulletin_board, MemeBundleName.hint, BundleItem.money_bundle(10)) +sacrifice_bundle = CurrencyBundleTemplate(CCRoom.boiler_room, MemeBundleName.sacrifice, BundleItem(MemeCurrency.goat, 1)) + +# Stopped at 49 responses on the form + +# Todo Bundles +# Acrostic Bundle (Asks for a specific word, you need to donate an item for each letter) +# Bubbles Bundle +# Cipher Bundle (Some sort of code?) +# DLC Bundle +# Doom Bundle +# Dragonball Bundle +# Empty Bundle (donate empty inventory spot) +# Friendship Bundle (Show some NPCs, gotta donate a loved gift for each of them) +# GeoGessr Bundle +# Ghost Bundle (it ghosts you) +# Joja/Morris Bundle +# Leaf Blower Bundle (Leaf Blower Minigame, similar to the cookie clicker one) +# Lingo Bundle +# Lost Axe Bundle (Donate your axe then talk to Robin) +# Maguffin Bundle (Ap items) +# Millibelle Bundle (money, run away, find at spa) +# Minesweeper bundle (donate bombs on correct spots) +# Pico-8 Bundle +# Pollution Bundle +# QA Bundle (Some sort of bug, not sure yet) +# Relay Bundle (Relay Stick passed around the multiworld) +# Robin's Lost Axe Bundle (Give your axe, then Robin brings it back to you) +# Scavenger Bundle (The bundle moves around the map and you need to keep finding it) +# Side Quest Bundle (Sends you on side quests to talk to random NPCs several times) +# Therapy Bundle +# Torrent Bundle (someone must seed it for you) +# Witness Bundle +# Change Cap Bundle to forgetting something at home + + + +# Bundles that need special Mod Handling: +# None + +pantry_bundles_meme = [hurricane_tortilla_bundle, look_at_chickens_bundle, lemonade_stand_bundle, what_the_rock_is_cooking_bundle, sunmaid_bundle, + big_grapes_bundle, eg_bundle, not_the_bees_bundle, speedrunners_bundle, bun_dle_bundle, animal_well_bundle, bad_farmer_bundle] +pantry_meme = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_meme, 6) + +crafts_room_bundles_meme = [AAAA_bundle, anything_for_beyonce_bundle, potato_bundle, chaos_emerald_bundle, caffeinated_bundle, reverse_bundle, + ikea_bundle, this_is_fine_bundle, very_sticky_bundle, dr_seuss_bundle] +crafts_room_meme = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_meme, 6) + +fish_tank_bundles_meme = [crab_rave_bundle, trout_bundle, doctor_angler_bundle, mermaid_bundle, legendairy_bundle, kent_c_bundle, bundle_bundle, + schrodinger_bundle, bad_fisherman_bundle, pollution_bundle, catch_and_release_bundle] +fish_tank_meme = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_meme, 6) + +boiler_room_bundles_meme = [amons_fall_bundle, screw_you_bundle, rick_bundle, minecraft_bundle, balls_bundle, tilesanity_bundle, obelisks_bundle, + honorable_bundle, sisyphus_bundle, automation_bundle, crap_pot_bundle, deathlink_bundle, pool_bundle, # colored_crystals_bundle, + sacrifice_bundle] +boiler_room_meme = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_meme, 3) + +bulletin_board_bundles_meme = [burger_king_bundle, romance_bundle, burger_king_revenge_bundle, smapi_bundle, sappy_bundle, hats_off_to_you_bundle, + snitch_bundle, commitment_bundle_bundle, journalist_bundle, trap_bundle, off_your_back_bundle, vocaloid_bundle, fruit_bundle, + celeste_bundle, cap_bundle, emmalution_bundle, joetg_bundle, honeywell_bundle, cooperation_bundle, square_hole_bundle, + ministry_bundle, loser_club_bundle, frazzleduck_bundle, argonmatrix_bundle, pomnut_bundle, blossom_garden_bundle, doctor_bundle, + hint_bundle, algorerhythm_bundle, distracted_bundle] +bulletin_board_meme = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_meme, 5) + +vault_bundles_meme = [capitalist_bundle, death_bundle, permit_a38_bundle, vampire_bundle, exhaustion_bundle, + tick_tock_bundle, archipela_go_bundle, clique_bundle, cookie_clicker_bundle, communism_bundle, + flashbang_bundle, connection_bundle, reconnection_bundle, nft_bundle, firstborn_bundle, restraint_bundle, fast_bundle, + floor_is_lava_bundle, gacha_bundle, hibernation_bundle, crowdfunding_bundle, clickbait_bundle, + humble_bundle, puzzle_bundle, asmr_bundle, investment_bundle, stanley_bundle, hairy_bundle] +vault_meme = BundleRoomTemplate(CCRoom.vault, vault_bundles_meme, 4) + +all_cc_meme_bundles = [*pantry_bundles_meme, *crafts_room_bundles_meme, *fish_tank_bundles_meme, + *boiler_room_bundles_meme, *bulletin_board_bundles_meme, *vault_bundles_meme] +community_center_meme_bundles = BundleRoomTemplate("Community Center", all_cc_meme_bundles, 30) diff --git a/worlds/stardew_valley/data/bundles_data/meme_bundles_data/__init__.py b/worlds/stardew_valley/data/bundles_data/meme_bundles_data/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/stardew_valley/data/bundles_data/meme_bundles_data/capitalist_bundle.py b/worlds/stardew_valley/data/bundles_data/meme_bundles_data/capitalist_bundle.py new file mode 100644 index 000000000000..3c37eebcd2ab --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/meme_bundles_data/capitalist_bundle.py @@ -0,0 +1,92 @@ +import math +from typing import Dict, List + +from ..bundle_items_data import * +from ....bundles.bundle_item import BundleItem +from ....strings.quality_names import ArtisanQuality + +capitalist_value = 1000000 +ancient_fruit_wines = {ArtisanQuality.basic: 2310, ArtisanQuality.silver: 2886, ArtisanQuality.gold: 3465, ArtisanQuality.iridium: 4620} +starfruit_wines = {ArtisanQuality.basic: 3150, ArtisanQuality.silver: 3936, ArtisanQuality.gold: 4725, ArtisanQuality.iridium: 6300} +rhubarb_wines = {ArtisanQuality.silver: 1155, ArtisanQuality.gold: 1386, ArtisanQuality.iridium: 1848} +melon_wines = {ArtisanQuality.basic: 1050, ArtisanQuality.silver: 1311, ArtisanQuality.gold: 1575, ArtisanQuality.iridium: 2100} +pineapple_wines = {ArtisanQuality.basic: 1260, ArtisanQuality.silver: 1575, ArtisanQuality.gold: 1890, ArtisanQuality.iridium: 2520} +starfruits = {ArtisanQuality.silver: 1030, ArtisanQuality.gold: 1237, ArtisanQuality.iridium: 1650} +sweet_gem_berries = {ArtisanQuality.basic: 3000, ArtisanQuality.silver: 3750, ArtisanQuality.gold: 4500, ArtisanQuality.iridium: 6000} + +# These are just too rude I think +# cherry_saplings = {ArtisanQuality.silver: 1062, ArtisanQuality.gold: 1275, ArtisanQuality.iridium: 1700} +# banana_saplings = {ArtisanQuality.silver: 1062, ArtisanQuality.gold: 1275, ArtisanQuality.iridium: 1700} +# mango_saplings = {ArtisanQuality.silver: 1062, ArtisanQuality.gold: 1275, ArtisanQuality.iridium: 1700} +# orange_saplings = {ArtisanQuality.silver: 1250, ArtisanQuality.gold: 1500, ArtisanQuality.iridium: 2000} +# peach_saplings = {ArtisanQuality.basic: 1500, ArtisanQuality.silver: 1875, ArtisanQuality.gold: 2250, ArtisanQuality.iridium: 3000} +# apple_saplings = {ArtisanQuality.silver: 1250, ArtisanQuality.gold: 1500, ArtisanQuality.iridium: 2000} +# pomegranate_saplings = {ArtisanQuality.basic: 1500, ArtisanQuality.silver: 1875, ArtisanQuality.gold: 2250, ArtisanQuality.iridium: 3000} + + +def get_capitalist_item(item: BundleItem, quality: str, value: int) -> BundleItem: + amount = math.ceil(capitalist_value / value) + assert amount < 1000 + return item.as_quality(quality).as_amount(amount) + + +def get_capitalist_items(item: BundleItem, values_by_quality: Dict[str, int]) -> List[BundleItem]: + return [get_capitalist_item(item, quality, values_by_quality[quality]) for quality in values_by_quality] + + +capitalist_items = [ + *get_capitalist_items(ancient_fruit_wine, ancient_fruit_wines), + get_capitalist_item(dried_ancient_fruit, ArtisanQuality.basic, 5810), + get_capitalist_item(ancient_fruit_jelly, ArtisanQuality.basic, 1610), + get_capitalist_item(ancient_fruit, ArtisanQuality.iridium, 1210), + + *get_capitalist_items(starfruit_wine, starfruit_wines), + get_capitalist_item(dried_starfruit, ArtisanQuality.basic, 7910), + get_capitalist_item(starfruit_jelly, ArtisanQuality.basic, 2170), + *get_capitalist_items(starfruit, starfruits), + + *get_capitalist_items(rhubarb_wine, rhubarb_wines), + get_capitalist_item(dried_rhubarb, ArtisanQuality.basic, 2345), + *get_capitalist_items(melon_wine, melon_wines), + get_capitalist_item(dried_melon, ArtisanQuality.basic, 2660), + *get_capitalist_items(pineapple_wine, pineapple_wines), + + get_capitalist_item(dried_pineapple, ArtisanQuality.basic, 3185), + get_capitalist_item(dried_banana, ArtisanQuality.basic, 1610), + get_capitalist_item(strawberry_wine, ArtisanQuality.iridium, 1008), + get_capitalist_item(dried_strawberry, ArtisanQuality.basic, 1295), + + *get_capitalist_items(sweet_gem_berry, sweet_gem_berries), + get_capitalist_item(pumpkin_juice, ArtisanQuality.basic, 1008), + + get_capitalist_item(goat_cheese, ArtisanQuality.iridium, 1120), + get_capitalist_item(golden_egg, ArtisanQuality.iridium, 1200), + get_capitalist_item(dinosaur_mayo, ArtisanQuality.basic, 1120), + get_capitalist_item(truffle_oil, ArtisanQuality.basic, 1491), + get_capitalist_item(truffle, ArtisanQuality.iridium, 1250), + + get_capitalist_item(aged_lava_eel_roe, ArtisanQuality.basic, 1064), + get_capitalist_item(aged_crimsonfish_roe, ArtisanQuality.basic, 2184), + get_capitalist_item(aged_angler_roe, ArtisanQuality.basic, 1344), + get_capitalist_item(legend_roe, ArtisanQuality.basic, 2530), + get_capitalist_item(aged_legend_roe, ArtisanQuality.basic, 7084), + get_capitalist_item(aged_glacierfish_roe, ArtisanQuality.basic, 1484), + get_capitalist_item(aged_mutant_carp_roe, ArtisanQuality.basic, 1484), + + get_capitalist_item(iridium_bar, ArtisanQuality.basic, 1500), + get_capitalist_item(radioactive_bar, ArtisanQuality.basic, 4500), + get_capitalist_item(prismatic_shard, ArtisanQuality.basic, 2600), + + get_capitalist_item(mystic_syrup, ArtisanQuality.basic, 1250), + + # *get_capitalist_items(cherry_sapling, cherry_saplings), + # *get_capitalist_items(banana_sapling, banana_saplings), + # *get_capitalist_items(mango_sapling, mango_saplings), + # *get_capitalist_items(orange_sapling, orange_saplings), + # *get_capitalist_items(peach_sapling, peach_saplings), + # *get_capitalist_items(apple_sapling, apple_saplings), + # *get_capitalist_items(pomegranate_sapling, pomegranate_saplings), + + bowler_hat, + sombrero, +] \ No newline at end of file diff --git a/worlds/stardew_valley/data/bundles_data/remixed_anywhere_bundles.py b/worlds/stardew_valley/data/bundles_data/remixed_anywhere_bundles.py new file mode 100644 index 000000000000..56504f146e9e --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/remixed_anywhere_bundles.py @@ -0,0 +1,5 @@ +from .remixed_bundles import * + +all_cc_remixed_bundles = [*crafts_room_bundles_remixed, *pantry_bundles_remixed, *fish_tank_bundles_remixed, + *boiler_room_bundles_remixed, *bulletin_board_bundles_remixed, *vault_bundles_remixed] +community_center_remixed_anywhere = BundleRoomTemplate("Community Center", all_cc_remixed_bundles, 30) diff --git a/worlds/stardew_valley/data/bundles_data/remixed_bundles.py b/worlds/stardew_valley/data/bundles_data/remixed_bundles.py new file mode 100644 index 000000000000..a4098be4c591 --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/remixed_bundles.py @@ -0,0 +1,245 @@ +from .thematic_bundles import * +from ...bundles.bundle import IslandBundleTemplate, FestivalBundleTemplate, CurrencyBundleTemplate +from ...bundles.bundle_room import BundleRoomTemplate +from ...content import content_packs +from ...strings.bundle_names import CCRoom + +# Giant Stump +from ...strings.quality_names import ForageQuality, FishQuality + +giant_stump_bundles_remixed = giant_stump_bundles_thematic +giant_stump_remixed = BundleRoomTemplate(CCRoom.raccoon_requests, giant_stump_bundles_remixed, 8) + +# Crafts Room + +beach_foraging_items = [nautilus_shell, coral, sea_urchin, rainbow_shell, clam, cockle, mussel, oyster, seaweed] +beach_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.beach_foraging, beach_foraging_items, 4, 4) + +mines_foraging_items = [quartz, earth_crystal, frozen_tear, fire_quartz, red_mushroom, purple_mushroom, cave_carrot] +mines_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.mines_foraging, mines_foraging_items, 4, 4) + +desert_foraging_items = [cactus_fruit.as_quality(ForageQuality.gold), cactus_fruit.as_amount(5), coconut.as_quality(ForageQuality.gold), coconut.as_amount(5)] +desert_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.desert_foraging, desert_foraging_items, 2, 2) + +island_foraging_items = [ginger.as_amount(5), magma_cap.as_quality(ForageQuality.gold), magma_cap.as_amount(5), + fiddlehead_fern.as_quality(ForageQuality.gold), fiddlehead_fern.as_amount(5)] +island_foraging_bundle = IslandBundleTemplate(CCRoom.crafts_room, BundleName.island_foraging, island_foraging_items, 2, 2) + +sticky_items = [sap.as_amount(500), sap.as_amount(500)] +sticky_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.sticky, sticky_items, 1, 1) + +forest_items = [moss.as_amount(10), fiber.as_amount(200), acorn.as_amount(10), maple_seed.as_amount(10), pine_cone.as_amount(10), mahogany_seed, + mushroom_tree_seed, mossy_seed.as_amount(5), mystic_tree_seed] +forest_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.forest, forest_items, 4, 2) + +wild_medicine_items = [item.as_amount(5) for item in [purple_mushroom, fiddlehead_fern, white_algae, hops, blackberry, dandelion]] +wild_medicine_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.wild_medicine, wild_medicine_items, 4, 3) + +quality_foraging_items = sorted({item.as_quality(ForageQuality.gold).as_amount(3) + for item in + [*spring_foraging_items_thematic, *summer_foraging_items_thematic, *fall_foraging_items_thematic, + *winter_foraging_items_thematic, *beach_foraging_items, *desert_foraging_items, magma_cap] if item.can_have_quality}) +quality_foraging_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.quality_foraging, quality_foraging_items, 4, 3) + +green_rain_items = [moss.as_amount(200), fiber.as_amount(200), mossy_seed.as_amount(20), fiddlehead_fern.as_amount(10)] +green_rain_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.green_rain, green_rain_items, 4, 3) + +totems_items = [warp_totem_beach.as_amount(5), warp_totem_mountains.as_amount(5), warp_totem_farm.as_amount(5), warp_totem_desert.as_amount(5), + warp_totem_island.as_amount(5), rain_totem.as_amount(5), treasure_totem.as_amount(5)] +totems_bundle = BundleTemplate(CCRoom.crafts_room, BundleName.totems, totems_items, 4, 3) + +crafts_room_bundles_remixed = [*crafts_room_bundles_thematic, beach_foraging_bundle, mines_foraging_bundle, desert_foraging_bundle, + island_foraging_bundle, sticky_bundle, forest_bundle, wild_medicine_bundle, quality_foraging_bundle, green_rain_bundle] +crafts_room_remixed = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_remixed, 6) + +# Pantry + +rare_crops_items = [ancient_fruit, sweet_gem_berry] +rare_crops_bundle = BundleTemplate(CCRoom.pantry, BundleName.rare_crops, rare_crops_items, 2, 2) + +# all_specific_roes = [BundleItem(AnimalProduct.roe, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fish] +fish_farmer_items = [roe.as_amount(15), aged_roe.as_amount(5), squid_ink, caviar.as_amount(5)] +fish_farmer_bundle = BundleTemplate(CCRoom.pantry, BundleName.fish_farmer, fish_farmer_items, 3, 2) + +garden_items = [tulip, blue_jazz, summer_spangle, sunflower, fairy_rose, poppy, bouquet] +garden_bundle = BundleTemplate(CCRoom.pantry, BundleName.garden, garden_items, 5, 4) + +brewer_items = [mead, pale_ale, wine, juice, green_tea, beer] +brewer_bundle = BundleTemplate(CCRoom.pantry, BundleName.brewer, brewer_items, 5, 4) + +orchard_items = [apple, apricot, orange, peach, pomegranate, cherry, banana, mango] +orchard_bundle = BundleTemplate(CCRoom.pantry, BundleName.orchard, orchard_items, 6, 4) + +island_crops_items = [pineapple, taro_root, banana, mango] +island_crops_bundle = IslandBundleTemplate(CCRoom.pantry, BundleName.island_crops, island_crops_items, 3, 3) + +agronomist_items = [basic_fertilizer, quality_fertilizer, deluxe_fertilizer, + basic_retaining_soil, quality_retaining_soil, deluxe_retaining_soil, + speed_gro, deluxe_speed_gro, hyper_speed_gro, tree_fertilizer] +agronomist_bundle = BundleTemplate(CCRoom.pantry, BundleName.agronomist, agronomist_items, 4, 3) + +slime_farmer_items = [slime.as_amount(99), petrified_slime.as_amount(10), blue_slime_egg, red_slime_egg, + purple_slime_egg, green_slime_egg, tiger_slime_egg] +slime_farmer_bundle = BundleTemplate(CCRoom.pantry, BundleName.slime_farmer, slime_farmer_items, 4, 3) + +sommelier_items = [BundleItem(ArtisanGood.wine, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits] +sommelier_bundle = BundleTemplate(CCRoom.pantry, BundleName.sommelier, sommelier_items, 6, 3) + +dry_items = [*[BundleItem(ArtisanGood.dried_fruit, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits], + *[BundleItem(ArtisanGood.dried_mushroom, flavor=mushroom, source=BundleItem.Sources.content) for mushroom in all_edible_mushrooms], + BundleItem(ArtisanGood.raisins, source=BundleItem.Sources.content)] +dry_bundle = BundleTemplate(CCRoom.pantry, BundleName.dry, dry_items, 6, 3) + +pantry_bundles_remixed = [*pantry_bundles_thematic, rare_crops_bundle, fish_farmer_bundle, garden_bundle, + brewer_bundle, orchard_bundle, island_crops_bundle, agronomist_bundle, slime_farmer_bundle, sommelier_bundle, dry_bundle] +pantry_remixed = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_remixed, 6) + +# Fish Tank +trash_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.trash, crab_pot_trash_items, 4, 4) + +spring_fish_items = [herring, halibut, shad, flounder, sunfish, sardine, catfish, anchovy, smallmouth_bass, eel, legend] +spring_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.spring_fish, spring_fish_items, 4, 4) + +summer_fish_items = [tuna, pike, red_mullet, sturgeon, red_snapper, super_cucumber, tilapia, pufferfish, rainbow_trout, + octopus, dorado, halibut, shad, flounder, sunfish, crimsonfish] +summer_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.summer_fish, summer_fish_items, 4, 4) + +fall_fish_items = [red_snapper, super_cucumber, tilapia, shad, sardine, catfish, anchovy, smallmouth_bass, eel, midnight_carp, + walleye, sea_cucumber, tiger_trout, albacore, salmon, angler] +fall_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.fall_fish, fall_fish_items, 4, 4) + +winter_fish_items = [perch, squid, lingcod, tuna, pike, red_mullet, sturgeon, red_snapper, herring, halibut, sardine, + midnight_carp, sea_cucumber, tiger_trout, albacore, glacierfish] +winter_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.winter_fish, winter_fish_items, 4, 4) + +rain_fish_items = [red_snapper, shad, catfish, eel, walleye] +rain_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.rain_fish, rain_fish_items, 3, 3) + +quality_fish_items = sorted({ + item.as_quality(FishQuality.gold).as_amount(2) + for item in [*river_fish_items_thematic, *lake_fish_items_thematic, *ocean_fish_items_thematic] +}) +quality_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.quality_fish, quality_fish_items, 4, 3) + +master_fisher_items = [lava_eel, scorpion_carp, octopus, blobfish, lingcod, ice_pip, super_cucumber, stingray, void_salmon, pufferfish] +master_fisher_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.master_fisher, master_fisher_items, 4, 2) + +legendary_fish_items = [angler, legend, mutant_carp, crimsonfish, glacierfish] +legendary_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.legendary_fish, legendary_fish_items, 4, 2) + +island_fish_items = [lionfish, blue_discus, stingray] +island_fish_bundle = IslandBundleTemplate(CCRoom.fish_tank, BundleName.island_fish, island_fish_items, 3, 3) + +tackle_items = [spinner, dressed_spinner, trap_bobber, sonar_bobber, cork_bobber, lead_bobber, treasure_hunter, barbed_hook, curiosity_lure, quality_bobber] +tackle_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.tackle, tackle_items, 3, 2) + +bait_items = [bait, magnet, wild_bait, magic_bait, challenge_bait, deluxe_bait, targeted_bait] +bait_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.bait, bait_items, 3, 2) + +# This bundle could change based on content packs, once the fish are properly in it. Until then, I'm not sure how, so pelican town only +specific_bait_items = [BundleItem(ArtisanGood.targeted_bait, flavor=fish.name).as_amount(10) for fish in content_packs.pelican_town.fishes] +specific_bait_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.specific_bait, specific_bait_items, 6, 3) + +deep_fishing_items = [blobfish, spook_fish, midnight_squid, sea_cucumber, super_cucumber, octopus, pearl, seaweed] +deep_fishing_bundle = FestivalBundleTemplate(CCRoom.fish_tank, BundleName.deep_fishing, deep_fishing_items, 4, 3) + +smokeable_fish = [Fish.largemouth_bass, Fish.bream, Fish.bullhead, Fish.chub, Fish.ghostfish, Fish.flounder, Fish.shad, Fish.rainbow_trout, Fish.tilapia, + Fish.red_mullet, Fish.tuna, Fish.midnight_carp, Fish.salmon, Fish.perch] +fish_smoker_items = [BundleItem(ArtisanGood.smoked_fish, flavor=fish) for fish in smokeable_fish] +fish_smoker_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.fish_smoker, fish_smoker_items, 6, 3) + +fish_tank_bundles_remixed = [*fish_tank_bundles_thematic, spring_fish_bundle, summer_fish_bundle, + fall_fish_bundle, winter_fish_bundle, trash_bundle, rain_fish_bundle, + quality_fish_bundle, master_fisher_bundle, legendary_fish_bundle, + tackle_bundle, bait_bundle, specific_bait_bundle, deep_fishing_bundle, + fish_smoker_bundle] + +# In Remixed, the trash items are in the recycling bundle, so we don't use the thematic version of the crab pot bundle that added trash items to it +fish_tank_bundles_remixed.remove(crab_pot_bundle_thematic) +fish_tank_bundles_remixed.append(crab_pot_bundle_vanilla) + +fish_tank_remixed = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_remixed, 6) + +# Boiler Room + +# Where to put radioactive bar? +treasure_hunter_items = [emerald, aquamarine, ruby, amethyst, topaz, jade, diamond] +treasure_hunter_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.treasure_hunter, treasure_hunter_items, 6, 5) + +engineer_items = [iridium_ore.as_amount(5), battery_pack, refined_quartz.as_amount(5), diamond] +engineer_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.engineer, engineer_items, 3, 3) + +demolition_items = [cherry_bomb, bomb, mega_bomb, explosive_ammo] +demolition_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.demolition, demolition_items, 3, 3) + +recycling_items = [stone, coal, iron_ore, wood, cloth, refined_quartz] +recycling_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.recycling, recycling_items, 4, 4) + +archaeologist_items = [golden_mask, golden_relic, ancient_drum, dwarf_gadget, dwarvish_helm, prehistoric_handaxe, bone_flute, anchor, prehistoric_tool, + chicken_statue, rusty_cog, rusty_spur, rusty_spoon, ancient_sword, ornamental_fan, elvish_jewelry, ancient_doll, chipped_amphora] +archaeologist_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.archaeologist, archaeologist_items, 6, 3) + +paleontologist_items = [prehistoric_scapula, prehistoric_tibia, prehistoric_skull, skeletal_hand, prehistoric_rib, prehistoric_vertebra, skeletal_tail, + nautilus_fossil, amphibian_fossil, palm_fossil, trilobite] +paleontologist_bundle = BundleTemplate(CCRoom.boiler_room, BundleName.paleontologist, paleontologist_items, 6, 3) + +boiler_room_bundles_remixed = [*boiler_room_bundles_thematic, treasure_hunter_bundle, engineer_bundle, + demolition_bundle, recycling_bundle, archaeologist_bundle, paleontologist_bundle] +boiler_room_remixed = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_remixed, 3) + +# Bulletin Board +children_items = [salmonberry.as_amount(10), cookie, ancient_doll, ice_cream, cranberry_candy, ginger_ale, + grape.as_amount(10), pink_cake, snail, fairy_rose, plum_pudding] +children_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.children, children_items, 4, 3) + +forager_items = [salmonberry.as_amount(50), blackberry.as_amount(50), wild_plum.as_amount(20), snow_yam.as_amount(20), + common_mushroom.as_amount(20), grape.as_amount(20), spring_onion.as_amount(20)] +forager_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.forager, forager_items, 3, 2) + +home_cook_items = [egg.as_amount(10), milk.as_amount(10), wheat_flour.as_amount(100), sugar.as_amount(100), vinegar.as_amount(100), + chocolate_cake, pancakes, rhubarb_pie] +home_cook_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.home_cook, home_cook_items, 3, 3) + +helper_items = [prize_ticket, mystery_box.as_amount(5), gold_mystery_box] +helper_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.helper, helper_items, 2, 2) + +spirit_eve_items = [jack_o_lantern, corn.as_amount(10), bat_wing.as_amount(10)] +spirit_eve_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.spirit_eve, spirit_eve_items, 3, 3) + +winter_star_items = [holly.as_amount(5), plum_pudding, stuffing, powdermelon.as_amount(5)] +winter_star_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.winter_star, winter_star_items, 2, 2) + +bartender_items = [shrimp_cocktail, triple_shot_espresso, ginger_ale, cranberry_candy, beer, pale_ale, pina_colada] +bartender_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.bartender, bartender_items, 3, 3) + +calico_items = [calico_egg.as_amount(200), calico_egg.as_amount(200), calico_egg.as_amount(200), calico_egg.as_amount(200), + magic_rock_candy, mega_bomb.as_amount(10), mystery_box.as_amount(10), mixed_seeds.as_amount(50), + strawberry_seeds.as_amount(20), + spicy_eel.as_amount(5), crab_cakes.as_amount(5), eggplant_parmesan.as_amount(5), + pumpkin_soup.as_amount(5), lucky_lunch.as_amount(5) ] +calico_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.calico, calico_items, 2, 2) + +raccoon_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.raccoon, raccoon_foraging_items, 4, 4) + +bulletin_board_bundles_remixed = [*bulletin_board_bundles_thematic, children_bundle, forager_bundle, home_cook_bundle, + helper_bundle, spirit_eve_bundle, winter_star_bundle, bartender_bundle, calico_bundle, raccoon_bundle] +bulletin_board_remixed = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_remixed, 5) + +# Abandoned Joja Mart +abandoned_joja_mart_remixed = abandoned_joja_mart_thematic + +# Vault +vault_gambler_items = BundleItem(Currency.qi_coin, 10000) +vault_gambler_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.gambler, vault_gambler_items) + +vault_carnival_items = BundleItem(Currency.star_token, 2500, source=BundleItem.Sources.festival) +vault_carnival_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.carnival, vault_carnival_items) + +vault_walnut_hunter_items = BundleItem(Currency.golden_walnut, 25) +vault_walnut_hunter_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.walnut_hunter, vault_walnut_hunter_items) + +vault_qi_helper_items = BundleItem(Currency.qi_gem, 25, source=BundleItem.Sources.island) +vault_qi_helper_bundle = CurrencyBundleTemplate(CCRoom.vault, BundleName.qi_helper, vault_qi_helper_items) + +vault_bundles_remixed = [*vault_bundles_vanilla, vault_gambler_bundle, vault_qi_helper_bundle, vault_carnival_bundle] # , vault_walnut_hunter_bundle +vault_remixed = BundleRoomTemplate(CCRoom.vault, vault_bundles_remixed, 4) diff --git a/worlds/stardew_valley/data/bundles_data/thematic_bundles.py b/worlds/stardew_valley/data/bundles_data/thematic_bundles.py new file mode 100644 index 000000000000..14988835333f --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/thematic_bundles.py @@ -0,0 +1,158 @@ +from .vanilla_bundles import * +from ...bundles.bundle import BundleTemplate +from ...bundles.bundle_room import BundleRoomTemplate +from ...strings.bundle_names import CCRoom, BundleName + +# Giant Stump +from ...strings.quality_names import ArtisanQuality, FishQuality + +raccoon_fish_items_flat = [*raccoon_crab_pot_fish_items, *raccoon_smoked_fish_items] +raccoon_fish_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_fish, raccoon_fish_items_flat, 3, 2) +raccoon_artisan_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_artisan, raccoon_artisan_items, 3, 2) + +raccoon_food_items_thematic = [*all_specific_dried_mushrooms, *raccoon_food_items, brown_egg.as_amount(5), large_egg.as_amount(2), large_brown_egg.as_amount(2), + green_algae.as_amount(10)] +raccoon_food_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_food, raccoon_food_items_thematic, 3, 2) + +raccoon_foraging_bundle_thematic = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_foraging, raccoon_foraging_items, 3, 2) + +giant_stump_bundles_thematic = [raccoon_fish_bundle_thematic, raccoon_artisan_bundle_thematic, raccoon_food_bundle_thematic, raccoon_foraging_bundle_thematic] +giant_stump_thematic = BundleRoomTemplate(CCRoom.raccoon_requests, giant_stump_bundles_thematic, 8) + + +# Crafts Room +spring_foraging_items_thematic = [*spring_foraging_items_vanilla, spring_onion, salmonberry, morel] +spring_foraging_bundle_thematic = BundleTemplate.extend_from(spring_foraging_bundle_vanilla, spring_foraging_items_thematic) + +summer_foraging_items_thematic = [*summer_foraging_items_vanilla, fiddlehead_fern, red_mushroom, rainbow_shell] +summer_foraging_bundle_thematic = BundleTemplate.extend_from(summer_foraging_bundle_vanilla, summer_foraging_items_thematic) + +fall_foraging_items_thematic = [*fall_foraging_items_vanilla, chanterelle] +fall_foraging_bundle_thematic = BundleTemplate.extend_from(fall_foraging_bundle_vanilla, fall_foraging_items_thematic) + +winter_foraging_items_thematic = [*winter_foraging_items_vanilla, holly, nautilus_shell] +winter_foraging_bundle_thematic = BundleTemplate.extend_from(winter_foraging_bundle_vanilla, winter_foraging_items_thematic) + +construction_items_thematic = [*construction_items_vanilla, clay.as_amount(10), fiber.as_amount(99), sap.as_amount(50)] +construction_bundle_thematic = BundleTemplate.extend_from(construction_bundle_vanilla, construction_items_thematic) + +exotic_foraging_items_thematic = [*exotic_foraging_items_vanilla, coral, sea_urchin, clam, cockle, mussel, oyster, seaweed] +exotic_foraging_bundle_thematic = BundleTemplate.extend_from(exotic_foraging_bundle_vanilla, exotic_foraging_items_thematic) + +crafts_room_bundles_thematic = [spring_foraging_bundle_thematic, summer_foraging_bundle_thematic, fall_foraging_bundle_thematic, + winter_foraging_bundle_thematic, construction_bundle_thematic, exotic_foraging_bundle_thematic] +crafts_room_thematic = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_thematic, 6) + +# Pantry +spring_crops_items_thematic = [*spring_crops_items_vanilla, blue_jazz, coffee_bean, garlic, kale, rhubarb, strawberry, tulip, unmilled_rice, carrot] +spring_crops_bundle_thematic = BundleTemplate.extend_from(spring_crops_bundle_vanilla, spring_crops_items_thematic) + +summer_crops_items_thematic = [*summer_crops_items_vanilla, corn, hops, poppy, radish, red_cabbage, starfruit, summer_spangle, sunflower, wheat, summer_squash] +summer_crops_bundle_thematic = BundleTemplate.extend_from(summer_crops_bundle_vanilla, summer_crops_items_thematic) + +fall_crops_items_thematic = [*fall_crops_items_vanilla, amaranth, artichoke, beet, bok_choy, cranberries, fairy_rose, grape, + sunflower, wheat, sweet_gem_berry, broccoli] +fall_crops_bundle_thematic = BundleTemplate.extend_from(fall_crops_bundle_vanilla, fall_crops_items_thematic) + +all_crops_items = sorted({*spring_crops_items_thematic, *summer_crops_items_thematic, *fall_crops_items_thematic, powdermelon}) + +quality_crops_items_thematic = [item.as_quality_crop() for item in all_crops_items] +quality_crops_bundle_thematic = BundleTemplate.extend_from(quality_crops_bundle_vanilla, quality_crops_items_thematic) + +animal_items_thematic = [*animal_items_vanilla, egg, brown_egg, milk, goat_milk, truffle, + duck_feather, rabbit_foot, dinosaur_egg, void_egg, golden_egg, ostrich_egg] +animal_bundle_thematic = BundleTemplate.extend_from(animal_bundle_vanilla, animal_items_thematic) + +artisan_items_thematic = [*artisan_items_vanilla, beer, juice, mead, pale_ale, wine, pickles, caviar, aged_roe, coffee, green_tea, banana, mango] +artisan_bundle_thematic = BundleTemplate.extend_from(artisan_bundle_vanilla, artisan_items_thematic) + +pantry_bundles_thematic = [spring_crops_bundle_thematic, summer_crops_bundle_thematic, fall_crops_bundle_thematic, + quality_crops_bundle_thematic, animal_bundle_thematic, artisan_bundle_thematic] +pantry_thematic = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_thematic, 6) + +# Fish Tank +river_fish_items_thematic = [*river_fish_items_vanilla, chub, rainbow_trout, lingcod, walleye, perch, pike, bream, salmon, smallmouth_bass, dorado] +river_fish_bundle_thematic = BundleTemplate.extend_from(river_fish_bundle_vanilla, river_fish_items_thematic) + +lake_fish_items_thematic = [*lake_fish_items_vanilla, chub, rainbow_trout, lingcod, walleye, perch, midnight_carp] +lake_fish_bundle_thematic = BundleTemplate.extend_from(lake_fish_bundle_vanilla, lake_fish_items_thematic) + +ocean_fish_items_thematic = [*ocean_fish_items_vanilla, pufferfish, super_cucumber, flounder, anchovy, red_mullet, + herring, eel, octopus, squid, sea_cucumber, albacore, halibut] +ocean_fish_bundle_thematic = BundleTemplate.extend_from(ocean_fish_bundle_vanilla, ocean_fish_items_thematic) + +night_fish_items_thematic = [*night_fish_items_vanilla, super_cucumber, squid, midnight_carp, midnight_squid] +night_fish_bundle_thematic = BundleTemplate.extend_from(night_fish_bundle_vanilla, night_fish_items_thematic) + +crab_pot_items_thematic = [*crab_pot_items_vanilla, *crab_pot_trash_items] +crab_pot_bundle_thematic = BundleTemplate.extend_from(crab_pot_bundle_vanilla, crab_pot_items_thematic) + +specialty_fish_items_thematic = [*specialty_fish_items_vanilla, scorpion_carp, eel, octopus, lava_eel, ice_pip, + stonefish, void_salmon, stingray, spookfish, midnight_squid, slimejack, goby] +specialty_fish_bundle_thematic = BundleTemplate.extend_from(specialty_fish_bundle_vanilla, specialty_fish_items_thematic) + +fish_tank_bundles_thematic = [river_fish_bundle_thematic, lake_fish_bundle_thematic, ocean_fish_bundle_thematic, + night_fish_bundle_thematic, crab_pot_bundle_thematic, specialty_fish_bundle_thematic] +fish_tank_thematic = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_thematic, 6) + +# Boiler Room +blacksmith_items_thematic = [*blacksmith_items_vanilla, iridium_bar, refined_quartz.as_amount(3), wilted_bouquet] +blacksmith_bundle_thematic = BundleTemplate.extend_from(blacksmith_bundle_vanilla, blacksmith_items_thematic) + +geologist_items_thematic = [*geologist_items_vanilla, emerald, aquamarine, ruby, amethyst, topaz, jade, diamond] +geologist_bundle_thematic = BundleTemplate.extend_from(geologist_bundle_vanilla, geologist_items_thematic) + +adventurer_items_thematic = [*adventurer_items_vanilla, bug_meat, coal.as_amount(5), bone_fragment.as_amount(10)] +adventurer_bundle_thematic = BundleTemplate.extend_from(adventurer_bundle_vanilla, adventurer_items_thematic) + +boiler_room_bundles_thematic = [blacksmith_bundle_thematic, geologist_bundle_thematic, adventurer_bundle_thematic] +boiler_room_thematic = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_thematic, 3) + +# Bulletin Board + +# More recipes? +chef_items_thematic = [maki_roll, fried_egg, omelet, pizza, hashbrowns, pancakes, bread, tortilla, + farmer_s_lunch, survival_burger, dish_o_the_sea, miner_s_treat, roots_platter, salad, + cheese_cauliflower, parsnip_soup, fried_mushroom, salmon_dinner, pepper_poppers, spaghetti, + sashimi, blueberry_tart, algae_soup, pale_broth, chowder] +chef_bundle_thematic = BundleTemplate.extend_from(chef_bundle_vanilla, chef_items_thematic) + +dye_red_items = [cranberries, hot_pepper, radish, rhubarb, spaghetti, strawberry, tomato, tulip, red_mushroom] +dye_orange_items = [poppy, pumpkin, apricot, orange, spice_berry, winter_root] +dye_yellow_items = [corn, parsnip, summer_spangle, sunflower, starfruit] +dye_green_items = [fiddlehead_fern, kale, artichoke, bok_choy, green_bean, cactus_fruit, duck_feather, dinosaur_egg] +dye_blue_items = [blueberry, blue_jazz, blackberry, crystal_fruit, aquamarine] +dye_purple_items = [beet, crocus, eggplant, red_cabbage, sweet_pea, iridium_bar, sea_urchin, amaranth] +dye_items_thematic = [dye_red_items, dye_orange_items, dye_yellow_items, dye_green_items, dye_blue_items, dye_purple_items] +dye_bundle_thematic = DeepBundleTemplate(CCRoom.bulletin_board, BundleName.dye, dye_items_thematic, 6, 6) + +field_research_items_thematic = [*field_research_items_vanilla, geode, magma_geode, omni_geode, + rainbow_shell, amethyst, bream, carp] +field_research_bundle_thematic = BundleTemplate.extend_from(field_research_bundle_vanilla, field_research_items_thematic) + +fodder_items_thematic = [*fodder_items_vanilla, kale.as_amount(3), corn.as_amount(3), green_bean.as_amount(3), + potato.as_amount(3), green_algae.as_amount(5), white_algae.as_amount(3)] +fodder_bundle_thematic = BundleTemplate.extend_from(fodder_bundle_vanilla, fodder_items_thematic) + +enchanter_items_thematic = [*enchanter_items_vanilla, purple_mushroom, solar_essence, + super_cucumber, void_essence, fire_quartz, frozen_tear, jade] +enchanter_bundle_thematic = BundleTemplate.extend_from(enchanter_bundle_vanilla, enchanter_items_thematic) + +bulletin_board_bundles_thematic = [chef_bundle_thematic, dye_bundle_thematic, field_research_bundle_thematic, fodder_bundle_thematic, enchanter_bundle_thematic] +bulletin_board_thematic = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_thematic, 5) + +# Abandoned Joja Mart +missing_bundle_items_thematic = [*missing_bundle_items_vanilla, pale_ale.as_quality(ArtisanQuality.silver), beer.as_quality(ArtisanQuality.silver), + mead.as_quality(ArtisanQuality.silver), + cheese.as_quality(ArtisanQuality.silver), goat_cheese.as_quality(ArtisanQuality.silver), void_mayo, cloth, green_tea, + truffle_oil, diamond, + sweet_gem_berry.as_quality_crop(), starfruit.as_quality_crop(), + tea_leaves.as_amount(5), lava_eel.as_quality(FishQuality.gold), scorpion_carp.as_quality(FishQuality.gold), + blobfish.as_quality(FishQuality.gold)] +missing_bundle_thematic = BundleTemplate.extend_from(missing_bundle_vanilla, missing_bundle_items_thematic) +abandoned_joja_mart_bundles_thematic = [missing_bundle_thematic] +abandoned_joja_mart_thematic = BundleRoomTemplate(CCRoom.abandoned_joja_mart, abandoned_joja_mart_bundles_thematic, 1) + +# Vault +vault_bundles_thematic = vault_bundles_vanilla +vault_thematic = BundleRoomTemplate(CCRoom.vault, vault_bundles_thematic, 4) diff --git a/worlds/stardew_valley/data/bundles_data/vanilla_bundles.py b/worlds/stardew_valley/data/bundles_data/vanilla_bundles.py new file mode 100644 index 000000000000..a30d9807640f --- /dev/null +++ b/worlds/stardew_valley/data/bundles_data/vanilla_bundles.py @@ -0,0 +1,167 @@ +from .bundle_items_data import * +from ...bundles.bundle import DeepBundleTemplate, BundleTemplate, MoneyBundleTemplate +from ...bundles.bundle_item import BundleItem +from ...bundles.bundle_room import BundleRoomTemplate +from ...content.vanilla.base import all_fruits, all_vegetables +from ...strings.artisan_good_names import ArtisanGood +from ...strings.bundle_names import CCRoom, BundleName +from ...strings.fish_names import Fish +from ...strings.forageable_names import all_edible_mushrooms + +# Giant Stump +from ...strings.quality_names import ArtisanQuality, FishQuality + +all_specific_jellies = [BundleItem(ArtisanGood.jelly, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits] +all_specific_pickles = [BundleItem(ArtisanGood.pickles, flavor=vegetable, source=BundleItem.Sources.content) for vegetable in all_vegetables] +all_specific_dried_fruits = [*[BundleItem(ArtisanGood.dried_fruit, flavor=fruit, source=BundleItem.Sources.content) for fruit in all_fruits], + BundleItem(ArtisanGood.raisins, source=BundleItem.Sources.content)] +all_specific_juices = [BundleItem(ArtisanGood.juice, flavor=vegetable, source=BundleItem.Sources.content) for vegetable in all_vegetables] + +raccoon_crab_pot_fish_items = [periwinkle.as_amount(5), snail.as_amount(5), crayfish.as_amount(5), mussel.as_amount(5), + oyster.as_amount(5), cockle.as_amount(5), clam.as_amount(5)] +raccoon_smoked_fish_items = [BundleItem(ArtisanGood.smoked_fish, flavor=fish) for fish in + [Fish.largemouth_bass, Fish.bream, Fish.bullhead, Fish.chub, Fish.ghostfish, Fish.flounder, Fish.shad, + Fish.rainbow_trout, Fish.tilapia, Fish.red_mullet, Fish.tuna, Fish.midnight_carp, Fish.salmon, Fish.perch]] + +raccoon_artisan_items = [*all_specific_jellies, *all_specific_pickles, *all_specific_dried_fruits, *all_specific_juices] +raccoon_fish_items_deep = [raccoon_crab_pot_fish_items, raccoon_smoked_fish_items] + +all_specific_dried_mushrooms = [BundleItem(ArtisanGood.dried_mushroom, flavor=mushroom, source=BundleItem.Sources.content) for mushroom in all_edible_mushrooms] +raccoon_food_items = [egg.as_amount(5), cave_carrot.as_amount(5), white_algae.as_amount(5)] + +raccoon_foraging_items = [moss.as_amount(10), rusty_spoon, trash.as_amount(5), slime.as_amount(99), bat_wing.as_amount(10), geode.as_amount(8), + frozen_geode.as_amount(5), magma_geode.as_amount(3), coral.as_amount(4), sea_urchin.as_amount(2), bug_meat.as_amount(10), + diamond, topaz.as_amount(3), ghostfish.as_amount(3)] + +raccoon_fish_bundle_vanilla = DeepBundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_fish, raccoon_fish_items_deep, 2, 2) +raccoon_artisan_bundle_vanilla = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_artisan, raccoon_artisan_items, 2, 2) +raccoon_food_items_vanilla = [all_specific_dried_mushrooms, raccoon_food_items] +raccoon_food_bundle_vanilla = DeepBundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_food, raccoon_food_items_vanilla, 2, 2) +raccoon_foraging_bundle_vanilla = BundleTemplate(CCRoom.raccoon_requests, BundleName.raccoon_foraging, raccoon_foraging_items, 2, 2) +giant_stump_bundles_vanilla = [raccoon_fish_bundle_vanilla, raccoon_artisan_bundle_vanilla, raccoon_food_bundle_vanilla, raccoon_foraging_bundle_vanilla] +giant_stump_vanilla = BundleRoomTemplate(CCRoom.raccoon_requests, giant_stump_bundles_vanilla, 8) + +# Crafts Room +spring_foraging_items_vanilla = [wild_horseradish, daffodil, leek, dandelion] +spring_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.spring_foraging, spring_foraging_items_vanilla, 4, 4) + +summer_foraging_items_vanilla = [grape, spice_berry, sweet_pea] +summer_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.summer_foraging, summer_foraging_items_vanilla, 3, 3) + +fall_foraging_items_vanilla = [common_mushroom, wild_plum, hazelnut, blackberry] +fall_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.fall_foraging, fall_foraging_items_vanilla, 4, 4) + +winter_foraging_items_vanilla = [winter_root, crystal_fruit, snow_yam, crocus] +winter_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.winter_foraging, winter_foraging_items_vanilla, 4, 4) + +construction_items_vanilla = [wood, stone, hardwood] +construction_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.construction, construction_items_vanilla, 4, 4) + +exotic_foraging_items_vanilla = [coconut, cactus_fruit, cave_carrot, red_mushroom, purple_mushroom, maple_syrup, oak_resin, pine_tar, morel] +exotic_foraging_bundle_vanilla = BundleTemplate(CCRoom.crafts_room, BundleName.exotic_foraging, exotic_foraging_items_vanilla, 9, 5) + +crafts_room_bundles_vanilla = [spring_foraging_bundle_vanilla, summer_foraging_bundle_vanilla, fall_foraging_bundle_vanilla, + winter_foraging_bundle_vanilla, construction_bundle_vanilla, exotic_foraging_bundle_vanilla] +crafts_room_vanilla = BundleRoomTemplate(CCRoom.crafts_room, crafts_room_bundles_vanilla, 6) + +# Pantry +spring_crops_items_vanilla = [parsnip, green_bean, cauliflower, potato] +spring_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.spring_crops, spring_crops_items_vanilla, 4, 4) + +summer_crops_items_vanilla = [tomato, hot_pepper, blueberry, melon] +summer_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.summer_crops, summer_crops_items_vanilla, 4, 4) + +fall_crops_items_vanilla = [corn, eggplant, pumpkin, yam] +fall_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.fall_crops, fall_crops_items_vanilla, 4, 4) + +quality_crops_items_vanilla = [item.as_quality_crop() for item in [parsnip, melon, pumpkin, corn]] +quality_crops_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.quality_crops, quality_crops_items_vanilla, 4, 3) + +animal_items_vanilla = [large_milk, large_brown_egg, large_egg, large_goat_milk, wool, duck_egg] +animal_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.animal, animal_items_vanilla, 6, 5) + +artisan_items_vanilla = [truffle_oil, cloth, goat_cheese, cheese, honey, jelly, apple, apricot, orange, peach, pomegranate, cherry] +artisan_bundle_vanilla = BundleTemplate(CCRoom.pantry, BundleName.artisan, artisan_items_vanilla, 12, 6) + +pantry_bundles_vanilla = [spring_crops_bundle_vanilla, summer_crops_bundle_vanilla, fall_crops_bundle_vanilla, + quality_crops_bundle_vanilla, animal_bundle_vanilla, artisan_bundle_vanilla] +pantry_vanilla = BundleRoomTemplate(CCRoom.pantry, pantry_bundles_vanilla, 6) + +# Fish Tank +river_fish_items_vanilla = [sunfish, catfish, shad, tiger_trout] +river_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.river_fish, river_fish_items_vanilla, 4, 4) + +lake_fish_items_vanilla = [largemouth_bass, carp, bullhead, sturgeon] +lake_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.lake_fish, lake_fish_items_vanilla, 4, 4) + +ocean_fish_items_vanilla = [sardine, tuna, red_snapper, tilapia] +ocean_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.ocean_fish, ocean_fish_items_vanilla, 4, 4) + +night_fish_items_vanilla = [walleye, bream, eel] +night_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.night_fish, night_fish_items_vanilla, 3, 3) + +crab_pot_items_vanilla = [lobster, crayfish, crab, cockle, mussel, shrimp, snail, periwinkle, oyster, clam] +crab_pot_trash_items = [trash, driftwood, soggy_newspaper, broken_cd, broken_glasses] +crab_pot_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.crab_pot, crab_pot_items_vanilla, 10, 5) + +specialty_fish_items_vanilla = [pufferfish, ghostfish, sandfish, woodskip] +specialty_fish_bundle_vanilla = BundleTemplate(CCRoom.fish_tank, BundleName.specialty_fish, specialty_fish_items_vanilla, 4, 4) + +fish_tank_bundles_vanilla = [river_fish_bundle_vanilla, lake_fish_bundle_vanilla, ocean_fish_bundle_vanilla, + night_fish_bundle_vanilla, crab_pot_bundle_vanilla, specialty_fish_bundle_vanilla] +fish_tank_vanilla = BundleRoomTemplate(CCRoom.fish_tank, fish_tank_bundles_vanilla, 6) + +# Boiler Room +blacksmith_items_vanilla = [copper_bar, iron_bar, gold_bar] +blacksmith_bundle_vanilla = BundleTemplate(CCRoom.boiler_room, BundleName.blacksmith, blacksmith_items_vanilla, 3, 3) + +geologist_items_vanilla = [quartz, earth_crystal, frozen_tear, fire_quartz] +geologist_bundle_vanilla = BundleTemplate(CCRoom.boiler_room, BundleName.geologist, geologist_items_vanilla, 4, 4) + +adventurer_items_vanilla = [slime, bat_wing, solar_essence, void_essence] +adventurer_bundle_vanilla = BundleTemplate(CCRoom.boiler_room, BundleName.adventurer, adventurer_items_vanilla, 4, 2) + +boiler_room_bundles_vanilla = [blacksmith_bundle_vanilla, geologist_bundle_vanilla, adventurer_bundle_vanilla] +boiler_room_vanilla = BundleRoomTemplate(CCRoom.boiler_room, boiler_room_bundles_vanilla, 3) + +# Bulletin Board +chef_items_vanilla = [maple_syrup, fiddlehead_fern, truffle, poppy, maki_roll, fried_egg] +chef_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.chef, chef_items_vanilla, 6, 6) + +dye_items_vanilla = [red_mushroom, sea_urchin, sunflower, duck_feather, aquamarine, red_cabbage] +dye_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.dye, dye_items_vanilla, 6, 6) + +field_research_items_vanilla = [purple_mushroom, nautilus_shell, chub, frozen_geode] +field_research_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.field_research, field_research_items_vanilla, 4, 4) + +fodder_items_vanilla = [wheat.as_amount(10), hay.as_amount(10), apple.as_amount(3)] +fodder_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.fodder, fodder_items_vanilla, 3, 3) + +enchanter_items_vanilla = [oak_resin, wine, rabbit_foot, pomegranate] +enchanter_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.enchanter, enchanter_items_vanilla, 4, 4) + +bulletin_board_bundles_vanilla = [chef_bundle_vanilla, dye_bundle_vanilla, field_research_bundle_vanilla, fodder_bundle_vanilla, enchanter_bundle_vanilla] +bulletin_board_vanilla = BundleRoomTemplate(CCRoom.bulletin_board, bulletin_board_bundles_vanilla, 5) + +# Abandoned Joja Mart +missing_bundle_items_vanilla = [wine.as_quality(ArtisanQuality.silver), dinosaur_mayo, prismatic_shard, caviar, + ancient_fruit.as_quality_crop(), void_salmon.as_quality(FishQuality.gold)] +missing_bundle_vanilla = BundleTemplate(CCRoom.abandoned_joja_mart, BundleName.missing_bundle, missing_bundle_items_vanilla, 6, 5) + +abandoned_joja_mart_bundles_vanilla = [missing_bundle_vanilla] +abandoned_joja_mart_vanilla = BundleRoomTemplate(CCRoom.abandoned_joja_mart, abandoned_joja_mart_bundles_vanilla, 1) + + +# Vault +vault_2500_gold = BundleItem.money_bundle(2500) +vault_5000_gold = BundleItem.money_bundle(5000) +vault_10000_gold = BundleItem.money_bundle(10000) +vault_25000_gold = BundleItem.money_bundle(25000) + +vault_2500_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_2500, vault_2500_gold) +vault_5000_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_5000, vault_5000_gold) +vault_10000_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_10000, vault_10000_gold) +vault_25000_bundle = MoneyBundleTemplate(CCRoom.vault, BundleName.money_25000, vault_25000_gold) + +vault_bundles_vanilla = [vault_2500_bundle, vault_5000_bundle, vault_10000_bundle, vault_25000_bundle] +vault_vanilla = BundleRoomTemplate(CCRoom.vault, vault_bundles_vanilla, 4) diff --git a/worlds/stardew_valley/data/craftable_data.py b/worlds/stardew_valley/data/craftable_data.py index 3dae67c2602c..ce883be3b1d5 100644 --- a/worlds/stardew_valley/data/craftable_data.py +++ b/worlds/stardew_valley/data/craftable_data.py @@ -1,7 +1,8 @@ -from typing import Dict, List, Optional +from typing import Dict, List from .recipe_source import RecipeSource, StarterSource, QueenOfSauceSource, ShopSource, SkillSource, FriendshipSource, ShopTradeSource, CutsceneSource, \ - ArchipelagoSource, LogicSource, SpecialOrderSource, FestivalShopSource, QuestSource, MasterySource, SkillCraftsanitySource + ArchipelagoSource, LogicSource, SpecialOrderSource, FestivalShopSource, QuestSource, MasterySource, SkillCraftsanitySource, ShopWithKnownRecipeSource +from ..content.content_packs import ginger_island_content_pack from ..mods.mod_data import ModNames from ..strings.animal_product_names import AnimalProduct from ..strings.artisan_good_names import ArtisanGood @@ -33,13 +34,13 @@ class CraftingRecipe: item: str ingredients: Dict[str, int] source: RecipeSource - mod_name: Optional[str] + content_pack: frozenset[str] | None - def __init__(self, item: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None): + def __init__(self, item: str, ingredients: Dict[str, int], source: RecipeSource, content_pack: frozenset[str] | None = None): self.item = item self.ingredients = ingredients self.source = source - self.mod_name = mod_name + self.content_pack = content_pack def __repr__(self): return f"{self.item} (Source: {self.source} |" \ @@ -49,9 +50,9 @@ def __repr__(self): all_crafting_recipes: List[CraftingRecipe] = [] -def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe: +def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = FriendshipSource(friend, hearts) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) def cutscene_recipe(name: str, region: str, friend: str, hearts: int, ingredients: Dict[str, int]) -> CraftingRecipe: @@ -59,24 +60,29 @@ def cutscene_recipe(name: str, region: str, friend: str, hearts: int, ingredient return create_recipe(name, ingredients, source) -def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe: +def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = SkillSource(skill, level) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def skill_craftsanity_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe: +def skill_craftsanity_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = SkillCraftsanitySource(skill, level) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def mastery_recipe(name: str, skill: str, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe: +def mastery_recipe(name: str, skill: str, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = MasterySource(skill) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe: +def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = ShopSource(region, price) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) + + +def shop_with_known_recipe_recipe(name: str, region: str, price: int, recipe_requirement: str, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: + source = ShopWithKnownRecipeSource(region, price, recipe_requirement) + return create_recipe(name, ingredients, source, content_pack) def festival_shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int]) -> CraftingRecipe: @@ -84,9 +90,9 @@ def festival_shop_recipe(name: str, region: str, price: int, ingredients: Dict[s return create_recipe(name, ingredients, source) -def shop_trade_recipe(name: str, region: str, currency: str, price: int, ingredients: Dict[str, int]) -> CraftingRecipe: +def shop_trade_recipe(name: str, region: str, currency: str, price: int, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = ShopTradeSource(region, currency, price) - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) def queen_of_sauce_recipe(name: str, year: int, season: str, day: int, ingredients: Dict[str, int]) -> CraftingRecipe: @@ -94,14 +100,14 @@ def queen_of_sauce_recipe(name: str, year: int, season: str, day: int, ingredien return create_recipe(name, ingredients, source) -def quest_recipe(name: str, quest: str, ingredients: Dict[str, int]) -> CraftingRecipe: +def quest_recipe(name: str, quest: str, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = QuestSource(quest) - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) -def special_order_recipe(name: str, special_order: str, ingredients: Dict[str, int]) -> CraftingRecipe: +def special_order_recipe(name: str, special_order: str, ingredients: Dict[str, int], content_pack: List[str] | str | None = None) -> CraftingRecipe: source = SpecialOrderSource(special_order) - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) def starter_recipe(name: str, ingredients: Dict[str, int]) -> CraftingRecipe: @@ -109,11 +115,11 @@ def starter_recipe(name: str, ingredients: Dict[str, int]) -> CraftingRecipe: return create_recipe(name, ingredients, source) -def ap_recipe(name: str, ingredients: Dict[str, int], ap_item: str = None) -> CraftingRecipe: +def ap_recipe(name: str, ingredients: Dict[str, int], ap_item: str = None, content_pack: List[str] | str | None = None) -> CraftingRecipe: if ap_item is None: ap_item = f"{name} Recipe" source = ArchipelagoSource(ap_item) - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) def cellar_recipe(name: str, ingredients: Dict[str, int]) -> CraftingRecipe: @@ -121,8 +127,12 @@ def cellar_recipe(name: str, ingredients: Dict[str, int]) -> CraftingRecipe: return create_recipe(name, ingredients, source) -def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None) -> CraftingRecipe: - recipe = CraftingRecipe(name, ingredients, source, mod_name) +def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, content_pack: List[str] | str | None = None) -> CraftingRecipe: + if content_pack is None: + content_pack = [] + if isinstance(content_pack, str): + content_pack = [content_pack] + recipe = CraftingRecipe(name, ingredients, source, frozenset(content_pack)) all_crafting_recipes.append(recipe) return recipe @@ -161,11 +171,11 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, basic_speed_gro = skill_recipe(SpeedGro.basic, Skill.farming, 3, {ArtisanGood.pine_tar: 1, Material.moss: 5}) deluxe_speed_gro = skill_recipe(SpeedGro.deluxe, Skill.farming, 8, {ArtisanGood.oak_resin: 1, Fossil.bone_fragment: 5}) -hyper_speed_gro = ap_recipe(SpeedGro.hyper, {Ore.radioactive: 1, Fossil.bone_fragment: 3, Loot.solar_essence: 1}) +hyper_speed_gro = ap_recipe(SpeedGro.hyper, {Ore.radioactive: 1, Fossil.bone_fragment: 3, Loot.solar_essence: 1}, content_pack=ginger_island_content_pack.name) basic_retaining_soil = skill_recipe(RetainingSoil.basic, Skill.farming, 4, {Material.stone: 2}) quality_retaining_soil = skill_recipe(RetainingSoil.quality, Skill.farming, 7, {Material.stone: 3, Material.clay: 1}) deluxe_retaining_soil = shop_trade_recipe(RetainingSoil.deluxe, Region.island_trader, Currency.cinder_shard, 50, - {Material.stone: 5, Material.fiber: 3, Material.clay: 1}) + {Material.stone: 5, Material.fiber: 3, Material.clay: 1}, content_pack=ginger_island_content_pack.name) tree_fertilizer = skill_recipe(Fertilizer.tree, Skill.foraging, 7, {Material.fiber: 5, Material.stone: 5}) spring_seeds = skill_recipe(WildSeeds.spring, Skill.foraging, 1, @@ -176,7 +186,7 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, {Forageable.winter_root: 1, Forageable.crystal_fruit: 1, Forageable.snow_yam: 1, Forageable.crocus: 1}) ancient_seeds = ap_recipe(WildSeeds.ancient, {Artifact.ancient_seed: 1}) grass_starter = shop_recipe(WildSeeds.grass_starter, Region.pierre_store, 1000, {Material.fiber: 10}) -blue_grass_starter = ap_recipe(WildSeeds.blue_grass_starter, {Material.fiber: 25, Material.moss: 10, ArtisanGood.mystic_syrup: 1}) +blue_grass_starter = ap_recipe(WildSeeds.blue_grass_starter, {Material.fiber: 25, Material.moss: 10, ArtisanGood.mystic_syrup: 1}, content_pack=ginger_island_content_pack.name) for wild_seeds in [WildSeeds.spring, WildSeeds.summer, WildSeeds.fall, WildSeeds.winter]: tea_sapling = cutscene_recipe(WildSeeds.tea_sapling, Region.sunroom, NPC.caroline, 2, {wild_seeds: 2, Material.fiber: 5, Material.wood: 5}) fiber_seeds = special_order_recipe(WildSeeds.fiber, SpecialOrder.community_cleanup, {Seed.mixed: 1, Material.sap: 5, Material.clay: 1}) @@ -207,13 +217,13 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, bait = skill_recipe(Fishing.bait, Skill.fishing, 2, {Loot.bug_meat: 1}) deluxe_bait = skill_recipe(Fishing.deluxe_bait, Skill.fishing, 4, {Fishing.bait: 5, Material.moss: 2}) wild_bait = cutscene_recipe(Fishing.wild_bait, Region.tent, NPC.linus, 4, {Material.fiber: 10, Loot.bug_meat: 5, Loot.slime: 5}) -magic_bait = ap_recipe(Fishing.magic_bait, {Ore.radioactive: 1, Loot.bug_meat: 3}) +magic_bait = ap_recipe(Fishing.magic_bait, {Ore.radioactive: 1, Loot.bug_meat: 3}, content_pack=ginger_island_content_pack.name) crab_pot = skill_recipe(Machine.crab_pot, Skill.fishing, 3, {Material.wood: 40, MetalBar.iron: 3}) sturdy_ring = skill_recipe(Ring.sturdy_ring, Skill.combat, 1, {MetalBar.copper: 2, Loot.bug_meat: 25, Loot.slime: 25}) warrior_ring = skill_recipe(Ring.warrior_ring, Skill.combat, 4, {MetalBar.iron: 10, Material.coal: 25, Mineral.frozen_tear: 10}) ring_of_yoba = skill_recipe(Ring.ring_of_yoba, Skill.combat, 7, {MetalBar.gold: 5, MetalBar.iron: 5, Mineral.diamond: 1}) -thorns_ring = skill_recipe(Ring.thorns_ring, Skill.combat, 7, {Fossil.bone_fragment: 50, Material.stone: 50, MetalBar.gold: 1}) +thorns_ring = skill_recipe(Ring.thorns_ring, Skill.combat, 7, {Fossil.bone_fragment: 50, Material.stone: 50, MetalBar.gold: 1}, content_pack=ginger_island_content_pack.name) glowstone_ring = skill_recipe(Ring.glowstone_ring, Skill.mining, 4, {Loot.solar_essence: 5, MetalBar.iron: 5}) iridium_band = skill_recipe(Ring.iridium_band, Skill.combat, 9, {MetalBar.iridium: 5, Loot.solar_essence: 50, Loot.void_essence: 50}) wedding_ring = shop_recipe(Ring.wedding_ring, LogicRegion.traveling_cart, 500, {MetalBar.iridium: 5, Mineral.prismatic_shard: 1}) @@ -224,14 +234,14 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, oil_of_garlic = skill_recipe(Edible.oil_of_garlic, Skill.combat, 6, {Vegetable.garlic: 10, Ingredient.oil: 1}) monster_musk = special_order_recipe(Consumable.monster_musk, SpecialOrder.prismatic_jelly, {Loot.bat_wing: 30, Loot.slime: 30}) -fairy_dust = quest_recipe(Consumable.fairy_dust, Quest.the_pirates_wife, {Mineral.diamond: 1, Flower.fairy_rose: 1}) +fairy_dust = quest_recipe(Consumable.fairy_dust, Quest.the_pirates_wife, {Mineral.diamond: 1, Flower.fairy_rose: 1}, content_pack=ginger_island_content_pack.name) warp_totem_beach = skill_recipe(Consumable.warp_totem_beach, Skill.foraging, 6, {Material.hardwood: 1, WaterItem.coral: 2, Material.fiber: 10}) warp_totem_mountains = skill_recipe(Consumable.warp_totem_mountains, Skill.foraging, 7, {Material.hardwood: 1, MetalBar.iron: 1, Material.stone: 25}) warp_totem_farm = skill_recipe(Consumable.warp_totem_farm, Skill.foraging, 8, {Material.hardwood: 1, ArtisanGood.honey: 1, Material.fiber: 20}) warp_totem_desert = shop_trade_recipe(Consumable.warp_totem_desert, Region.desert, MetalBar.iridium, 10, {Material.hardwood: 2, Forageable.coconut: 1, Ore.iridium: 4}) warp_totem_island = shop_recipe(Consumable.warp_totem_island, Region.volcano_dwarf_shop, 10000, - {Material.hardwood: 5, Forageable.dragon_tooth: 1, Forageable.ginger: 1}) + {Material.hardwood: 5, Forageable.dragon_tooth: 1, Forageable.ginger: 1}, content_pack=ginger_island_content_pack.name) rain_totem = skill_recipe(Consumable.rain_totem, Skill.foraging, 9, {Material.hardwood: 1, ArtisanGood.truffle_oil: 1, ArtisanGood.pine_tar: 5}) torch = starter_recipe(Lighting.torch, {Material.wood: 1, Material.sap: 2}) @@ -259,14 +269,14 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, furnace = skill_craftsanity_recipe(Machine.furnace, Skill.mining, 1, {Ore.copper: 20, Material.stone: 25}) geode_crusher = special_order_recipe(Machine.geode_crusher, SpecialOrder.cave_patrol, {MetalBar.gold: 2, Material.stone: 50, Mineral.diamond: 1}) mushroom_log = skill_recipe(Machine.mushroom_log, Skill.foraging, 4, {Material.hardwood: 10, Material.moss: 10}) -heavy_tapper = ap_recipe(Machine.heavy_tapper, {Material.hardwood: 30, MetalBar.radioactive: 1}) +heavy_tapper = ap_recipe(Machine.heavy_tapper, {Material.hardwood: 30, MetalBar.radioactive: 1}, content_pack=ginger_island_content_pack.name) lightning_rod = skill_recipe(Machine.lightning_rod, Skill.foraging, 6, {MetalBar.iron: 1, MetalBar.quartz: 1, Loot.bat_wing: 5}) -ostrich_incubator = ap_recipe(Machine.ostrich_incubator, {Fossil.bone_fragment: 50, Material.hardwood: 50, Currency.cinder_shard: 20}) +ostrich_incubator = ap_recipe(Machine.ostrich_incubator, {Fossil.bone_fragment: 50, Material.hardwood: 50, Currency.cinder_shard: 20}, content_pack=ginger_island_content_pack.name) recycling_machine = skill_recipe(Machine.recycling_machine, Skill.fishing, 4, {Material.wood: 25, Material.stone: 25, MetalBar.iron: 1}) seed_maker = skill_recipe(Machine.seed_maker, Skill.farming, 9, {Material.wood: 25, Material.coal: 10, MetalBar.gold: 1}) slime_egg_press = skill_recipe(Machine.slime_egg_press, Skill.combat, 6, {Material.coal: 25, Mineral.fire_quartz: 1, ArtisanGood.battery_pack: 1}) slime_incubator = skill_recipe(Machine.slime_incubator, Skill.combat, 8, {MetalBar.iridium: 2, Loot.slime: 100}) -solar_panel = special_order_recipe(Machine.solar_panel, SpecialOrder.island_ingredients, {MetalBar.quartz: 10, MetalBar.iron: 5, MetalBar.gold: 5}) +solar_panel = special_order_recipe(Machine.solar_panel, SpecialOrder.island_ingredients, {MetalBar.quartz: 10, MetalBar.iron: 5, MetalBar.gold: 5})#, content_pack=ginger_island_content_pack.name) # If set this as a ginger island only recipe, the rule for battery packs will fail. It does OR on lightning rod and solar panel, even when GI is off tapper = skill_recipe(Machine.tapper, Skill.foraging, 4, {Material.wood: 40, MetalBar.copper: 2}) @@ -282,7 +292,7 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, chest = starter_recipe(Storage.chest, {Material.wood: 50}) stone_chest = special_order_recipe(Storage.stone_chest, SpecialOrder.robins_resource_rush, {Material.stone: 50}) big_chest = shop_recipe(Storage.big_chest, Region.carpenter, 5000, {Material.wood: 120, MetalBar.copper: 2}) -big_stone_chest = shop_recipe(Storage.big_stone_chest, LogicRegion.mines_dwarf_shop, 5000, {Material.stone: 250}) +big_stone_chest = shop_with_known_recipe_recipe(Storage.big_stone_chest, LogicRegion.mines_dwarf_shop, 5000, Storage.stone_chest, {Material.stone: 250}) wood_sign = starter_recipe(Sign.wood, {Material.wood: 25}) stone_sign = starter_recipe(Sign.stone, {Material.stone: 25}) @@ -300,7 +310,7 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, mini_obelisk = special_order_recipe(Craftable.mini_obelisk, SpecialOrder.a_curious_substance, {Material.hardwood: 30, Loot.solar_essence: 20, MetalBar.gold: 3}) farm_computer = special_order_recipe(Craftable.farm_computer, SpecialOrder.aquatic_overpopulation, {Artifact.dwarf_gadget: 1, ArtisanGood.battery_pack: 1, MetalBar.quartz: 10}) -hopper = ap_recipe(Craftable.hopper, {Material.hardwood: 10, MetalBar.iridium: 1, MetalBar.radioactive: 1}) +hopper = ap_recipe(Craftable.hopper, {Material.hardwood: 10, MetalBar.iridium: 1, MetalBar.radioactive: 1}, content_pack=ginger_island_content_pack.name) cookout_kit = skill_recipe(Craftable.cookout_kit, Skill.foraging, 3, {Material.wood: 15, Material.fiber: 10, Material.coal: 3}) tent_kit = skill_recipe(Craftable.tent_kit, Skill.foraging, 8, {Material.hardwood: 10, Material.fiber: 25, ArtisanGood.cloth: 1}) @@ -312,81 +322,81 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, treasure_totem = mastery_recipe(Consumable.treasure_totem, Skill.foraging, {Material.hardwood: 5, ArtisanGood.mystic_syrup: 1, Material.moss: 10}) challenge_bait = mastery_recipe(Fishing.challenge_bait, Skill.fishing, {Fossil.bone_fragment: 5, Material.moss: 2}) anvil = mastery_recipe(Machine.anvil, Skill.combat, {MetalBar.iron: 50}) -mini_forge = mastery_recipe(Machine.mini_forge, Skill.combat, {Forageable.dragon_tooth: 5, MetalBar.iron: 10, MetalBar.gold: 10, MetalBar.iridium: 5}) +mini_forge = mastery_recipe(Machine.mini_forge, Skill.combat, {Forageable.dragon_tooth: 5, MetalBar.iron: 10, MetalBar.gold: 10, MetalBar.iridium: 5}, content_pack=ginger_island_content_pack.name) -travel_charm = shop_recipe(ModCraftable.travel_core, Region.adventurer_guild, 250, {Loot.solar_essence: 1, Loot.void_essence: 1}, ModNames.magic) +travel_charm = shop_recipe(ModCraftable.travel_core, Region.adventurer_guild, 250, {Loot.solar_essence: 1, Loot.void_essence: 1}, content_pack=ModNames.magic) preservation_chamber = skill_recipe(ModMachine.preservation_chamber, ModSkill.archaeology, 1, - {MetalBar.copper: 1, Material.wood: 15, ArtisanGood.oak_resin: 30}, + {MetalBar.copper: 1, Material.wood: 15, ArtisanGood.oak_resin: 10}, ModNames.archaeology) -restoration_table = skill_recipe(ModMachine.restoration_table, ModSkill.archaeology, 1, {Material.wood: 15, MetalBar.copper: 1, MetalBar.iron: 1}, +restoration_table = skill_recipe(ModMachine.restoration_table, ModSkill.archaeology, 1, {Material.wood: 25, MetalBar.quartz: 1, MetalBar.iron: 1}, ModNames.archaeology) preservation_chamber_h = skill_recipe(ModMachine.hardwood_preservation_chamber, ModSkill.archaeology, 6, {MetalBar.copper: 1, Material.hardwood: 15, - ArtisanGood.oak_resin: 30}, ModNames.archaeology) -grinder = skill_recipe(ModMachine.grinder, ModSkill.archaeology, 2, {Artifact.rusty_cog: 10, MetalBar.iron: 5, ArtisanGood.battery_pack: 1}, + ArtisanGood.oak_resin: 10}, content_pack=ModNames.archaeology) +grinder = skill_recipe(ModMachine.grinder, ModSkill.archaeology, 2, {Artifact.rusty_cog: 4, MetalBar.iron: 5, ArtisanGood.battery_pack: 1}, ModNames.archaeology) -ancient_battery = skill_recipe(ModMachine.ancient_battery, ModSkill.archaeology, 7, {Material.stone: 40, MetalBar.copper: 10, MetalBar.iron: 5}, +ancient_battery = skill_recipe(ModMachine.ancient_battery, ModSkill.archaeology, 7, {Material.stone: 40, Material.clay: 10, MetalBar.iron: 5}, ModNames.archaeology) -glass_bazier = skill_recipe(ModCraftable.glass_brazier, ModSkill.archaeology, 4, {Artifact.glass_shards: 10}, ModNames.archaeology) -glass_path = skill_recipe(ModFloor.glass_path, ModSkill.archaeology, 3, {Artifact.glass_shards: 1}, ModNames.archaeology) -glass_fence = skill_recipe(ModCraftable.glass_fence, ModSkill.archaeology, 7, {Artifact.glass_shards: 5}, ModNames.archaeology) -bone_path = skill_recipe(ModFloor.bone_path, ModSkill.archaeology, 4, {Fossil.bone_fragment: 1}, ModNames.archaeology) -rust_path = skill_recipe(ModFloor.rusty_path, ModSkill.archaeology, 2, {ModTrash.rusty_scrap: 2}, ModNames.archaeology) +glass_brazier = skill_recipe(ModCraftable.glass_brazier, ModSkill.archaeology, 4, {Artifact.glass_shards: 10, Material.coal: 1, Material.fiber: 1}, content_pack=ModNames.archaeology) +glass_path = skill_recipe(ModFloor.glass_path, ModSkill.archaeology, 3, {Artifact.glass_shards: 2}, content_pack=ModNames.archaeology) +glass_fence = skill_recipe(ModCraftable.glass_fence, ModSkill.archaeology, 7, {Artifact.glass_shards: 2}, content_pack=ModNames.archaeology) +bone_path = skill_recipe(ModFloor.bone_path, ModSkill.archaeology, 4, {Fossil.bone_fragment: 2}, content_pack=ModNames.archaeology) +rust_path = skill_recipe(ModFloor.rusty_path, ModSkill.archaeology, 2, {ModTrash.rusty_scrap: 2}, content_pack=ModNames.archaeology) rusty_brazier = skill_recipe(ModCraftable.rusty_brazier, ModSkill.archaeology, 3, {ModTrash.rusty_scrap: 10, Material.coal: 1, Material.fiber: 1}, ModNames.archaeology) -bone_fence = skill_recipe(ModCraftable.bone_fence, ModSkill.archaeology, 8, {Fossil.bone_fragment: 2}, ModNames.archaeology) -water_shifter = skill_recipe(ModCraftable.water_shifter, ModSkill.archaeology, 4, {Material.wood: 40, MetalBar.copper: 4}, ModNames.archaeology) -wooden_display = skill_recipe(ModCraftable.wooden_display, ModSkill.archaeology, 1, {Material.wood: 25}, ModNames.archaeology) -hardwood_display = skill_recipe(ModCraftable.hardwood_display, ModSkill.archaeology, 7, {Material.hardwood: 10}, ModNames.archaeology) +bone_fence = skill_recipe(ModCraftable.bone_fence, ModSkill.archaeology, 8, {Fossil.bone_fragment: 2}, content_pack=ModNames.archaeology) +water_sifter = skill_recipe(ModCraftable.water_sifter, ModSkill.archaeology, 8, {MetalBar.copper: 4, Material.fiber: 8}, content_pack=ModNames.archaeology) +wooden_display = skill_recipe(ModCraftable.wooden_display, ModSkill.archaeology, 1, {Material.wood: 25}, content_pack=ModNames.archaeology) +hardwood_display = skill_recipe(ModCraftable.hardwood_display, ModSkill.archaeology, 6, {Material.hardwood: 10}, content_pack=ModNames.archaeology) lucky_ring = skill_recipe(Ring.lucky_ring, ModSkill.archaeology, 8, {Artifact.elvish_jewelry: 1, AnimalProduct.rabbit_foot: 5, Mineral.tigerseye: 1}, ModNames.archaeology) volcano_totem = skill_recipe(ModConsumable.volcano_totem, ModSkill.archaeology, 9, {Material.cinder_shard: 5, Artifact.rare_disc: 1, Artifact.dwarf_gadget: 1}, ModNames.archaeology) haste_elixir = shop_recipe(ModEdible.haste_elixir, SVERegion.alesia_shop, 35000, {Loot.void_essence: 35, ModLoot.void_soul: 5, Ingredient.sugar: 1, - Meal.spicy_eel: 1}, ModNames.sve) + Meal.spicy_eel: 1}, content_pack=ModNames.sve) hero_elixir = shop_recipe(ModEdible.hero_elixir, SVERegion.isaac_shop, 65000, {ModLoot.void_pebble: 3, ModLoot.void_soul: 5, Ingredient.oil: 1, - Loot.slime: 10}, ModNames.sve) + Loot.slime: 10}, content_pack=ModNames.sve) armor_elixir = shop_recipe(ModEdible.armor_elixir, SVERegion.alesia_shop, 50000, {Loot.solar_essence: 30, ModLoot.void_soul: 5, Ingredient.vinegar: 5, - Fossil.bone_fragment: 5}, ModNames.sve) + Fossil.bone_fragment: 5}, content_pack=ModNames.sve) ginger_tincture = friendship_recipe(ModConsumable.ginger_tincture, ModNPC.goblin, 4, {DistantLandsForageable.brown_amanita: 1, Forageable.ginger: 5, Material.cinder_shard: 1, DistantLandsForageable.swamp_herb: 1}, - ModNames.distant_lands) + content_pack=[ModNames.distant_lands, ginger_island_content_pack.name]) neanderthal_skeleton = shop_recipe(ModCraftable.neanderthal_skeleton, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.neanderthal_skull: 1, ModFossil.neanderthal_ribs: 1, ModFossil.neanderthal_pelvis: 1, ModFossil.neanderthal_limb_bones: 1, - MetalBar.iron: 5, Material.hardwood: 10}, ModNames.boarding_house) + MetalBar.iron: 5, Material.hardwood: 10}, content_pack=ModNames.boarding_house) pterodactyl_skeleton_l = shop_recipe(ModCraftable.pterodactyl_skeleton_l, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.pterodactyl_phalange: 1, ModFossil.pterodactyl_skull: 1, ModFossil.pterodactyl_l_wing_bone: 1, - MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house) + MetalBar.iron: 10, Material.hardwood: 15}, content_pack=ModNames.boarding_house) pterodactyl_skeleton_m = shop_recipe(ModCraftable.pterodactyl_skeleton_m, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.pterodactyl_phalange: 1, ModFossil.pterodactyl_vertebra: 1, ModFossil.pterodactyl_ribs: 1, - MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house) + MetalBar.iron: 10, Material.hardwood: 15}, content_pack=ModNames.boarding_house) pterodactyl_skeleton_r = shop_recipe(ModCraftable.pterodactyl_skeleton_r, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.pterodactyl_phalange: 1, ModFossil.pterodactyl_claw: 1, ModFossil.pterodactyl_r_wing_bone: 1, - MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house) + MetalBar.iron: 10, Material.hardwood: 15}, content_pack=ModNames.boarding_house) trex_skeleton_l = shop_recipe(ModCraftable.trex_skeleton_l, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.dinosaur_vertebra: 1, ModFossil.dinosaur_tooth: 1, ModFossil.dinosaur_skull: 1, - MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house) + MetalBar.iron: 10, Material.hardwood: 15}, content_pack=ModNames.boarding_house) trex_skeleton_m = shop_recipe(ModCraftable.trex_skeleton_m, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.dinosaur_vertebra: 1, ModFossil.dinosaur_ribs: 1, ModFossil.dinosaur_claw: 1, - MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house) + MetalBar.iron: 10, Material.hardwood: 15}, content_pack=ModNames.boarding_house) trex_skeleton_r = shop_recipe(ModCraftable.trex_skeleton_r, LogicRegion.mines_dwarf_shop, 5000, {ModFossil.dinosaur_vertebra: 1, ModFossil.dinosaur_femur: 1, ModFossil.dinosaur_pelvis: 1, - MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house) + MetalBar.iron: 10, Material.hardwood: 15}, content_pack=ModNames.boarding_house) -bouquet = skill_recipe(Gift.bouquet, ModSkill.socializing, 3, {Flower.tulip: 3}, ModNames.socializing_skill) -trash_bin = skill_recipe(ModMachine.trash_bin, ModSkill.binning, 2, {Material.stone: 30, MetalBar.iron: 2}, ModNames.binning_skill) -composter = skill_recipe(ModMachine.composter, ModSkill.binning, 4, {Material.wood: 70, Material.sap: 20, Material.fiber: 30}, ModNames.binning_skill) -recycling_bin = skill_recipe(ModMachine.recycling_bin, ModSkill.binning, 7, {MetalBar.iron: 3, Material.fiber: 10, MetalBar.gold: 2}, ModNames.binning_skill) +bouquet = skill_recipe(Gift.bouquet, ModSkill.socializing, 3, {Flower.tulip: 3}, content_pack=ModNames.socializing_skill) +trash_bin = skill_recipe(ModMachine.trash_bin, ModSkill.binning, 2, {Material.stone: 30, MetalBar.iron: 2}, content_pack=ModNames.binning_skill) +composter = skill_recipe(ModMachine.composter, ModSkill.binning, 4, {Material.wood: 70, Material.sap: 20, Material.fiber: 30}, content_pack=ModNames.binning_skill) +recycling_bin = skill_recipe(ModMachine.recycling_bin, ModSkill.binning, 7, {MetalBar.iron: 3, Material.fiber: 10, MetalBar.gold: 2}, content_pack=ModNames.binning_skill) advanced_recycling_machine = skill_recipe(ModMachine.advanced_recycling_machine, ModSkill.binning, 9, - {MetalBar.iridium: 5, ArtisanGood.battery_pack: 2, MetalBar.quartz: 10}, ModNames.binning_skill) + {MetalBar.iridium: 5, ArtisanGood.battery_pack: 2, MetalBar.quartz: 10}, content_pack=ModNames.binning_skill) coppper_slot_machine = skill_recipe(ModMachine.copper_slot_machine, ModSkill.luck, 2, {MetalBar.copper: 15, Material.stone: 1, Material.wood: 1, Material.fiber: 1, Material.sap: 1, Loot.slime: 1, - Forageable.salmonberry: 1, Material.clay: 1, Trash.joja_cola: 1}, ModNames.luck_skill) + Forageable.salmonberry: 1, Material.clay: 1, Trash.joja_cola: 1}, content_pack=ModNames.luck_skill) -gold_slot_machine = skill_recipe(ModMachine.gold_slot_machine, ModSkill.luck, 4, {MetalBar.gold: 15, ModMachine.copper_slot_machine: 1}, ModNames.luck_skill) -iridium_slot_machine = skill_recipe(ModMachine.iridium_slot_machine, ModSkill.luck, 6, {MetalBar.iridium: 15, ModMachine.gold_slot_machine: 1}, ModNames.luck_skill) -radioactive_slot_machine = skill_recipe(ModMachine.radioactive_slot_machine, ModSkill.luck, 8, {MetalBar.radioactive: 15, ModMachine.iridium_slot_machine: 1}, ModNames.luck_skill) +gold_slot_machine = skill_recipe(ModMachine.gold_slot_machine, ModSkill.luck, 4, {MetalBar.gold: 15, ModMachine.copper_slot_machine: 1}, content_pack=ModNames.luck_skill) +iridium_slot_machine = skill_recipe(ModMachine.iridium_slot_machine, ModSkill.luck, 6, {MetalBar.iridium: 15, ModMachine.gold_slot_machine: 1}, content_pack=ModNames.luck_skill) +radioactive_slot_machine = skill_recipe(ModMachine.radioactive_slot_machine, ModSkill.luck, 8, {MetalBar.radioactive: 15, ModMachine.iridium_slot_machine: 1}, content_pack=ModNames.luck_skill) all_crafting_recipes_by_name = {recipe.item: recipe for recipe in all_crafting_recipes} diff --git a/worlds/stardew_valley/data/fish_data.py b/worlds/stardew_valley/data/fish_data.py index dfa8891077ee..d263837b4357 100644 --- a/worlds/stardew_valley/data/fish_data.py +++ b/worlds/stardew_valley/data/fish_data.py @@ -2,9 +2,18 @@ from typing import Tuple, Union, Optional from . import season_data as season +from .game_item import Source from ..mods.mod_data import ModNames from ..strings.fish_names import Fish, SVEFish, DistantLandsFish from ..strings.region_names import Region, SVERegion, LogicRegion +from ..strings.tool_names import FishingRod + + +@dataclass(frozen=True, kw_only=True) +class FishingSource(Source): + region: str + minimum_rod: str = FishingRod.training + fishing_level: int = 0 @dataclass(frozen=True) @@ -15,6 +24,7 @@ class FishItem: difficulty: int legendary: bool extended_family: bool + minimum_level: int mod_name: Optional[str] = None def __repr__(self): @@ -55,11 +65,11 @@ def __repr__(self): def create_fish(name: str, locations: Tuple[str, ...], seasons: Union[str, Tuple[str, ...]], - difficulty: int, legendary: bool = False, extended_family: bool = False, mod_name: Optional[str] = None) -> FishItem: + difficulty: int, legendary: bool = False, extended_family: bool = False, minimum_level: int = 0, mod_name: Optional[str] = None) -> FishItem: if isinstance(seasons, str): seasons = (seasons,) - fish_item = FishItem(name, locations, seasons, difficulty, legendary, extended_family, mod_name) + fish_item = FishItem(name, locations, seasons, difficulty, legendary, extended_family, minimum_level, mod_name) return fish_item @@ -118,16 +128,16 @@ def create_fish(name: str, locations: Tuple[str, ...], seasons: Union[str, Tuple midnight_squid = create_fish(Fish.midnight_squid, night_market, season.winter, 55) spook_fish = create_fish(Fish.spook_fish, night_market, season.winter, 60) -angler = create_fish(Fish.angler, town_river, season.fall, 85, True, False) -crimsonfish = create_fish(Fish.crimsonfish, tide_pools, season.summer, 95, True, False) -glacierfish = create_fish(Fish.glacierfish, forest_river, season.winter, 100, True, False) -legend = create_fish(Fish.legend, mountain_lake, season.spring, 110, True, False) +angler = create_fish(Fish.angler, town_river, season.fall, 85, True, False, minimum_level=3) +crimsonfish = create_fish(Fish.crimsonfish, tide_pools, season.summer, 95, True, False, minimum_level=5) +glacierfish = create_fish(Fish.glacierfish, forest_river, season.winter, 100, True, False, minimum_level=6) +legend = create_fish(Fish.legend, mountain_lake, season.spring, 110, True, False, minimum_level=10) mutant_carp = create_fish(Fish.mutant_carp, sewers, season.all_seasons, 80, True, False) -ms_angler = create_fish(Fish.ms_angler, town_river, season.fall, 85, True, True) -son_of_crimsonfish = create_fish(Fish.son_of_crimsonfish, tide_pools, season.summer, 95, True, True) -glacierfish_jr = create_fish(Fish.glacierfish_jr, forest_river, season.winter, 100, True, True) -legend_ii = create_fish(Fish.legend_ii, mountain_lake, season.spring, 110, True, True) +ms_angler = create_fish(Fish.ms_angler, town_river, season.fall, 85, True, True, minimum_level=3) +son_of_crimsonfish = create_fish(Fish.son_of_crimsonfish, tide_pools, season.summer, 95, True, True, minimum_level=5) +glacierfish_jr = create_fish(Fish.glacierfish_jr, forest_river, season.winter, 100, True, True, minimum_level=6) +legend_ii = create_fish(Fish.legend_ii, mountain_lake, season.spring, 110, True, True, minimum_level=10) radioactive_carp = create_fish(Fish.radioactive_carp, sewers, season.all_seasons, 80, True, True) baby_lunaloo = create_fish(SVEFish.baby_lunaloo, ginger_island_ocean, season.all_seasons, 15, mod_name=ModNames.sve) diff --git a/worlds/stardew_valley/data/fish_pond_data.py b/worlds/stardew_valley/data/fish_pond_data.py new file mode 100644 index 000000000000..0afaac5ce775 --- /dev/null +++ b/worlds/stardew_valley/data/fish_pond_data.py @@ -0,0 +1,50 @@ +from typing import Dict + +from ..strings.animal_product_names import AnimalProduct +from ..strings.artisan_good_names import ArtisanGood +from ..strings.crop_names import Fruit +from ..strings.fish_names import Fish, WaterItem +from ..strings.food_names import Meal +from ..strings.forageable_names import Forageable +from ..strings.metal_names import Mineral, Ore +from ..strings.monster_drop_names import Loot +from ..strings.seed_names import Seed + +# Some of these are commented out, because they shouldn't be used, because they cause a loop on themselves, even if the loop is one of many ways to complete the quest +# I don't know the correct architectural way to fix this. So in the meantime, obtaining these items from fish ponds is not in logic + +# Dictionary of fish pond requests, in the format of Dict[fish_name, Dict[population, Dict[item_name, item_amount]]] +fish_pond_quests: Dict[str, Dict[int, Dict[str, int]]] = { + Fish.blobfish: { + 1: {WaterItem.coral: 3, Mineral.frozen_tear: 2, WaterItem.sea_urchin: 2, }, + 3: {Seed.coffee: 5, ArtisanGood.mayonnaise: 1, Meal.pizza: 1, }, + 5: {Meal.cookie: 1, ArtisanGood.green_tea: 1, ArtisanGood.wine: 1, }, + 7: {Forageable.rainbow_shell: 1, Meal.rice_pudding: 1, }, + }, + # Fish.lava_eel: { + # 1: {Mineral.fire_quartz: 3, }, + # 3: {"Basalt": 1, Mineral.diamond: 2, Artifact.dwarf_scroll_iii: 1, }, + # 5: {Bomb.mega_bomb: 2, }, + # 7: {MetalBar.iridium: 1, }, + # }, + Fish.lionfish: { + 3: {Forageable.ginger: 3, Fruit.pineapple: 1, }, + 5: {Fruit.mango: 1, }, + }, + # Fish.octopus: { + # 3: {WaterItem.coral: 3, ArtisanGood.honey: 1, Fish.oyster: 1, MetalBar.quartz: 3, }, + # 5: {Fossil.dried_starfish: 1, Mineral.emerald: 2, Geode.omni: 2, Mushroom.purple: 2, }, + # 7: {ArtisanGood.green_tea: 1, }, + # }, + # Fish.super_cucumber: { + # 3: {WaterItem.coral: 3, ArtisanGood.honey: 1, Fish.oyster: 1, Trash.driftwood: 3, MetalBar.quartz: 3, }, + # 5: {Fossil.dried_starfish: 1, Mineral.emerald: 2, Geode.omni: 2, Mushroom.purple: 2 }, + # 7: {Mineral.diamond: 1, MetalBar.gold: 3, Ore.iridium: 1, ArtisanGood.jelly: 1, ArtisanGood.pickles: 1, WaterItem.sea_urchin: 2 }, + # }, + Fish.void_salmon: { + 1: {Loot.void_essence: 5, }, + 3: {Loot.bat_wing: 10, }, + 5: {Mineral.diamond: 1, AnimalProduct.void_egg: 1, }, + 7: {Ore.iridium: 1, }, + }, +} diff --git a/worlds/stardew_valley/data/game_item.py b/worlds/stardew_valley/data/game_item.py index 6c23f59f2bc0..a6fc6077b45c 100644 --- a/worlds/stardew_valley/data/game_item.py +++ b/worlds/stardew_valley/data/game_item.py @@ -2,10 +2,13 @@ from abc import ABC from dataclasses import dataclass, field from types import MappingProxyType -from typing import List, Iterable, Set, ClassVar, Tuple, Mapping, Callable, Any +from typing import Iterable, ClassVar, Mapping, Callable, TYPE_CHECKING from ..stardew_rule.protocol import StardewRule +if TYPE_CHECKING: + from ..logic.logic import StardewLogic + DEFAULT_REQUIREMENT_TAGS = MappingProxyType({}) @@ -24,34 +27,47 @@ class ItemTag(enum.Enum): BOOK = enum.auto() BOOK_POWER = enum.auto() BOOK_SKILL = enum.auto() + HAT = enum.auto() + FORAGE = enum.auto() + COOKING = enum.auto() @dataclass(frozen=True) class Source(ABC): - add_tags: ClassVar[Tuple[ItemTag]] = () + add_tags: ClassVar[tuple[ItemTag]] = () - other_requirements: Tuple[Requirement, ...] = field(kw_only=True, default_factory=tuple) + other_requirements: tuple[Requirement, ...] = field(kw_only=True, default=()) @property - def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]: + def requirement_tags(self) -> Mapping[str, tuple[ItemTag, ...]]: return DEFAULT_REQUIREMENT_TAGS + @property + def all_requirements(self) -> Iterable[Requirement]: + """Returns all requirements that are not directly part of the source.""" + return self.other_requirements + @dataclass(frozen=True, kw_only=True) class GenericSource(Source): - regions: Tuple[str, ...] = () + regions: tuple[str, ...] = () """No region means it's available everywhere.""" +@dataclass(frozen=True, kw_only=True) +class AllRegionsSource(Source): + regions: tuple[str, ...] = () + + @dataclass(frozen=True) class CustomRuleSource(Source): """Hopefully once everything is migrated to sources, we won't need these custom logic anymore.""" - create_rule: Callable[[Any], StardewRule] + create_rule: "Callable[[StardewLogic], StardewRule]" class Tag(Source): """Not a real source, just a way to add tags to an item. Will be removed from the item sources during unpacking.""" - tag: Tuple[ItemTag, ...] + tag: tuple[ItemTag, ...] def __init__(self, *tag: ItemTag): self.tag = tag # noqa @@ -64,8 +80,8 @@ def add_tags(self): @dataclass(frozen=True) class GameItem: name: str - sources: List[Source] = field(default_factory=list) - tags: Set[ItemTag] = field(default_factory=set) + sources: list[Source] = field(default_factory=list) + tags: set[ItemTag] = field(default_factory=set) def add_sources(self, sources: Iterable[Source]): self.sources.extend(source for source in sources if type(source) is not Tag) diff --git a/worlds/stardew_valley/data/harvest.py b/worlds/stardew_valley/data/harvest.py index 621288ec4bd6..f19039b9f075 100644 --- a/worlds/stardew_valley/data/harvest.py +++ b/worlds/stardew_valley/data/harvest.py @@ -9,6 +9,8 @@ class ForagingSource(Source): regions: Tuple[str, ...] seasons: Tuple[str, ...] = Season.all + require_all_regions: bool = False + grind_months: int = 0 @dataclass(frozen=True, kw_only=True) diff --git a/worlds/stardew_valley/data/hats_data.py b/worlds/stardew_valley/data/hats_data.py new file mode 100644 index 000000000000..4149121b4033 --- /dev/null +++ b/worlds/stardew_valley/data/hats_data.py @@ -0,0 +1,160 @@ +from dataclasses import dataclass +from functools import cached_property +from typing import List + +from worlds.stardew_valley.strings.ap_names.ap_option_names import HatsanityOptionName + +hat_clarifier = " (Hat)" + + +@dataclass(frozen=True) +class HatItem: + name: str + difficulty: frozenset[str] + need_clarifier: bool = False + + def __post_init__(self): + all_hats.append(self) + + @cached_property + def clarified_name(self) -> str: + if self.need_clarifier: + return self.name + hat_clarifier + return self.name + + def __repr__(self) -> str: + return f"{self.name} (Difficulty: {self.difficulty})" + + +def create_hat(name: str, difficulty: List[str] | str, need_clarifier: bool = False) -> HatItem: + if isinstance(difficulty, str): + difficulty = [difficulty] + return HatItem(name, frozenset(difficulty), need_clarifier) + + +all_hats: list[HatItem] = [] + + +class Hats: + abigails_bow = create_hat("Abigail's Bow", HatsanityOptionName.rng) + arcane_hat = create_hat("Arcane Hat", HatsanityOptionName.near_perfection) + archers_cap = create_hat("Archer's Cap", HatsanityOptionName.near_perfection) + beanie = create_hat("Beanie", HatsanityOptionName.tailoring) + blobfish_mask = create_hat("Blobfish Mask", HatsanityOptionName.tailoring) + blue_bonnet = create_hat("Blue Bonnet", HatsanityOptionName.medium) + blue_bow = create_hat("Blue Bow", HatsanityOptionName.easy) + blue_cowboy_hat = create_hat("Blue Cowboy Hat", HatsanityOptionName.rng) + blue_ribbon = create_hat("Blue Ribbon", HatsanityOptionName.medium) + bluebird_mask = create_hat("Bluebird Mask", HatsanityOptionName.medium) + bowler = create_hat("Bowler Hat", HatsanityOptionName.difficult) + bridal_veil = create_hat("Bridal Veil", HatsanityOptionName.tailoring) + bucket_hat = create_hat("Bucket Hat", HatsanityOptionName.medium) + butterfly_bow = create_hat("Butterfly Bow", HatsanityOptionName.easy) + cat_ears = create_hat("Cat Ears", HatsanityOptionName.medium) + chef_hat = create_hat("Chef Hat", HatsanityOptionName.near_perfection) + chicken_mask = create_hat("Chicken Mask", HatsanityOptionName.near_perfection) + concerned_ape_mask = create_hat("???", HatsanityOptionName.post_perfection) + cone_hat = create_hat("Cone Hat", HatsanityOptionName.easy) + cool_cap = create_hat("Cool Cap", HatsanityOptionName.medium) + copper_pan_hat = create_hat("Copper Pan", HatsanityOptionName.easy, need_clarifier=True) + cowboy = create_hat("Cowboy Hat", HatsanityOptionName.near_perfection) + cowgal_hat = create_hat("Cowgal Hat", HatsanityOptionName.difficult) + cowpoke_hat = create_hat("Cowpoke Hat", HatsanityOptionName.difficult) + daisy = create_hat("Daisy", HatsanityOptionName.easy) + dark_ballcap = create_hat("Dark Ballcap", HatsanityOptionName.rng) + dark_cowboy_hat = create_hat("Dark Cowboy Hat", HatsanityOptionName.rng) + dark_velvet_bow = create_hat("Dark Velvet Bow", HatsanityOptionName.easy) + delicate_bow = create_hat("Delicate Bow", HatsanityOptionName.easy) + deluxe_cowboy_hat = create_hat("Deluxe Cowboy Hat", HatsanityOptionName.medium) + deluxe_pirate_hat = create_hat("Deluxe Pirate Hat", HatsanityOptionName.rng) + dinosaur_hat = create_hat("Dinosaur Hat", HatsanityOptionName.tailoring) + earmuffs = create_hat("Earmuffs", HatsanityOptionName.medium) + elegant_turban = create_hat("Elegant Turban", HatsanityOptionName.post_perfection) + emilys_magic_hat = create_hat("Emily's Magic Hat", HatsanityOptionName.medium) + eye_patch = create_hat("Eye Patch", HatsanityOptionName.near_perfection) + fashion_hat = create_hat("Fashion Hat", HatsanityOptionName.tailoring) + fedora = create_hat("Fedora", HatsanityOptionName.easy) + fishing_hat = create_hat("Fishing Hat", HatsanityOptionName.tailoring) + flat_topped_hat = create_hat("Flat Topped Hat", HatsanityOptionName.tailoring) + floppy_beanie = create_hat("Floppy Beanie", HatsanityOptionName.tailoring) + foragers_hat = create_hat("Forager's Hat", HatsanityOptionName.tailoring) + frog_hat = create_hat("Frog Hat", HatsanityOptionName.medium) + garbage_hat = create_hat("Garbage Hat", HatsanityOptionName.rng) + gils_hat = create_hat("Gil's Hat", HatsanityOptionName.difficult) + gnomes_cap = create_hat("Gnome's Cap", HatsanityOptionName.near_perfection) + goblin_mask = create_hat("Goblin Mask", HatsanityOptionName.near_perfection) + goggles = create_hat("Goggles", HatsanityOptionName.tailoring) + gold_pan_hat = create_hat("Gold Pan", HatsanityOptionName.easy, need_clarifier=True) + golden_helmet = create_hat("Golden Helmet", HatsanityOptionName.rng) + golden_mask = create_hat("Golden Mask", HatsanityOptionName.tailoring, need_clarifier=True) + good_ol_cap = create_hat("Good Ol' Cap", HatsanityOptionName.easy) + governors_hat = create_hat("Governor's Hat", HatsanityOptionName.medium) + green_turban = create_hat("Green Turban", HatsanityOptionName.medium) + hair_bone = create_hat("Hair Bone", HatsanityOptionName.tailoring) + hard_hat = create_hat("Hard Hat", HatsanityOptionName.difficult) + hunters_cap = create_hat("Hunter's Cap", HatsanityOptionName.medium) + infinity_crown = create_hat("Infinity Crown", HatsanityOptionName.difficult) + iridium_pan_hat = create_hat("Iridium Pan", HatsanityOptionName.easy, need_clarifier=True) + jester_hat = create_hat("Jester Hat", HatsanityOptionName.easy) + joja_cap = create_hat("Joja Cap", HatsanityOptionName.rng) + junimo_hat = create_hat("Junimo Hat", HatsanityOptionName.post_perfection) + knights_helmet = create_hat("Knight's Helmet", HatsanityOptionName.near_perfection) + laurel_wreath_crown = create_hat("Laurel Wreath Crown", HatsanityOptionName.rng) + leprechaun_hat = create_hat("Leprechaun Hat", HatsanityOptionName.easy) + living_hat = create_hat("Living Hat", HatsanityOptionName.rng) + logo_cap = create_hat("Logo Cap", HatsanityOptionName.tailoring) + lucky_bow = create_hat("Lucky Bow", HatsanityOptionName.medium) + magic_cowboy_hat = create_hat("Magic Cowboy Hat", HatsanityOptionName.difficult) + magic_turban = create_hat("Magic Turban", HatsanityOptionName.difficult) + mouse_ears = create_hat("Mouse Ears", HatsanityOptionName.easy) + mr_qis_hat = create_hat("Mr. Qi's Hat", HatsanityOptionName.medium) + mummy_mask = create_hat("Mummy Mask", HatsanityOptionName.easy) + mushroom_cap = create_hat("Mushroom Cap", HatsanityOptionName.rng) + mystery_hat = create_hat("Mystery Hat", HatsanityOptionName.rng) + official_cap = create_hat("Official Cap", HatsanityOptionName.medium) + pageboy_cap = create_hat("Pageboy Cap", HatsanityOptionName.near_perfection) + panda_hat = create_hat("Panda Hat", "Impossible") + paper_hat = create_hat("Paper Hat", HatsanityOptionName.easy) + party_hat_blue = create_hat("Party Hat (Blue)", HatsanityOptionName.tailoring) + party_hat_green = create_hat("Party Hat (Green)", HatsanityOptionName.tailoring) + party_hat_red = create_hat("Party Hat (Red)", HatsanityOptionName.tailoring) + pink_bow = create_hat("Pink Bow", HatsanityOptionName.easy) + pirate_hat = create_hat("Pirate Hat", HatsanityOptionName.tailoring) + plum_chapeau = create_hat("Plum Chapeau", HatsanityOptionName.difficult) + polka_bow = create_hat("Polka Bow", HatsanityOptionName.medium) + propeller_hat = create_hat("Propeller Hat", HatsanityOptionName.tailoring) + pumpkin_mask = create_hat("Pumpkin Mask", HatsanityOptionName.tailoring) + qi_mask = create_hat("Qi Mask", HatsanityOptionName.tailoring) + raccoon_hat = create_hat("Raccoon Hat", HatsanityOptionName.medium) + radioactive_goggles = create_hat("Radioactive Goggles", HatsanityOptionName.tailoring) + red_cowboy_hat = create_hat("Red Cowboy Hat", HatsanityOptionName.rng) + red_fez = create_hat("Red Fez", HatsanityOptionName.medium) + sailors_cap = create_hat("Sailor's Cap", HatsanityOptionName.easy) + santa_hat = create_hat("Santa Hat", HatsanityOptionName.medium) + skeleton_mask = create_hat("Skeleton Mask", HatsanityOptionName.medium) + small_cap = create_hat("Small Cap", HatsanityOptionName.medium) + sombrero = create_hat("Sombrero", HatsanityOptionName.near_perfection) + souwester = create_hat("Sou'wester", HatsanityOptionName.easy) + space_helmet = create_hat("Space Helmet", HatsanityOptionName.difficult) + sports_cap = create_hat("Sports Cap", HatsanityOptionName.medium) + spotted_headscarf = create_hat("Spotted Headscarf", HatsanityOptionName.tailoring) + squid_hat = create_hat("Squid Hat", HatsanityOptionName.medium) + squires_helmet = create_hat("Squire's Helmet", HatsanityOptionName.rng) + star_helmet = create_hat("Star Helmet", HatsanityOptionName.tailoring) + steel_pan_hat = create_hat("Steel Pan", HatsanityOptionName.easy, need_clarifier=True) + straw = create_hat("Straw Hat", HatsanityOptionName.medium) + sunglasses = create_hat("Sunglasses", HatsanityOptionName.tailoring) + swashbuckler_hat = create_hat("Swashbuckler Hat", HatsanityOptionName.tailoring) + tiara = create_hat("Tiara", HatsanityOptionName.easy) + tiger_hat = create_hat("Tiger Hat", HatsanityOptionName.rng) + top_hat = create_hat("Top Hat", HatsanityOptionName.easy) + totem_mask = create_hat("Totem Mask", HatsanityOptionName.tailoring) + tricorn = create_hat("Tricorn Hat", HatsanityOptionName.rng) + tropiclip = create_hat("Tropiclip", HatsanityOptionName.easy) + trucker_hat = create_hat("Trucker Hat", HatsanityOptionName.medium) + warrior_helmet = create_hat("Warrior Helmet", HatsanityOptionName.tailoring) + watermelon_band = create_hat("Watermelon Band", HatsanityOptionName.difficult) + wearable_dwarf_helm = create_hat("Wearable Dwarf Helm", HatsanityOptionName.tailoring) + white_bow = create_hat("White Bow", HatsanityOptionName.difficult) + white_turban = create_hat("White Turban", HatsanityOptionName.tailoring) + witch_hat = create_hat("Witch Hat", HatsanityOptionName.tailoring) diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index f7dac9c5794e..42c22d872d47 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -1,11 +1,11 @@ -id,name,classification,groups,mod_name +id,name,classification,groups,content_packs 0,Joja Cola,filler,TRASH, 15,Rusty Key,progression,, 16,Dwarvish Translation Guide,progression,, 17,Bridge Repair,progression,COMMUNITY_REWARD, 18,Greenhouse,progression,COMMUNITY_REWARD, 19,Glittering Boulder Removed,progression,COMMUNITY_REWARD, -20,Minecarts Repair,useful,COMMUNITY_REWARD, +20,Minecarts Repair,progression,COMMUNITY_REWARD, 21,Bus Repair,progression,"COMMUNITY_REWARD,DESERT_TRANSPORTATION", 22,Progressive Movie Theater,"progression,trap",COMMUNITY_REWARD, 23,Stardrop,progression,, @@ -65,16 +65,16 @@ id,name,classification,groups,mod_name 79,Water Obelisk,progression,WIZARD_BUILDING, 80,Desert Obelisk,progression,"WIZARD_BUILDING,DESERT_TRANSPORTATION", 81,Island Obelisk,progression,"WIZARD_BUILDING,GINGER_ISLAND,ISLAND_TRANSPORTATION", -82,Junimo Hut,useful,WIZARD_BUILDING, +82,Junimo Hut,progression,WIZARD_BUILDING, 83,Gold Clock,progression,WIZARD_BUILDING, 84,Progressive Coop,progression,BUILDING, 85,Progressive Barn,progression,BUILDING, -86,Well,useful,BUILDING, +86,Well,progression,BUILDING, 87,Silo,progression,BUILDING, 88,Mill,progression,BUILDING, 89,Progressive Shed,progression,BUILDING, 90,Fish Pond,progression,BUILDING, -91,Stable,useful,BUILDING, +91,Stable,progression,BUILDING, 92,Slime Hutch,progression,BUILDING, 93,Shipping Bin,progression,BUILDING, 94,Beach Bridge,progression,, @@ -82,7 +82,7 @@ id,name,classification,groups,mod_name 96,Club Card,progression,, 97,Magnifying Glass,progression,, 98,Bear's Knowledge,progression,, -99,Iridium Snake Milk,useful,, +99,Iridium Snake Milk,progression,, 100,JotPK: Progressive Boots,progression,ARCADE_MACHINE_BUFFS, 101,JotPK: Progressive Gun,progression,ARCADE_MACHINE_BUFFS, 102,JotPK: Progressive Ammo,progression,ARCADE_MACHINE_BUFFS, @@ -146,9 +146,9 @@ id,name,classification,groups,mod_name 160,Wheat Seeds,progression,CROPSANITY, 161,Yam Seeds,progression,CROPSANITY, 162,Cactus Seeds,progression,CROPSANITY, -163,Magic Rock Candy,useful,"MUSEUM,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +163,Magic Rock Candy,useful,"MUSEUM,FILLER_BUFF_FOOD", 164,Ancient Seeds Recipe,progression,MUSEUM, -165,Ancient Seeds,progression,"MUSEUM,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +165,Ancient Seeds,progression,"MUSEUM,FILLER_FARMING", 166,Traveling Merchant Metal Detector,progression,MUSEUM, 167,Alex <3,progression,FRIENDSANITY, 168,Elliott <3,progression,FRIENDSANITY, @@ -193,24 +193,24 @@ id,name,classification,groups,mod_name 207,Rarecrow #6,progression,"FESTIVAL,RARECROW", 208,Rarecrow #7,progression,"FESTIVAL,RARECROW", 209,Rarecrow #8,progression,"FESTIVAL,RARECROW", -210,Straw Hat,filler,FESTIVAL, -211,Golden Pumpkin,useful,FESTIVAL, +210,Straw Hat,filler,"FILLER_HAT", +211,Golden Pumpkin,progression,FESTIVAL, 212,Barbed Hook,useful,FESTIVAL, 213,Dressed Spinner,useful,FESTIVAL, 214,Magnet,useful,FESTIVAL, -215,Sailor's Cap,filler,FESTIVAL, +215,Sailor's Cap,filler,"FILLER_HAT", 216,Pearl,useful,FESTIVAL, -217,Cone Hat,filler,FESTIVAL, -218,Iridium Fireplace,filler,FESTIVAL, -219,Lupini: Red Eagle,filler,FESTIVAL, -220,Lupini: Portrait Of A Mermaid,filler,FESTIVAL, -221,Lupini: Solar Kingdom,filler,FESTIVAL, -222,Lupini: Clouds,filler,FESTIVAL, -223,Lupini: 1000 Years From Now,filler,FESTIVAL, -224,Lupini: Three Trees,filler,FESTIVAL, -225,Lupini: The Serpent,filler,FESTIVAL, -226,Lupini: 'Tropical Fish #173',filler,FESTIVAL, -227,Lupini: Land Of Clay,filler,FESTIVAL, +217,Cone Hat,filler,"FILLER_HAT", +218,Iridium Fireplace,filler,FILLER_DECORATION, +219,Lupini: Red Eagle,filler,FILLER_DECORATION, +220,Lupini: Portrait Of A Mermaid,filler,FILLER_DECORATION, +221,Lupini: Solar Kingdom,filler,FILLER_DECORATION, +222,Lupini: Clouds,filler,FILLER_DECORATION, +223,Lupini: 1000 Years From Now,filler,FILLER_DECORATION, +224,Lupini: Three Trees,filler,FILLER_DECORATION, +225,Lupini: The Serpent,filler,FILLER_DECORATION, +226,Lupini: 'Tropical Fish #173',filler,FILLER_DECORATION, +227,Lupini: Land Of Clay,filler,FILLER_DECORATION, 228,Special Order Board,progression,SPECIAL_ORDER_BOARD, 229,Solar Panel Recipe,progression,SPECIAL_ORDER_BOARD, 230,Geode Crusher Recipe,progression,SPECIAL_ORDER_BOARD, @@ -221,11 +221,11 @@ id,name,classification,groups,mod_name 235,Quality Bobber Recipe,progression,SPECIAL_ORDER_BOARD, 236,Mini-Obelisk Recipe,progression,SPECIAL_ORDER_BOARD, 237,Monster Musk Recipe,progression,SPECIAL_ORDER_BOARD, -239,Sewing Machine,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", -240,Coffee Maker,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", -241,Mini-Fridge,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", -242,Mini-Shipping Bin,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", -243,Deluxe Fish Tank,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", +239,Sewing Machine,progression,"SPECIAL_ORDER_BOARD", +240,Coffee Maker,progression,"SPECIAL_ORDER_BOARD,FILLER_MACHINE", +241,Mini-Fridge,useful,"SPECIAL_ORDER_BOARD,FILLER_STORAGE", +242,Mini-Shipping Bin,useful,"SPECIAL_ORDER_BOARD,FILLER_MACHINE", +243,Deluxe Fish Tank,useful,"SPECIAL_ORDER_BOARD,FILLER_DECORATION", 244,10 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", 245,20 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", 246,25 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", @@ -233,15 +233,15 @@ id,name,classification,groups,mod_name 248,40 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", 249,50 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", 250,100 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", -251,Rare Seed,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -252,Apple Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -253,Apricot Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -254,Cherry Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -255,Orange Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -256,Pomegranate Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -257,Peach Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -258,Banana Sapling,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", -259,Mango Sapling,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +251,Rare Seed,progression,"CROPSANITY,FILLER_FARMING", +252,Apple Sapling,progression,"CROPSANITY,FILLER_FRUIT_TREES", +253,Apricot Sapling,progression,"CROPSANITY,FILLER_FRUIT_TREES", +254,Cherry Sapling,progression,"CROPSANITY,FILLER_FRUIT_TREES", +255,Orange Sapling,progression,"CROPSANITY,FILLER_FRUIT_TREES", +256,Pomegranate Sapling,progression,"CROPSANITY,FILLER_FRUIT_TREES", +257,Peach Sapling,progression,"CROPSANITY,FILLER_FRUIT_TREES", +258,Banana Sapling,progression,"GINGER_ISLAND,CROPSANITY,FILLER_FRUIT_TREES", +259,Mango Sapling,progression,"GINGER_ISLAND,CROPSANITY,FILLER_FRUIT_TREES", 260,Boat Repair,progression,"GINGER_ISLAND,ISLAND_TRANSPORTATION", 261,Open Professor Snail Cave,progression,GINGER_ISLAND, 262,Island North Turtle,progression,"GINGER_ISLAND,WALNUT_PURCHASE", @@ -263,7 +263,7 @@ id,name,classification,groups,mod_name 278,Livin' Off The Land,useful,TV_CHANNEL, 279,The Queen of Sauce,progression,TV_CHANNEL, 280,Fishing Information Broadcasting Service,useful,TV_CHANNEL, -281,Sinister Signal,filler,TV_CHANNEL, +281,Sinister Signal,progression,TV_CHANNEL, 282,Dark Talisman,progression,, 283,Ostrich Incubator Recipe,progression,GINGER_ISLAND, 284,Cute Baby,progression,BABY, @@ -276,37 +276,37 @@ id,name,classification,groups,mod_name 291,Progressive Club,progression,"WEAPON,WEAPON_CLUB", 292,Progressive Dagger,progression,"WEAPON,WEAPON_DAGGER", 293,Progressive Slingshot,progression,"WEAPON,WEAPON_SLINGSHOT", -294,Progressive Footwear,useful,FOOTWEAR, -295,Small Glow Ring,filler,"RING,RESOURCE_PACK,MAXIMUM_ONE", -296,Glow Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -297,Small Magnet Ring,filler,"RING,RESOURCE_PACK,MAXIMUM_ONE", -298,Magnet Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -299,Slime Charmer Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -300,Warrior Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -301,Vampire Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -302,Savage Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -303,Ring of Yoba,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -304,Sturdy Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -305,Burglar's Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -306,Iridium Band,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -307,Jukebox Ring,filler,"RING,RESOURCE_PACK,MAXIMUM_ONE", -308,Amethyst Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -309,Topaz Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -310,Aquamarine Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -311,Jade Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -312,Emerald Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -313,Ruby Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -314,Wedding Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -315,Crabshell Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -316,Napalm Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -317,Thorns Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -318,Lucky Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -319,Hot Java Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -320,Protection Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -321,Soul Sapper Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -322,Phoenix Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -323,Immunity Band,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -324,Glowstone Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", +294,Progressive Footwear,progression,FOOTWEAR, +295,Small Glow Ring,filler,"MAXIMUM_ONE,FILLER_RING", +296,Glow Ring,filler,"MAXIMUM_ONE,FILLER_RING", +297,Small Magnet Ring,filler,"MAXIMUM_ONE,FILLER_RING", +298,Magnet Ring,filler,"MAXIMUM_ONE,FILLER_RING", +299,Slime Charmer Ring,progression,"MAXIMUM_ONE,FILLER_RING", +300,Warrior Ring,useful,"MAXIMUM_ONE,FILLER_RING", +301,Vampire Ring,useful,"MAXIMUM_ONE,FILLER_RING", +302,Savage Ring,useful,"MAXIMUM_ONE,FILLER_RING", +303,Ring of Yoba,progression,"MAXIMUM_ONE,FILLER_RING", +304,Sturdy Ring,progression,"MAXIMUM_ONE,FILLER_RING", +305,Burglar's Ring,progression,"MAXIMUM_ONE,FILLER_RING", +306,Iridium Band,progression,"MAXIMUM_ONE,FILLER_RING", +307,Jukebox Ring,filler,"MAXIMUM_ONE,FILLER_RING", +308,Amethyst Ring,filler,"MAXIMUM_ONE,FILLER_RING", +309,Topaz Ring,filler,"MAXIMUM_ONE,FILLER_RING", +310,Aquamarine Ring,filler,"MAXIMUM_ONE,FILLER_RING", +311,Jade Ring,filler,"MAXIMUM_ONE,FILLER_RING", +312,Emerald Ring,filler,"MAXIMUM_ONE,FILLER_RING", +313,Ruby Ring,filler,"MAXIMUM_ONE,FILLER_RING", +314,Wedding Ring,progression,"MAXIMUM_ONE,FILLER_RING", +315,Crabshell Ring,useful,"MAXIMUM_ONE,FILLER_RING", +316,Napalm Ring,progression,"MAXIMUM_ONE,FILLER_RING", +317,Thorns Ring,useful,"MAXIMUM_ONE,FILLER_RING", +318,Lucky Ring,useful,"MAXIMUM_ONE,FILLER_RING", +319,Hot Java Ring,progression,"MAXIMUM_ONE,FILLER_RING", +320,Protection Ring,useful,"MAXIMUM_ONE,FILLER_RING", +321,Soul Sapper Ring,useful,"MAXIMUM_ONE,FILLER_RING", +322,Phoenix Ring,useful,"MAXIMUM_ONE,FILLER_RING", +323,Immunity Band,useful,"MAXIMUM_ONE,FILLER_RING", +324,Glowstone Ring,useful,"MAXIMUM_ONE,FILLER_RING", 325,Fairy Dust Recipe,progression,"GINGER_ISLAND", 326,Heavy Tapper Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", 327,Hyper Speed-Gro Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", @@ -317,7 +317,7 @@ id,name,classification,groups,mod_name 333,Tub o' Flowers Recipe,progression,FESTIVAL, 335,Moonlight Jellies Banner,filler,FESTIVAL, 336,Starport Decal,filler,FESTIVAL, -337,Golden Egg,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +337,Golden Egg,progression,"FILLER_FOOD", 340,Algae Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP", 341,Artichoke Dip Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS", 342,Autumn's Bounty Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP", @@ -457,16 +457,16 @@ id,name,classification,groups,mod_name 490,Text Sign Recipe,progression,CRAFTSANITY, 491,Blue Grass Starter Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", 492,Mastery Of The Five Ways,progression,SKILL_MASTERY, -493,Progressive Scythe,useful,, +493,Progressive Scythe,progression,, 494,Progressive Pan,progression,PROGRESSIVE_TOOLS, 495,Calico Statue,filler,FESTIVAL, -496,Mummy Mask,filler,FESTIVAL, +496,Mummy Mask,filler,"FILLER_HAT", 497,Free Cactis,filler,FESTIVAL, -498,Gil's Hat,filler,FESTIVAL, -499,Bucket Hat,filler,FESTIVAL, +498,Gil's Hat,filler,"FILLER_HAT", +499,Bucket Hat,filler,"FILLER_HAT", 500,Mounted Trout,filler,FESTIVAL, -501,'Squid Kid',filler,FESTIVAL, -502,Squid Hat,filler,FESTIVAL, +501,'Squid Kid',filler,FILLER_DECORATION, +502,Squid Hat,filler,"FILLER_HAT", 503,Resource Pack: 200 Calico Egg,useful,"FESTIVAL", 504,Resource Pack: 120 Calico Egg,useful,"FESTIVAL", 505,Resource Pack: 100 Calico Egg,useful,"FESTIVAL", @@ -476,27 +476,27 @@ id,name,classification,groups,mod_name 509,Resource Pack: 30 Calico Egg,useful,"FESTIVAL", 510,Book: The Art O' Crabbing,progression,"FESTIVAL", 511,Mr Qi's Plane Ride,progression,, -521,Power: Price Catalogue,useful,"BOOK_POWER", -522,Power: Mapping Cave Systems,useful,"BOOK_POWER", +521,Power: Price Catalogue,progression,"BOOK_POWER", +522,Power: Mapping Cave Systems,progression,"BOOK_POWER", 523,Power: Way Of The Wind pt. 1,progression,"BOOK_POWER", -524,Power: Way Of The Wind pt. 2,useful,"BOOK_POWER", -525,Power: Monster Compendium,useful,"BOOK_POWER", -526,Power: Friendship 101,useful,"BOOK_POWER", -527,"Power: Jack Be Nimble, Jack Be Thick",useful,"BOOK_POWER", -528,Power: Woody's Secret,useful,"BOOK_POWER", -529,Power: Raccoon Journal,useful,"BOOK_POWER", -530,Power: Jewels Of The Sea,useful,"BOOK_POWER", -531,Power: Dwarvish Safety Manual,useful,"BOOK_POWER", -532,Power: The Art O' Crabbing,useful,"BOOK_POWER", -533,Power: The Alleyway Buffet,useful,"BOOK_POWER", -534,Power: The Diamond Hunter,useful,"BOOK_POWER", +524,Power: Way Of The Wind pt. 2,progression,"BOOK_POWER", +525,Power: Monster Compendium,progression,"BOOK_POWER", +526,Power: Friendship 101,progression,"BOOK_POWER", +527,"Power: Jack Be Nimble, Jack Be Thick",progression,"BOOK_POWER", +528,Power: Woody's Secret,progression,"BOOK_POWER", +529,Power: Raccoon Journal,progression,"BOOK_POWER", +530,Power: Jewels Of The Sea,progression,"BOOK_POWER", +531,Power: Dwarvish Safety Manual,progression,"BOOK_POWER", +532,Power: The Art O' Crabbing,progression,"BOOK_POWER", +533,Power: The Alleyway Buffet,progression,"BOOK_POWER", +534,Power: The Diamond Hunter,progression,"BOOK_POWER,GINGER_ISLAND", 535,Power: Book of Mysteries,progression,"BOOK_POWER", -536,Power: Horse: The Book,useful,"BOOK_POWER", -537,Power: Treasure Appraisal Guide,useful,"BOOK_POWER", -538,Power: Ol' Slitherlegs,useful,"BOOK_POWER", -539,Power: Animal Catalogue,useful,"BOOK_POWER", +536,Power: Horse: The Book,progression,"BOOK_POWER", +537,Power: Treasure Appraisal Guide,progression,"BOOK_POWER", +538,Power: Ol' Slitherlegs,progression,"BOOK_POWER", +539,Power: Animal Catalogue,progression,"BOOK_POWER", 541,Progressive Lost Book,progression,"LOST_BOOK", -551,Golden Walnut,progression,"RESOURCE_PACK,GINGER_ISLAND", +551,Golden Walnut,progression,"FILLER_CURRENCY,GINGER_ISLAND", 552,3 Golden Walnuts,progression,"GINGER_ISLAND", 553,5 Golden Walnuts,progression,"GINGER_ISLAND", 554,Damage Bonus,filler,PLAYER_BUFF, @@ -510,6 +510,95 @@ id,name,classification,groups,mod_name 562,Quality Bonus,filler,PLAYER_BUFF, 563,Glow Bonus,filler,PLAYER_BUFF, 564,Pet Bowl,progression,BUILDING, +565,??HMTGF??,filler,"EASY_SECRET", +566,??Pinky Lemon??,filler,"EASY_SECRET", +567,??Foroguemon??,filler,"EASY_SECRET", +568,Junimo Plush,filler,"EASY_SECRET", +569,'Boat',filler,"FILLER_DECORATION", +570,Decorative Trash Can,filler,"FILLER_DECORATION", +571,Foliage Print,filler,"FILLER_DECORATION", +572,Frog Hat,filler,"FILLER_HAT", +573,Gourmand Statue,filler,"FILLER_DECORATION", +574,Iridium Krobus,filler,"FILLER_DECORATION", +575,Lifesaver,filler,"FILLER_DECORATION", +576,'Physics 101',filler,"FILLER_DECORATION", +577,Pyramid Decal,filler,"FILLER_DECORATION", +578,Squirrel Figurine,filler,"FILLER_DECORATION", +579,'Vista',filler,"FILLER_DECORATION", +580,Wall Basket,filler,"FILLER_DECORATION", +581,Far Away Stone,progression,"EASY_SECRET", +582,Strange Doll,filler,"SECRET_NOTES_SECRET", +583,Strange Doll (Green),filler,"SECRET_NOTES_SECRET", +584,Solid Gold Lewis,progression,"SECRET_NOTES_SECRET", +585,Special Charm,useful,"SECRET_NOTES_SECRET", +586,Stone Junimo,filler,"SECRET_NOTES_SECRET", +587,Movie Salty Snacks,progression,"MOVIESANITY", +588,Movie Sweet Snacks,progression,"MOVIESANITY", +589,Movie Drinks,progression,"MOVIESANITY", +590,Movie Meals,progression,"MOVIESANITY", +601,Basilisk Paw,filler,"TRINKET", +602,Fairy Box,filler,"TRINKET", +603,Frog Egg,filler,"TRINKET", +604,Golden Spur,filler,"TRINKET", +605,Ice Rod,filler,"TRINKET", +606,Magic Hair Gel,filler,"TRINKET", +607,Magic Quiver,filler,"TRINKET", +608,Parrot Egg,filler,"TRINKET", +621,Stamina Enzyme,useful,"EATSANITY_ENZYME", +622,Health Enzyme,useful,"EATSANITY_ENZYME", +623,Speed Enzyme,useful,"EATSANITY_ENZYME", +624,Luck Enzyme,useful,"EATSANITY_ENZYME", +625,Farming Enzyme,useful,"EATSANITY_ENZYME", +626,Foraging Enzyme,useful,"EATSANITY_ENZYME", +627,Fishing Enzyme,progression,"EATSANITY_ENZYME", +628,Mining Enzyme,useful,"EATSANITY_ENZYME", +629,Magnetism Enzyme,useful,"EATSANITY_ENZYME", +630,Defense Enzyme,useful,"EATSANITY_ENZYME", +631,Max Stamina Enzyme,useful,"EATSANITY_ENZYME", +632,Squid Ink Enzyme,useful,"EATSANITY_ENZYME", +633,Monster Musk Enzyme,useful,"EATSANITY_ENZYME", +634,Oil Of Garlic Enzyme,useful,"EATSANITY_ENZYME", +635,Tipsy Enzyme,trap,"EATSANITY_ENZYME", +636,Attack Enzyme,useful,"EATSANITY_ENZYME", +640,Forest Magic,progression,"COMMUNITY_REWARD", +641,Landslide Removed,progression,"COMMUNITY_REWARD", +642,Magic Ink,progression,, +643,Community Center Key,progression,"COMMUNITY_REWARD", +644,Wizard Invitation,progression,"COMMUNITY_REWARD", +645,Pam House,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +646,Forest To Beach Shortcut,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +647,Mountain Shortcuts,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +648,Town To Tide Pools Shortcut,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +649,Tunnel To Backwoods Shortcut,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +650,Statue Of Endless Fortune,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_MACHINE", +651,Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +652,Furniture Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +653,Joja Furniture Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +654,Junimo Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +655,Retro Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +656,Trash Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +657,Wizard Catalogue,progression,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION", +658,Abigail Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +659,Alex Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +660,Elliott Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +661,Emily Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +662,Haley Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +663,Harvey Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +664,Krobus Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +665,Leah Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +666,Maru Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +667,Penny Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +668,Sam Portrait,filler,"ENDGAME_LOCATION_ITEMS,MAXIMUM_ONE,FILLER_DECORATION,REQUIRES_FRIENDSANITY_MARRIAGE", +669,Sebastian Portrait,filler,"FILLER_DECORATION", +670,Shane Portrait,filler,"FILLER_DECORATION", +671,Trash Bear Arrival,progression,"TRASH_BEAR,COMMUNITY_REWARD", +672,Trash Bear Cleanup,filler,"TRASH_BEAR,COMMUNITY_REWARD", +673,Progressive Bookseller Days,progression,"BOOKSELLER", +674,Bookseller Stock: Progressive Experience Books,progression,"BOOKSELLER", +675,Bookseller Stock: Progressive Rare Books,progression,"BOOKSELLER", +676,Bookseller Stock: Permanent Books,progression,"BOOKSELLER", +677,Men's Locker Key,progression,, +678,Women's Locker Key,progression,, 4001,Burnt Trap,trap,TRAP, 4002,Darkness Trap,trap,TRAP, 4003,Frozen Trap,trap,TRAP, @@ -537,290 +626,296 @@ id,name,classification,groups,mod_name 4025,Bomb Trap,trap,TRAP, 4026,Nudge Trap,trap,TRAP, 4501,Deflation Bonus,filler,, -5000,Resource Pack: 500 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", -5001,Resource Pack: 1000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", -5002,Resource Pack: 1500 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", -5003,Resource Pack: 2000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", -5004,Resource Pack: 25 Stone,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5005,Resource Pack: 50 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK", -5006,Resource Pack: 75 Stone,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5007,Resource Pack: 100 Stone,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5008,Resource Pack: 25 Wood,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5009,Resource Pack: 50 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK", -5010,Resource Pack: 75 Wood,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5011,Resource Pack: 100 Wood,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5012,Resource Pack: 5 Hardwood,useful,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5013,Resource Pack: 10 Hardwood,useful,"BASE_RESOURCE,RESOURCE_PACK", -5014,Resource Pack: 15 Hardwood,useful,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5015,Resource Pack: 20 Hardwood,useful,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5016,Resource Pack: 15 Fiber,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5017,Resource Pack: 30 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK", -5018,Resource Pack: 45 Fiber,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5019,Resource Pack: 60 Fiber,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5020,Resource Pack: 5 Coal,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5021,Resource Pack: 10 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK", -5022,Resource Pack: 15 Coal,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5023,Resource Pack: 20 Coal,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5024,Resource Pack: 5 Clay,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5025,Resource Pack: 10 Clay,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5026,Resource Pack: 15 Clay,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", -5027,Resource Pack: 20 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK", -5028,Resource Pack: 1 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5029,Resource Pack: 3 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5030,Resource Pack: 5 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM", -5031,Resource Pack: 7 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5032,Resource Pack: 9 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5033,Resource Pack: 10 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5034,Resource Pack: 1 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5035,Resource Pack: 3 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5036,Resource Pack: 5 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM", -5037,Resource Pack: 7 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5038,Resource Pack: 9 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5039,Resource Pack: 10 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5040,Resource Pack: 1 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5041,Resource Pack: 3 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5042,Resource Pack: 5 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM", -5043,Resource Pack: 7 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5044,Resource Pack: 9 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5045,Resource Pack: 10 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5046,Resource Pack: 1 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND", -5047,Resource Pack: 3 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND", -5048,Resource Pack: 5 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND", -5049,Resource Pack: 7 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND", -5050,Resource Pack: 9 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND", -5051,Resource Pack: 10 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND", -5052,Resource Pack: 1 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5053,Resource Pack: 3 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5054,Resource Pack: 5 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM", -5055,Resource Pack: 7 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5056,Resource Pack: 9 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5057,Resource Pack: 10 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", -5058,Resource Pack: 6 Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5059,Resource Pack: 12 Geode,filler,"GEODE,RESOURCE_PACK", -5060,Resource Pack: 18 Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5061,Resource Pack: 24 Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5062,Resource Pack: 4 Frozen Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5063,Resource Pack: 8 Frozen Geode,filler,"GEODE,RESOURCE_PACK", -5064,Resource Pack: 12 Frozen Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5065,Resource Pack: 16 Frozen Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5066,Resource Pack: 3 Magma Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5067,Resource Pack: 6 Magma Geode,filler,"GEODE,RESOURCE_PACK", -5068,Resource Pack: 9 Magma Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5069,Resource Pack: 12 Magma Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", -5070,Resource Pack: 2 Omni Geode,useful,"DEPRECATED,GEODE,RESOURCE_PACK", -5071,Resource Pack: 4 Omni Geode,useful,"GEODE,RESOURCE_PACK", -5072,Resource Pack: 6 Omni Geode,useful,"DEPRECATED,GEODE,RESOURCE_PACK", -5073,Resource Pack: 8 Omni Geode,useful,"DEPRECATED,GEODE,RESOURCE_PACK", -5074,Resource Pack: 25 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5075,Resource Pack: 50 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5076,Resource Pack: 75 Copper Ore,filler,"ORE,RESOURCE_PACK", -5077,Resource Pack: 100 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5078,Resource Pack: 125 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5079,Resource Pack: 150 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5080,Resource Pack: 25 Iron Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5081,Resource Pack: 50 Iron Ore,filler,"ORE,RESOURCE_PACK", -5082,Resource Pack: 75 Iron Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5083,Resource Pack: 100 Iron Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", -5084,Resource Pack: 12 Gold Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", -5085,Resource Pack: 25 Gold Ore,useful,"ORE,RESOURCE_PACK", -5086,Resource Pack: 38 Gold Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", -5087,Resource Pack: 50 Gold Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", -5088,Resource Pack: 5 Iridium Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", -5089,Resource Pack: 10 Iridium Ore,useful,"ORE,RESOURCE_PACK", -5090,Resource Pack: 15 Iridium Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", -5091,Resource Pack: 20 Iridium Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", -5092,Resource Pack: 5 Quartz,filler,"ORE,RESOURCE_PACK", -5093,Resource Pack: 10 Quartz,filler,"ORE,DEPRECATED,RESOURCE_PACK", -5094,Resource Pack: 15 Quartz,filler,"ORE,DEPRECATED,RESOURCE_PACK", -5095,Resource Pack: 20 Quartz,filler,"ORE,DEPRECATED,RESOURCE_PACK", -5096,Resource Pack: 10 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5097,Resource Pack: 20 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5098,Resource Pack: 30 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK", -5099,Resource Pack: 40 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5100,Resource Pack: 50 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5101,Resource Pack: 60 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5102,Resource Pack: 10 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5103,Resource Pack: 20 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5104,Resource Pack: 30 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK", -5105,Resource Pack: 40 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5106,Resource Pack: 50 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5107,Resource Pack: 60 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5108,Resource Pack: 10 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5109,Resource Pack: 20 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5110,Resource Pack: 30 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK", -5111,Resource Pack: 40 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5112,Resource Pack: 50 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5113,Resource Pack: 60 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5114,Resource Pack: 4 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5115,Resource Pack: 12 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5116,Resource Pack: 20 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK", -5117,Resource Pack: 28 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5118,Resource Pack: 36 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5119,Resource Pack: 40 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5120,Resource Pack: 4 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5121,Resource Pack: 12 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5122,Resource Pack: 20 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK", -5123,Resource Pack: 28 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5124,Resource Pack: 36 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5125,Resource Pack: 40 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5126,Resource Pack: 4 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5127,Resource Pack: 12 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5128,Resource Pack: 20 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK", -5129,Resource Pack: 28 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5130,Resource Pack: 36 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5131,Resource Pack: 40 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5132,Resource Pack: 2 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5133,Resource Pack: 6 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5134,Resource Pack: 10 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK", -5135,Resource Pack: 14 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5136,Resource Pack: 18 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5137,Resource Pack: 20 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5138,Resource Pack: 2 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5139,Resource Pack: 6 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5140,Resource Pack: 10 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK", -5141,Resource Pack: 14 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5142,Resource Pack: 18 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5143,Resource Pack: 20 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5144,Resource Pack: 2 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5145,Resource Pack: 6 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5146,Resource Pack: 10 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK", -5147,Resource Pack: 14 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5148,Resource Pack: 18 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5149,Resource Pack: 20 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5150,Resource Pack: 2 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5151,Resource Pack: 6 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5152,Resource Pack: 10 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK", -5153,Resource Pack: 14 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5154,Resource Pack: 18 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5155,Resource Pack: 20 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", -5156,Resource Pack: 10 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5157,Resource Pack: 20 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5158,Resource Pack: 30 Spring Seeds,filler,"RESOURCE_PACK,SEED", -5159,Resource Pack: 40 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5160,Resource Pack: 50 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5161,Resource Pack: 60 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5162,Resource Pack: 10 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5163,Resource Pack: 20 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5164,Resource Pack: 30 Summer Seeds,filler,"RESOURCE_PACK,SEED", -5165,Resource Pack: 40 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5166,Resource Pack: 50 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5167,Resource Pack: 60 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5168,Resource Pack: 10 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5169,Resource Pack: 20 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5170,Resource Pack: 30 Fall Seeds,filler,"RESOURCE_PACK,SEED", -5171,Resource Pack: 40 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5172,Resource Pack: 50 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5173,Resource Pack: 60 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5174,Resource Pack: 10 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5175,Resource Pack: 20 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5176,Resource Pack: 30 Winter Seeds,filler,"RESOURCE_PACK,SEED", -5177,Resource Pack: 40 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5178,Resource Pack: 50 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5179,Resource Pack: 60 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5180,Resource Pack: 1 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5181,Resource Pack: 3 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5182,Resource Pack: 5 Mahogany Seed,filler,"RESOURCE_PACK,SEED", -5183,Resource Pack: 7 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5184,Resource Pack: 9 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5185,Resource Pack: 10 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", -5186,Resource Pack: 10 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5187,Resource Pack: 20 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5188,Resource Pack: 30 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK", -5189,Resource Pack: 40 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5190,Resource Pack: 50 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5191,Resource Pack: 60 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5192,Resource Pack: 1 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5193,Resource Pack: 2 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5194,Resource Pack: 3 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK", -5195,Resource Pack: 4 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5196,Resource Pack: 5 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5197,Resource Pack: 6 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", -5198,Friendship Bonus (1 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK", -5199,Friendship Bonus (2 <3),useful,"FRIENDSHIP_PACK,COMMUNITY_REWARD", -5200,Friendship Bonus (3 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK", -5201,Friendship Bonus (4 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK", -5202,15 Qi Gems,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5203,Solar Panel,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5204,Geode Crusher,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5205,Farm Computer,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5206,Bone Mill,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5207,Fiber Seeds,filler,RESOURCE_PACK, -5208,Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5209,Stone Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5210,Quality Bobber,filler,RESOURCE_PACK, -5211,Mini-Obelisk,filler,"AT_LEAST_TWO,RESOURCE_PACK", -5212,Monster Musk,filler,RESOURCE_PACK, -5213,Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5214,Quality Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5215,Iridium Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5216,Scarecrow,filler,RESOURCE_PACK, -5217,Deluxe Scarecrow,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5218,Furnace,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5219,Charcoal Kiln,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5220,Lightning Rod,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5221,Resource Pack: 5000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5222,Resource Pack: 10000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5223,Junimo Chest,filler,"AT_LEAST_TWO,RESOURCE_PACK", -5224,Horse Flute,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5225,Pierre's Missing Stocklist,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5226,Hopper,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5227,Enricher,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5228,Pressure Nozzle,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5229,Deconstructor,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5230,Key To The Town,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5231,Galaxy Soul,filler,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5232,Mushroom Tree Seed,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5233,Resource Pack: 20 Magic Bait,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5234,Resource Pack: 10 Qi Seasoning,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5235,Mr. Qi's Hat,filler,"MAXIMUM_ONE,RESOURCE_PACK", -5236,Aquatic Sanctuary,filler,RESOURCE_PACK, -5237,Leprechaun Hat,filler,"MAXIMUM_ONE,RESOURCE_PACK", -5242,Exotic Double Bed,filler,RESOURCE_PACK, -5243,Resource Pack: 2 Qi Gem,filler,"GINGER_ISLAND,RESOURCE_PACK,DEPRECATED", -5247,Fairy Dust,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5248,Seed Maker,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5249,Keg,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5250,Cask,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5251,Preserves Jar,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5252,Bee House,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5253,Garden Pot,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5254,Cheese Press,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5255,Mayonnaise Machine,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5256,Loom,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5257,Oil Maker,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5258,Recycling Machine,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5259,Worm Bin,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5260,Tapper,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5261,Heavy Tapper,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5262,Slime Incubator,useful,RESOURCE_PACK, -5263,Slime Egg-Press,useful,RESOURCE_PACK, -5264,Crystalarium,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5265,Ostrich Incubator,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5266,Resource Pack: 5 Staircase,filler,"RESOURCE_PACK", -5267,Auto-Petter,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5268,Auto-Grabber,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5269,Resource Pack: 10 Calico Egg,filler,"RESOURCE_PACK", -5270,Resource Pack: 20 Calico Egg,filler,"RESOURCE_PACK", -5272,Tent Kit,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5273,Resource Pack: 4 Mystery Box,filler,"RESOURCE_PACK", -5274,Prize Ticket,filler,"RESOURCE_PACK", -5275,Resource Pack: 20 Deluxe Bait,filler,"RESOURCE_PACK", -5276,Resource Pack: 2 Triple Shot Espresso,filler,"RESOURCE_PACK", -5277,Dish O' The Sea,filler,"RESOURCE_PACK", -5278,Seafoam Pudding,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5279,Trap Bobber,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5280,Treasure Chest,filler,"RESOURCE_PACK", -5281,Resource Pack: 15 Mixed Seeds,filler,"RESOURCE_PACK", -5282,Resource Pack: 15 Mixed Flower Seeds,filler,"RESOURCE_PACK", -5283,Resource Pack: 5 Cherry Bomb,filler,"RESOURCE_PACK", -5284,Resource Pack: 3 Bomb,filler,"RESOURCE_PACK", -5285,Resource Pack: 2 Mega Bomb,filler,"RESOURCE_PACK", -5286,Resource Pack: 2 Life Elixir,filler,"RESOURCE_PACK", -5287,Resource Pack: 5 Coffee,filler,"RESOURCE_PACK", -5289,Prismatic Shard,filler,"RESOURCE_PACK", -5290,Stardrop Tea,filler,"RESOURCE_PACK", -5291,Resource Pack: 2 Artifact Trove,filler,"RESOURCE_PACK", -5292,Resource Pack: 20 Cinder Shard,filler,"GINGER_ISLAND,RESOURCE_PACK", +5000,Resource Pack: 500 Money,useful,"FILLER_MONEY", +5001,Resource Pack: 1000 Money,useful,"FILLER_MONEY", +5002,Resource Pack: 1500 Money,useful,"FILLER_MONEY", +5003,Resource Pack: 2000 Money,useful,"FILLER_MONEY", +5004,Resource Pack: 25 Stone,filler,"DEPRECATED,FILLER_MATERIALS", +5005,Resource Pack: 50 Stone,filler,"FILLER_MATERIALS", +5006,Resource Pack: 75 Stone,filler,"DEPRECATED,FILLER_MATERIALS", +5007,Resource Pack: 100 Stone,filler,"DEPRECATED,FILLER_MATERIALS", +5008,Resource Pack: 25 Wood,filler,"DEPRECATED,FILLER_MATERIALS", +5009,Resource Pack: 50 Wood,filler,"FILLER_MATERIALS", +5010,Resource Pack: 75 Wood,filler,"DEPRECATED,FILLER_MATERIALS", +5011,Resource Pack: 100 Wood,filler,"DEPRECATED,FILLER_MATERIALS", +5012,Resource Pack: 5 Hardwood,useful,"DEPRECATED,FILLER_MATERIALS", +5013,Resource Pack: 10 Hardwood,useful,"FILLER_MATERIALS", +5014,Resource Pack: 15 Hardwood,useful,"DEPRECATED,FILLER_MATERIALS", +5015,Resource Pack: 20 Hardwood,useful,"DEPRECATED,FILLER_MATERIALS", +5016,Resource Pack: 15 Fiber,filler,"DEPRECATED,FILLER_MATERIALS", +5017,Resource Pack: 30 Fiber,filler,"FILLER_MATERIALS", +5018,Resource Pack: 45 Fiber,filler,"DEPRECATED,FILLER_MATERIALS", +5019,Resource Pack: 60 Fiber,filler,"DEPRECATED,FILLER_MATERIALS", +5020,Resource Pack: 5 Coal,filler,"DEPRECATED,FILLER_MATERIALS", +5021,Resource Pack: 10 Coal,filler,"FILLER_MATERIALS", +5022,Resource Pack: 15 Coal,filler,"DEPRECATED,FILLER_MATERIALS", +5023,Resource Pack: 20 Coal,filler,"DEPRECATED,FILLER_MATERIALS", +5024,Resource Pack: 5 Clay,filler,"DEPRECATED,FILLER_MATERIALS", +5025,Resource Pack: 10 Clay,filler,"FILLER_MATERIALS", +5026,Resource Pack: 15 Clay,filler,"DEPRECATED,FILLER_MATERIALS", +5027,Resource Pack: 20 Clay,filler,"DEPRECATED,FILLER_MATERIALS", +5028,Resource Pack: 1 Warp Totem: Beach,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5029,Resource Pack: 3 Warp Totem: Beach,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5030,Resource Pack: 5 Warp Totem: Beach,filler,"FILLER_CONSUMABLE,WARP_TOTEM", +5031,Resource Pack: 7 Warp Totem: Beach,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5032,Resource Pack: 9 Warp Totem: Beach,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5033,Resource Pack: 10 Warp Totem: Beach,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5034,Resource Pack: 1 Warp Totem: Desert,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5035,Resource Pack: 3 Warp Totem: Desert,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5036,Resource Pack: 5 Warp Totem: Desert,filler,"FILLER_CONSUMABLE,WARP_TOTEM", +5037,Resource Pack: 7 Warp Totem: Desert,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5038,Resource Pack: 9 Warp Totem: Desert,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5039,Resource Pack: 10 Warp Totem: Desert,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5040,Resource Pack: 1 Warp Totem: Farm,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5041,Resource Pack: 3 Warp Totem: Farm,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5042,Resource Pack: 5 Warp Totem: Farm,filler,"FILLER_CONSUMABLE,WARP_TOTEM", +5043,Resource Pack: 7 Warp Totem: Farm,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5044,Resource Pack: 9 Warp Totem: Farm,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5045,Resource Pack: 10 Warp Totem: Farm,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5046,Resource Pack: 1 Warp Totem: Island,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM,GINGER_ISLAND", +5047,Resource Pack: 3 Warp Totem: Island,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM,GINGER_ISLAND", +5048,Resource Pack: 5 Warp Totem: Island,filler,"FILLER_CONSUMABLE,WARP_TOTEM,GINGER_ISLAND", +5049,Resource Pack: 7 Warp Totem: Island,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM,GINGER_ISLAND", +5050,Resource Pack: 9 Warp Totem: Island,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM,GINGER_ISLAND", +5051,Resource Pack: 10 Warp Totem: Island,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM,GINGER_ISLAND", +5052,Resource Pack: 1 Warp Totem: Mountains,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5053,Resource Pack: 3 Warp Totem: Mountains,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5054,Resource Pack: 5 Warp Totem: Mountains,filler,"FILLER_CONSUMABLE,WARP_TOTEM", +5055,Resource Pack: 7 Warp Totem: Mountains,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5056,Resource Pack: 9 Warp Totem: Mountains,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5057,Resource Pack: 10 Warp Totem: Mountains,filler,"DEPRECATED,FILLER_CONSUMABLE,WARP_TOTEM", +5058,Resource Pack: 6 Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5059,Resource Pack: 12 Geode,filler,"GEODE,FILLER_MATERIALS", +5060,Resource Pack: 18 Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5061,Resource Pack: 24 Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5062,Resource Pack: 4 Frozen Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5063,Resource Pack: 8 Frozen Geode,filler,"GEODE,FILLER_MATERIALS", +5064,Resource Pack: 12 Frozen Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5065,Resource Pack: 16 Frozen Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5066,Resource Pack: 3 Magma Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5067,Resource Pack: 6 Magma Geode,filler,"GEODE,FILLER_MATERIALS", +5068,Resource Pack: 9 Magma Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5069,Resource Pack: 12 Magma Geode,filler,"DEPRECATED,GEODE,FILLER_MATERIALS", +5070,Resource Pack: 2 Omni Geode,useful,"DEPRECATED,GEODE,FILLER_MATERIALS", +5071,Resource Pack: 4 Omni Geode,useful,"GEODE,FILLER_MATERIALS", +5072,Resource Pack: 6 Omni Geode,useful,"DEPRECATED,GEODE,FILLER_MATERIALS", +5073,Resource Pack: 8 Omni Geode,useful,"DEPRECATED,GEODE,FILLER_MATERIALS", +5074,Resource Pack: 25 Copper Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5075,Resource Pack: 50 Copper Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5076,Resource Pack: 75 Copper Ore,filler,"ORE,FILLER_MATERIALS", +5077,Resource Pack: 100 Copper Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5078,Resource Pack: 125 Copper Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5079,Resource Pack: 150 Copper Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5080,Resource Pack: 25 Iron Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5081,Resource Pack: 50 Iron Ore,filler,"ORE,FILLER_MATERIALS", +5082,Resource Pack: 75 Iron Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5083,Resource Pack: 100 Iron Ore,filler,"DEPRECATED,ORE,FILLER_MATERIALS", +5084,Resource Pack: 12 Gold Ore,useful,"DEPRECATED,ORE,FILLER_MATERIALS", +5085,Resource Pack: 25 Gold Ore,useful,"ORE,FILLER_MATERIALS", +5086,Resource Pack: 38 Gold Ore,useful,"DEPRECATED,ORE,FILLER_MATERIALS", +5087,Resource Pack: 50 Gold Ore,useful,"DEPRECATED,ORE,FILLER_MATERIALS", +5088,Resource Pack: 5 Iridium Ore,useful,"DEPRECATED,ORE,FILLER_MATERIALS", +5089,Resource Pack: 10 Iridium Ore,useful,"ORE,FILLER_MATERIALS", +5090,Resource Pack: 15 Iridium Ore,useful,"DEPRECATED,ORE,FILLER_MATERIALS", +5091,Resource Pack: 20 Iridium Ore,useful,"DEPRECATED,ORE,FILLER_MATERIALS", +5092,Resource Pack: 5 Quartz,filler,"ORE,FILLER_MATERIALS", +5093,Resource Pack: 10 Quartz,filler,"ORE,DEPRECATED,FILLER_MATERIALS", +5094,Resource Pack: 15 Quartz,filler,"ORE,DEPRECATED,FILLER_MATERIALS", +5095,Resource Pack: 20 Quartz,filler,"ORE,DEPRECATED,FILLER_MATERIALS", +5096,Resource Pack: 10 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5097,Resource Pack: 20 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5098,Resource Pack: 30 Basic Fertilizer,filler,"FERTILIZER,FILLER_FARMING", +5099,Resource Pack: 40 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5100,Resource Pack: 50 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5101,Resource Pack: 60 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5102,Resource Pack: 10 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5103,Resource Pack: 20 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5104,Resource Pack: 30 Basic Retaining Soil,filler,"FERTILIZER,FILLER_FARMING", +5105,Resource Pack: 40 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5106,Resource Pack: 50 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5107,Resource Pack: 60 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5108,Resource Pack: 10 Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5109,Resource Pack: 20 Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5110,Resource Pack: 30 Speed-Gro,filler,"FERTILIZER,FILLER_FARMING", +5111,Resource Pack: 40 Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5112,Resource Pack: 50 Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5113,Resource Pack: 60 Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5114,Resource Pack: 4 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5115,Resource Pack: 12 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5116,Resource Pack: 20 Quality Fertilizer,filler,"FERTILIZER,FILLER_FARMING", +5117,Resource Pack: 28 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5118,Resource Pack: 36 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5119,Resource Pack: 40 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5120,Resource Pack: 4 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5121,Resource Pack: 12 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5122,Resource Pack: 20 Quality Retaining Soil,filler,"FERTILIZER,FILLER_FARMING", +5123,Resource Pack: 28 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5124,Resource Pack: 36 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5125,Resource Pack: 40 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5126,Resource Pack: 4 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5127,Resource Pack: 12 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5128,Resource Pack: 20 Deluxe Speed-Gro,filler,"FERTILIZER,FILLER_FARMING", +5129,Resource Pack: 28 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5130,Resource Pack: 36 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5131,Resource Pack: 40 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5132,Resource Pack: 2 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5133,Resource Pack: 6 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5134,Resource Pack: 10 Deluxe Fertilizer,useful,"FERTILIZER,FILLER_FARMING", +5135,Resource Pack: 14 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5136,Resource Pack: 18 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5137,Resource Pack: 20 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5138,Resource Pack: 2 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5139,Resource Pack: 6 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5140,Resource Pack: 10 Deluxe Retaining Soil,useful,"FERTILIZER,FILLER_FARMING", +5141,Resource Pack: 14 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5142,Resource Pack: 18 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5143,Resource Pack: 20 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5144,Resource Pack: 2 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5145,Resource Pack: 6 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5146,Resource Pack: 10 Hyper Speed-Gro,useful,"FERTILIZER,FILLER_FARMING", +5147,Resource Pack: 14 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5148,Resource Pack: 18 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5149,Resource Pack: 20 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5150,Resource Pack: 2 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5151,Resource Pack: 6 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5152,Resource Pack: 10 Tree Fertilizer,filler,"FERTILIZER,FILLER_FARMING", +5153,Resource Pack: 14 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5154,Resource Pack: 18 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5155,Resource Pack: 20 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,FILLER_FARMING", +5156,Resource Pack: 10 Spring Seeds,filler,"DEPRECATED,FILLER_FARMING", +5157,Resource Pack: 20 Spring Seeds,filler,"DEPRECATED,FILLER_FARMING", +5158,Resource Pack: 30 Spring Seeds,filler,"FILLER_FARMING", +5159,Resource Pack: 40 Spring Seeds,filler,"DEPRECATED,FILLER_FARMING", +5160,Resource Pack: 50 Spring Seeds,filler,"DEPRECATED,FILLER_FARMING", +5161,Resource Pack: 60 Spring Seeds,filler,"DEPRECATED,FILLER_FARMING", +5162,Resource Pack: 10 Summer Seeds,filler,"DEPRECATED,FILLER_FARMING", +5163,Resource Pack: 20 Summer Seeds,filler,"DEPRECATED,FILLER_FARMING", +5164,Resource Pack: 30 Summer Seeds,filler,"FILLER_FARMING", +5165,Resource Pack: 40 Summer Seeds,filler,"DEPRECATED,FILLER_FARMING", +5166,Resource Pack: 50 Summer Seeds,filler,"DEPRECATED,FILLER_FARMING", +5167,Resource Pack: 60 Summer Seeds,filler,"DEPRECATED,FILLER_FARMING", +5168,Resource Pack: 10 Fall Seeds,filler,"DEPRECATED,FILLER_FARMING", +5169,Resource Pack: 20 Fall Seeds,filler,"DEPRECATED,FILLER_FARMING", +5170,Resource Pack: 30 Fall Seeds,filler,"FILLER_FARMING", +5171,Resource Pack: 40 Fall Seeds,filler,"DEPRECATED,FILLER_FARMING", +5172,Resource Pack: 50 Fall Seeds,filler,"DEPRECATED,FILLER_FARMING", +5173,Resource Pack: 60 Fall Seeds,filler,"DEPRECATED,FILLER_FARMING", +5174,Resource Pack: 10 Winter Seeds,filler,"DEPRECATED,FILLER_FARMING", +5175,Resource Pack: 20 Winter Seeds,filler,"DEPRECATED,FILLER_FARMING", +5176,Resource Pack: 30 Winter Seeds,filler,"FILLER_FARMING", +5177,Resource Pack: 40 Winter Seeds,filler,"DEPRECATED,FILLER_FARMING", +5178,Resource Pack: 50 Winter Seeds,filler,"DEPRECATED,FILLER_FARMING", +5179,Resource Pack: 60 Winter Seeds,filler,"DEPRECATED,FILLER_FARMING", +5180,Resource Pack: 1 Mahogany Seed,filler,"DEPRECATED,FILLER_FARMING", +5181,Resource Pack: 3 Mahogany Seed,filler,"DEPRECATED,FILLER_FARMING", +5182,Resource Pack: 5 Mahogany Seed,filler,"FILLER_FARMING", +5183,Resource Pack: 7 Mahogany Seed,filler,"DEPRECATED,FILLER_FARMING", +5184,Resource Pack: 9 Mahogany Seed,filler,"DEPRECATED,FILLER_FARMING", +5185,Resource Pack: 10 Mahogany Seed,filler,"DEPRECATED,FILLER_FARMING", +5186,Resource Pack: 10 Bait,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5187,Resource Pack: 20 Bait,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5188,Resource Pack: 30 Bait,filler,"FISHING_RESOURCE,FILLER_FISHING", +5189,Resource Pack: 40 Bait,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5190,Resource Pack: 50 Bait,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5191,Resource Pack: 60 Bait,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5192,Resource Pack: 1 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5193,Resource Pack: 2 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5194,Resource Pack: 3 Crab Pot,filler,"FISHING_RESOURCE,FILLER_FISHING", +5195,Resource Pack: 4 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5196,Resource Pack: 5 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5197,Resource Pack: 6 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,FILLER_FISHING", +5198,Friendship Bonus (1 <3),useful,"DEPRECATED,FRIENDSHIP_PACK", +5199,Friendship Bonus (2 <3),progression,"FRIENDSHIP_PACK,COMMUNITY_REWARD", +5200,Friendship Bonus (3 <3),useful,"DEPRECATED,FRIENDSHIP_PACK", +5201,Friendship Bonus (4 <3),useful,"DEPRECATED,FRIENDSHIP_PACK", +5202,15 Qi Gems,progression,"GINGER_ISLAND,FILLER_CURRENCY", +5203,Solar Panel,filler,"FILLER_MACHINE", +5204,Geode Crusher,filler,"FILLER_MACHINE", +5205,Farm Computer,filler,"FILLER_MACHINE", +5206,Bone Mill,filler,"FILLER_MACHINE", +5207,Fiber Seeds,filler,FILLER_FARMING, +5208,Chest,filler,"FILLER_STORAGE", +5209,Stone Chest,filler,"FILLER_STORAGE", +5210,Quality Bobber,filler,FILLER_FISHING, +5211,Mini-Obelisk,filler,"AT_LEAST_TWO,FILLER_QUALITY_OF_LIFE", +5212,Monster Musk,filler,FILLER_CONSUMABLE, +5213,Sprinkler,filler,"FILLER_FARMING", +5214,Quality Sprinkler,filler,"FILLER_FARMING", +5215,Iridium Sprinkler,filler,"FILLER_FARMING", +5216,Scarecrow,filler,FILLER_FARMING, +5217,Deluxe Scarecrow,useful,"FILLER_FARMING", +5218,Furnace,filler,"FILLER_MACHINE", +5219,Charcoal Kiln,filler,"FILLER_MACHINE", +5220,Lightning Rod,filler,"FILLER_MACHINE", +5221,Resource Pack: 5000 Money,useful,"FILLER_MONEY", +5222,Resource Pack: 10000 Money,useful,"FILLER_MONEY", +5223,Junimo Chest,filler,"AT_LEAST_TWO,FILLER_STORAGE", +5224,Horse Flute,useful,"MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +5225,Pierre's Missing Stocklist,useful,"MAXIMUM_ONE,FILLER_FARMING", +5226,Hopper,filler,"FILLER_MACHINE", +5227,Enricher,filler,"FILLER_FARMING", +5228,Pressure Nozzle,filler,"FILLER_FARMING", +5229,Deconstructor,filler,"FILLER_MACHINE", +5230,Key To The Town,useful,"MAXIMUM_ONE,FILLER_QUALITY_OF_LIFE", +5231,Galaxy Soul,filler,"GINGER_ISLAND,FILLER_MATERIALS", +5232,Mushroom Tree Seed,filler,"FILLER_FARMING", +5233,Resource Pack: 20 Magic Bait,filler,"FILLER_FISHING", +5234,Resource Pack: 10 Qi Seasoning,filler,"FILLER_CONSUMABLE", +5235,Mr. Qi's Hat,filler,"FILLER_HAT", +5236,Aquatic Sanctuary,filler,FILLER_DECORATION, +5237,Leprechaun Hat,filler,"FILLER_HAT", +5242,Exotic Double Bed,filler,FILLER_DECORATION, +5243,Resource Pack: 2 Qi Gem,filler,"GINGER_ISLAND,FILLER_CURRENCY,DEPRECATED", +5247,Fairy Dust,useful,"FILLER_CONSUMABLE", +5248,Seed Maker,useful,"FILLER_MACHINE", +5249,Keg,useful,"FILLER_MACHINE", +5250,Cask,useful,"FILLER_MACHINE", +5251,Preserves Jar,useful,"FILLER_MACHINE", +5252,Bee House,useful,"FILLER_MACHINE", +5253,Garden Pot,useful,"FILLER_FARMING", +5254,Cheese Press,useful,"FILLER_MACHINE", +5255,Mayonnaise Machine,useful,"FILLER_MACHINE", +5256,Loom,useful,"FILLER_MACHINE", +5257,Oil Maker,useful,"FILLER_MACHINE", +5258,Recycling Machine,useful,"FILLER_MACHINE", +5259,Worm Bin,useful,"FILLER_MACHINE", +5260,Tapper,useful,"FILLER_MACHINE", +5261,Heavy Tapper,useful,"FILLER_MACHINE", +5262,Slime Incubator,useful,FILLER_MACHINE, +5263,Slime Egg-Press,useful,FILLER_MACHINE, +5264,Crystalarium,useful,"FILLER_MACHINE", +5265,Ostrich Incubator,useful,"FILLER_MACHINE", +5266,Resource Pack: 5 Staircase,filler,"FILLER_CONSUMABLE", +5267,Auto-Petter,useful,"FILLER_QUALITY_OF_LIFE", +5268,Auto-Grabber,useful,"FILLER_QUALITY_OF_LIFE", +5269,Resource Pack: 10 Calico Egg,filler,"FILLER_CURRENCY", +5270,Resource Pack: 20 Calico Egg,filler,"FILLER_CURRENCY", +5272,Tent Kit,useful,"FILLER_QUALITY_OF_LIFE", +5273,Resource Pack: 4 Mystery Box,filler,"FILLER_CONSUMABLE", +5274,Prize Ticket,filler,"FILLER_CONSUMABLE", +5275,Resource Pack: 20 Deluxe Bait,filler,"FILLER_FISHING", +5276,Resource Pack: 2 Triple Shot Espresso,filler,"FILLER_BUFF_FOOD", +5277,Dish O' The Sea,filler,"FILLER_BUFF_FOOD", +5278,Seafoam Pudding,useful,"FILLER_BUFF_FOOD", +5279,Trap Bobber,useful,"FILLER_FISHING", +5280,Treasure Chest,filler,"FILLER_CURRENCY", +5281,Resource Pack: 15 Mixed Seeds,filler,"FILLER_FARMING", +5282,Resource Pack: 15 Mixed Flower Seeds,filler,"FILLER_FARMING", +5283,Resource Pack: 5 Cherry Bomb,filler,"FILLER_CONSUMABLE", +5284,Resource Pack: 3 Bomb,filler,"FILLER_CONSUMABLE", +5285,Resource Pack: 2 Mega Bomb,filler,"FILLER_CONSUMABLE", +5286,Resource Pack: 2 Life Elixir,filler,"FILLER_CONSUMABLE", +5287,Resource Pack: 5 Coffee,filler,"FILLER_BUFF_FOOD", +5289,Prismatic Shard,filler,"FILLER_MATERIALS", +5290,Stardrop Tea,filler,"FILLER_CONSUMABLE", +5291,Resource Pack: 2 Artifact Trove,filler,"FILLER_MATERIALS", +5292,Resource Pack: 20 Cinder Shard,filler,"GINGER_ISLAND,FILLER_CURRENCY", +5293,Workbench,useful,"FILLER_MACHINE", +5294,Big Chest,filler,"FILLER_STORAGE", +5295,Big Stone Chest,filler,"FILLER_STORAGE", +5296,Dehydrator,filler,"FILLER_MACHINE", +5297,Fish Smoker,filler,"FILLER_MACHINE", +5298,Heavy Furnace,filler,"FILLER_MACHINE", 10001,Luck Level,progression,SKILL_LEVEL_UP,Luck Skill 10002,Magic Level,progression,SKILL_LEVEL_UP,Magic 10003,Socializing Level,progression,SKILL_LEVEL_UP,Socializing Skill @@ -942,24 +1037,25 @@ id,name,classification,groups,mod_name 10610,T-Rex Skeleton L Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension 10611,T-Rex Skeleton M Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension 10612,T-Rex Skeleton R Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension -10701,Resource Pack: 3 Magic Elixir,filler,RESOURCE_PACK,Magic -10702,Resource Pack: 3 Travel Core,filler,RESOURCE_PACK,Magic -10703,Preservation Chamber,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology -10704,Hardwood Preservation Chamber,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology -10705,Resource Pack: 3 Water Shifter,filler,RESOURCE_PACK,Archaeology -10706,Resource Pack: 5 Hardwood Display,filler,RESOURCE_PACK,Archaeology -10707,Resource Pack: 5 Wooden Display,filler,RESOURCE_PACK,Archaeology -10708,Grinder,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology -10709,Ancient Battery Production Station,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology -10710,Hero Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10711,Aegis Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10712,Haste Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10713,Lightning Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10714,Armor Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10715,Gravity Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10716,Barbarian Elixir,filler,RESOURCE_PACK,Stardew Valley Expanded -10717,Restoration Table,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology -10718,Trash Bin,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Binning Skill -10719,Composter,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Binning Skill -10720,Recycling Bin,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Binning Skill -10721,Advanced Recycling Machine,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Binning Skill +10701,Resource Pack: 3 Magic Elixir,filler,FILLER_CONSUMABLE,Magic +10702,Resource Pack: 3 Travel Core,filler,FILLER_CONSUMABLE,Magic +10703,Preservation Chamber,filler,"FILLER_MACHINE",Archaeology +10704,Hardwood Preservation Chamber,filler,"FILLER_MACHINE",Archaeology +10705,Resource Pack: 3 Water Sifter,filler,FILLER_MACHINE,Archaeology +10706,Resource Pack: 5 Hardwood Display,filler,FILLER_MACHINE,Archaeology +10707,Resource Pack: 5 Wooden Display,filler,FILLER_MACHINE,Archaeology +10708,Grinder,filler,"FILLER_MACHINE",Archaeology +10709,Ancient Battery Production Station,filler,"FILLER_MACHINE",Archaeology +10710,Hero Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10711,Aegis Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10712,Haste Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10713,Lightning Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10714,Armor Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10715,Gravity Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10716,Barbarian Elixir,filler,FILLER_CONSUMABLE,Stardew Valley Expanded +10717,Restoration Table,filler,"FILLER_MACHINE",Archaeology +10718,Trash Bin,filler,"FILLER_MACHINE",Binning Skill +10719,Composter,filler,"FILLER_MACHINE",Binning Skill +10720,Recycling Bin,filler,"FILLER_MACHINE",Binning Skill +10721,Advanced Recycling Machine,filler,"FILLER_MACHINE",Binning Skill +10722,Digging Like Worms,filler,"FILLER_CONSUMABLE",Archaeology diff --git a/worlds/stardew_valley/data/locations.csv b/worlds/stardew_valley/data/locations.csv index 14554a3bcda2..9c6cffad519a 100644 --- a/worlds/stardew_valley/data/locations.csv +++ b/worlds/stardew_valley/data/locations.csv @@ -1,4 +1,4 @@ -id,region,name,tags,mod_name +id,region,name,tags,content_packs 1,Crafts Room,Spring Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE", 2,Crafts Room,Summer Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE", 3,Crafts Room,Fall Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE", @@ -98,8 +98,8 @@ id,region,name,tags,mod_name 98,Vault,Carnival Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE", 99,Vault,Walnut Hunter Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE", 100,Vault,Qi's Helper Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE", -101,Pierre's General Store,Large Pack,BACKPACK, -102,Pierre's General Store,Deluxe Pack,BACKPACK, +101,Pierre's General Store,Large Pack,"BACKPACK,BACKPACK_TIER", +102,Pierre's General Store,Deluxe Pack,"BACKPACK,BACKPACK_TIER", 103,Blacksmith Copper Upgrades,Copper Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", 104,Blacksmith Iron Upgrades,Iron Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", 105,Blacksmith Gold Upgrades,Gold Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", @@ -138,6 +138,43 @@ id,region,name,tags,mod_name 158,Bulletin Board,Raccoon Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE", 159,Crafts Room,Green Rain Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE", 160,Fish Tank,Specific Fishing Bundle,"FISH_TANK_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE", +161,Pierre's General Store,Small Pack,"BACKPACK,BACKPACK_TIER,STARTING_TOOLS", +162,Pierre's General Store,Small Pack 1,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +163,Pierre's General Store,Small Pack 2,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +164,Pierre's General Store,Small Pack 3,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +165,Pierre's General Store,Small Pack 4,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +166,Pierre's General Store,Small Pack 5,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +167,Pierre's General Store,Small Pack 6,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +168,Pierre's General Store,Small Pack 7,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +169,Pierre's General Store,Small Pack 8,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +170,Pierre's General Store,Small Pack 9,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +171,Pierre's General Store,Small Pack 10,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +172,Pierre's General Store,Small Pack 11,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +174,Pierre's General Store,Small Pack 12,"BACKPACK,SPLIT_BACKPACK,STARTING_TOOLS", +175,Pierre's General Store,Large Pack 1,"BACKPACK,SPLIT_BACKPACK", +176,Pierre's General Store,Large Pack 2,"BACKPACK,SPLIT_BACKPACK", +177,Pierre's General Store,Large Pack 3,"BACKPACK,SPLIT_BACKPACK", +178,Pierre's General Store,Large Pack 4,"BACKPACK,SPLIT_BACKPACK", +179,Pierre's General Store,Large Pack 5,"BACKPACK,SPLIT_BACKPACK", +180,Pierre's General Store,Large Pack 6,"BACKPACK,SPLIT_BACKPACK", +181,Pierre's General Store,Large Pack 7,"BACKPACK,SPLIT_BACKPACK", +182,Pierre's General Store,Large Pack 8,"BACKPACK,SPLIT_BACKPACK", +183,Pierre's General Store,Large Pack 9,"BACKPACK,SPLIT_BACKPACK", +184,Pierre's General Store,Large Pack 10,"BACKPACK,SPLIT_BACKPACK", +185,Pierre's General Store,Large Pack 11,"BACKPACK,SPLIT_BACKPACK", +187,Pierre's General Store,Large Pack 12,"BACKPACK,SPLIT_BACKPACK", +188,Pierre's General Store,Deluxe Pack 1,"BACKPACK,SPLIT_BACKPACK", +189,Pierre's General Store,Deluxe Pack 2,"BACKPACK,SPLIT_BACKPACK", +190,Pierre's General Store,Deluxe Pack 3,"BACKPACK,SPLIT_BACKPACK", +191,Pierre's General Store,Deluxe Pack 4,"BACKPACK,SPLIT_BACKPACK", +192,Pierre's General Store,Deluxe Pack 5,"BACKPACK,SPLIT_BACKPACK", +193,Pierre's General Store,Deluxe Pack 6,"BACKPACK,SPLIT_BACKPACK", +194,Pierre's General Store,Deluxe Pack 7,"BACKPACK,SPLIT_BACKPACK", +195,Pierre's General Store,Deluxe Pack 8,"BACKPACK,SPLIT_BACKPACK", +196,Pierre's General Store,Deluxe Pack 9,"BACKPACK,SPLIT_BACKPACK", +197,Pierre's General Store,Deluxe Pack 10,"BACKPACK,SPLIT_BACKPACK", +198,Pierre's General Store,Deluxe Pack 11,"BACKPACK,SPLIT_BACKPACK", +199,Pierre's General Store,Deluxe Pack 12,"BACKPACK,SPLIT_BACKPACK", 201,The Mines - Floor 10,The Mines Floor 10 Treasure,"MANDATORY,THE_MINES_TREASURE", 202,The Mines - Floor 20,The Mines Floor 20 Treasure,"MANDATORY,THE_MINES_TREASURE", 203,The Mines - Floor 40,The Mines Floor 40 Treasure,"MANDATORY,THE_MINES_TREASURE", @@ -149,7 +186,7 @@ id,region,name,tags,mod_name 209,The Mines - Floor 100,The Mines Floor 100 Treasure,"MANDATORY,THE_MINES_TREASURE", 210,The Mines - Floor 110,The Mines Floor 110 Treasure,"MANDATORY,THE_MINES_TREASURE", 211,The Mines - Floor 120,The Mines Floor 120 Treasure,"MANDATORY,THE_MINES_TREASURE", -212,Quarry Mine,Grim Reaper statue,MANDATORY, +212,Quarry Mine,Grim Reaper Statue,MANDATORY, 213,The Mines,The Mines Entrance Cutscene,MANDATORY, 214,The Mines - Floor 5,Floor 5 Elevator,ELEVATOR, 215,The Mines - Floor 10,Floor 10 Elevator,ELEVATOR, @@ -177,6 +214,12 @@ id,region,name,tags,mod_name 237,The Mines - Floor 120,Floor 120 Elevator,ELEVATOR, 250,Shipping,Demetrius's Breakthrough,MANDATORY, 251,Volcano - Floor 10,Volcano Caldera Treasure,"GINGER_ISLAND,MANDATORY", +252,Town,Rat Problem Cutscene,MANDATORY, +253,Trash Bear,Trash Bear Foraging,TRASH_BEAR, +254,Trash Bear,Trash Bear Cooking,TRASH_BEAR, +255,Trash Bear,Trash Bear Farming,TRASH_BEAR, +256,Trash Bear,Trash Bear Fishing,TRASH_BEAR, +261,Crafts Room,Totems Bundle,"CRAFTS_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE", 301,Farm,Level 1 Farming,"FARMING_LEVEL,SKILL_LEVEL", 302,Farm,Level 2 Farming,"FARMING_LEVEL,SKILL_LEVEL", 303,Farm,Level 3 Farming,"FARMING_LEVEL,SKILL_LEVEL", @@ -250,56 +293,58 @@ id,region,name,tags,mod_name 416,Carpenter Shop,Kitchen Blueprint,BUILDING_BLUEPRINT, 417,Carpenter Shop,Kids Room Blueprint,BUILDING_BLUEPRINT, 418,Carpenter Shop,Cellar Blueprint,BUILDING_BLUEPRINT, -501,Town,Introductions,"STORY_QUEST", -502,Town,How To Win Friends,"STORY_QUEST", -503,Farm,Getting Started,"STORY_QUEST", -504,Farm,Raising Animals,"STORY_QUEST", -505,Farm,Advancement,"STORY_QUEST", -506,Museum,Archaeology,"STORY_QUEST", -507,Wizard Tower,Meet The Wizard,"STORY_QUEST", -508,The Mines - Floor 5,Forging Ahead,"STORY_QUEST", -509,The Mines - Floor 10,Smelting,"STORY_QUEST", -510,The Mines - Floor 15,Initiation,"STORY_QUEST", -511,Mountain,Robin's Lost Axe,"STORY_QUEST", -512,Town,Jodi's Request,"STORY_QUEST", -513,Town,"Mayor's ""Shorts""","STORY_QUEST", -514,Mountain,Blackberry Basket,"STORY_QUEST", -515,Marnie's Ranch,Marnie's Request,"STORY_QUEST", -516,Town,Pam Is Thirsty,"STORY_QUEST", -517,Wizard Tower,A Dark Reagent,"STORY_QUEST", -518,Forest,Cow's Delight,"STORY_QUEST", -519,Skull Cavern Entrance,The Skull Key,"STORY_QUEST", -520,Mountain,Crop Research,"STORY_QUEST", -521,Alex's House,Knee Therapy,"STORY_QUEST", -522,Mountain,Robin's Request,"STORY_QUEST", -523,Skull Cavern Floor 25,Qi's Challenge,"STORY_QUEST", -524,Desert,The Mysterious Qi,"STORY_QUEST", -525,Town,Carving Pumpkins,"STORY_QUEST", -526,Town,A Winter Mystery,"STORY_QUEST", -527,Secret Woods,Strange Note,"STORY_QUEST", -528,Skull Cavern Floor 100,Cryptic Note,"STORY_QUEST", -529,Town,Fresh Fruit,"STORY_QUEST", -530,Mountain,Aquatic Research,"STORY_QUEST", -531,Town,A Soldier's Star,"STORY_QUEST", -532,Town,Mayor's Need,"STORY_QUEST", -533,Saloon,Wanted: Lobster,"STORY_QUEST", -534,Town,Pam Needs Juice,"STORY_QUEST", -535,Sam's House,Fish Casserole,"STORY_QUEST", -536,Beach,Catch A Squid,"STORY_QUEST", -537,Saloon,Fish Stew,"STORY_QUEST", -538,Pierre's General Store,Pierre's Notice,"STORY_QUEST", -539,Clint's Blacksmith,Clint's Attempt,"STORY_QUEST", -540,Town,A Favor For Clint,"STORY_QUEST", -541,Wizard Tower,Staff Of Power,"STORY_QUEST", -542,Town,Granny's Gift,"STORY_QUEST", -543,Desert,Exotic Spirits,"STORY_QUEST", -544,Fishing,Catch a Lingcod,"STORY_QUEST", -545,Island West,The Pirate's Wife,"GINGER_ISLAND,STORY_QUEST", -546,Mutant Bug Lair,Dark Talisman,"STORY_QUEST", -547,Witch's Swamp,Goblin Problem,"STORY_QUEST", -548,Witch's Hut,Magic Ink,"STORY_QUEST", -549,Forest,The Giant Stump,"STORY_QUEST", -550,Farm,Feeding Animals,"STORY_QUEST", +419,Carpenter Shop,Pet Bowl Blueprint,BUILDING_BLUEPRINT, +501,Town,Quest: Introductions,"STORY_QUEST", +502,Town,Quest: How To Win Friends,"STORY_QUEST", +503,Farm,Quest: Getting Started,"STORY_QUEST", +504,Farm,Quest: Raising Animals,"STORY_QUEST", +505,Farm,Quest: Advancement,"STORY_QUEST", +506,Museum,Quest: Archaeology,"STORY_QUEST", +507,Wizard Tower,Quest: Meet The Wizard,"STORY_QUEST", +508,The Mines - Floor 5,Quest: Forging Ahead,"STORY_QUEST", +509,The Mines - Floor 10,Quest: Smelting,"STORY_QUEST", +510,The Mines - Floor 15,Quest: Initiation,"STORY_QUEST", +511,Mountain,Quest: Robin's Lost Axe,"STORY_QUEST", +512,Town,Quest: Jodi's Request,"STORY_QUEST", +513,Town,"Quest: Mayor's ""Shorts""","STORY_QUEST", +514,Mountain,Quest: Blackberry Basket,"STORY_QUEST", +515,Marnie's Ranch,Quest: Marnie's Request,"STORY_QUEST", +516,Town,Quest: Pam Is Thirsty,"STORY_QUEST", +517,Wizard Tower,Quest: A Dark Reagent,"STORY_QUEST", +518,Forest,Quest: Cow's Delight,"STORY_QUEST", +519,Skull Cavern Entrance,Quest: The Skull Key,"STORY_QUEST", +520,Mountain,Quest: Crop Research,"STORY_QUEST", +521,Alex's House,Quest: Knee Therapy,"STORY_QUEST", +522,Mountain,Quest: Robin's Request,"STORY_QUEST", +523,Skull Cavern Floor 25,Quest: Qi's Challenge,"STORY_QUEST", +524,Desert,Quest: The Mysterious Qi,"STORY_QUEST", +525,Town,Quest: Carving Pumpkins,"STORY_QUEST", +526,Town,Quest: A Winter Mystery,"STORY_QUEST", +527,Secret Woods,Quest: Strange Note,"STORY_QUEST", +528,Skull Cavern Floor 100,Quest: Cryptic Note,"STORY_QUEST", +529,Town,Quest: Fresh Fruit,"STORY_QUEST", +530,Mountain,Quest: Aquatic Research,"STORY_QUEST", +531,Town,Quest: A Soldier's Star,"STORY_QUEST", +532,Town,Quest: Mayor's Need,"STORY_QUEST", +533,Saloon,Quest: Wanted: Lobster,"STORY_QUEST", +534,Town,Quest: Pam Needs Juice,"STORY_QUEST", +535,Sam's House,Quest: Fish Casserole,"STORY_QUEST", +536,Beach,Quest: Catch A Squid,"STORY_QUEST", +537,Saloon,Quest: Fish Stew,"STORY_QUEST", +538,Pierre's General Store,Quest: Pierre's Notice,"STORY_QUEST", +539,Clint's Blacksmith,Quest: Clint's Attempt,"STORY_QUEST", +540,Town,Quest: A Favor For Clint,"STORY_QUEST", +541,Wizard Tower,Quest: Staff Of Power,"STORY_QUEST", +542,Town,Quest: Granny's Gift,"STORY_QUEST", +543,Desert,Quest: Exotic Spirits,"STORY_QUEST", +544,Fishing,Quest: Catch a Lingcod,"STORY_QUEST", +545,Island West,Quest: The Pirate's Wife,"GINGER_ISLAND,STORY_QUEST", +546,Mutant Bug Lair,Quest: Dark Talisman,"STORY_QUEST", +547,Witch's Swamp,Quest: Goblin Problem,"STORY_QUEST", +548,Witch's Hut,Quest: Magic Ink,"STORY_QUEST", +549,Forest,Quest: The Giant Stump,"STORY_QUEST", +550,Farm,Quest: Feeding Animals,"STORY_QUEST", +551,Community Center,Quest: Rat Problem,"STORY_QUEST", 601,JotPK World 1,JotPK: Boots 1,"ARCADE_MACHINE,JOTPK", 602,JotPK World 1,JotPK: Boots 2,"ARCADE_MACHINE,JOTPK", 603,JotPK World 1,JotPK: Gun 1,"ARCADE_MACHINE,JOTPK", @@ -321,14 +366,14 @@ id,region,name,tags,mod_name 619,Junimo Kart 3,Junimo Kart: Red Hot Rollercoaster,"ARCADE_MACHINE,JUNIMO_KART", 620,JotPK World 3,Journey of the Prairie King Victory,"ARCADE_MACHINE_VICTORY,JOTPK", 621,Junimo Kart 4,Junimo Kart: Sunset Speedway (Victory),"ARCADE_MACHINE_VICTORY,JUNIMO_KART", -701,Secret Woods,Old Master Cannoli,MANDATORY, +701,Secret Woods,Secret: Old Master Cannoli,"SECRETSANITY,EASY_SECRET", 702,Beach,Beach Bridge Repair,MANDATORY, 703,Desert,Galaxy Sword Shrine,MANDATORY, 704,Farmhouse,Have a Baby,BABY, 705,Farmhouse,Have Another Baby,BABY, 706,Farmhouse,Spouse Stardrop,, 707,Sewer,Krobus Stardrop,MANDATORY, -708,Forest,Pot Of Gold,MANDATORY, +708,Forest,Secret: Pot Of Gold,"SECRETSANITY,EASY_SECRET", 801,Forest,Help Wanted: Gathering 1,HELP_WANTED, 802,Forest,Help Wanted: Gathering 2,HELP_WANTED, 803,Forest,Help Wanted: Gathering 3,HELP_WANTED, @@ -386,26 +431,61 @@ id,region,name,tags,mod_name 871,Town,Help Wanted: Item Delivery 31,HELP_WANTED, 872,Town,Help Wanted: Item Delivery 32,HELP_WANTED, 901,Traveling Cart Sunday,Traveling Merchant Sunday Item 1,"MANDATORY,TRAVELING_MERCHANT", -902,Traveling Cart Sunday,Traveling Merchant Sunday Item 2,"MANDATORY,TRAVELING_MERCHANT", -903,Traveling Cart Sunday,Traveling Merchant Sunday Item 3,"MANDATORY,TRAVELING_MERCHANT", +902,Traveling Cart Sunday,Traveling Merchant Sunday Item 2,"TRAVELING_MERCHANT", +903,Traveling Cart Sunday,Traveling Merchant Sunday Item 3,"TRAVELING_MERCHANT", +904,Traveling Cart Sunday,Traveling Merchant Sunday Item 4,"TRAVELING_MERCHANT", +905,Traveling Cart Sunday,Traveling Merchant Sunday Item 5,"TRAVELING_MERCHANT", +906,Traveling Cart Sunday,Traveling Merchant Sunday Item 6,"TRAVELING_MERCHANT", +907,Traveling Cart Sunday,Traveling Merchant Sunday Item 7,"TRAVELING_MERCHANT", +908,Traveling Cart Sunday,Traveling Merchant Sunday Item 8,"TRAVELING_MERCHANT", 911,Traveling Cart Monday,Traveling Merchant Monday Item 1,"MANDATORY,TRAVELING_MERCHANT", -912,Traveling Cart Monday,Traveling Merchant Monday Item 2,"MANDATORY,TRAVELING_MERCHANT", -913,Traveling Cart Monday,Traveling Merchant Monday Item 3,"MANDATORY,TRAVELING_MERCHANT", +912,Traveling Cart Monday,Traveling Merchant Monday Item 2,"TRAVELING_MERCHANT", +913,Traveling Cart Monday,Traveling Merchant Monday Item 3,"TRAVELING_MERCHANT", +914,Traveling Cart Monday,Traveling Merchant Monday Item 4,"TRAVELING_MERCHANT", +915,Traveling Cart Monday,Traveling Merchant Monday Item 5,"TRAVELING_MERCHANT", +916,Traveling Cart Monday,Traveling Merchant Monday Item 6,"TRAVELING_MERCHANT", +917,Traveling Cart Monday,Traveling Merchant Monday Item 7,"TRAVELING_MERCHANT", +918,Traveling Cart Monday,Traveling Merchant Monday Item 8,"TRAVELING_MERCHANT", 921,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 1,"MANDATORY,TRAVELING_MERCHANT", -922,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 2,"MANDATORY,TRAVELING_MERCHANT", -923,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 3,"MANDATORY,TRAVELING_MERCHANT", +922,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 2,"TRAVELING_MERCHANT", +923,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 3,"TRAVELING_MERCHANT", +924,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 4,"TRAVELING_MERCHANT", +925,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 5,"TRAVELING_MERCHANT", +926,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 6,"TRAVELING_MERCHANT", +927,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 7,"TRAVELING_MERCHANT", +928,Traveling Cart Tuesday,Traveling Merchant Tuesday Item 8,"TRAVELING_MERCHANT", 931,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 1,"MANDATORY,TRAVELING_MERCHANT", -932,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 2,"MANDATORY,TRAVELING_MERCHANT", -933,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 3,"MANDATORY,TRAVELING_MERCHANT", +932,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 2,"TRAVELING_MERCHANT", +933,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 3,"TRAVELING_MERCHANT", +934,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 4,"TRAVELING_MERCHANT", +935,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 5,"TRAVELING_MERCHANT", +936,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 6,"TRAVELING_MERCHANT", +937,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 7,"TRAVELING_MERCHANT", +938,Traveling Cart Wednesday,Traveling Merchant Wednesday Item 8,"TRAVELING_MERCHANT", 941,Traveling Cart Thursday,Traveling Merchant Thursday Item 1,"MANDATORY,TRAVELING_MERCHANT", -942,Traveling Cart Thursday,Traveling Merchant Thursday Item 2,"MANDATORY,TRAVELING_MERCHANT", -943,Traveling Cart Thursday,Traveling Merchant Thursday Item 3,"MANDATORY,TRAVELING_MERCHANT", +942,Traveling Cart Thursday,Traveling Merchant Thursday Item 2,"TRAVELING_MERCHANT", +943,Traveling Cart Thursday,Traveling Merchant Thursday Item 3,"TRAVELING_MERCHANT", +944,Traveling Cart Thursday,Traveling Merchant Thursday Item 4,"TRAVELING_MERCHANT", +945,Traveling Cart Thursday,Traveling Merchant Thursday Item 5,"TRAVELING_MERCHANT", +946,Traveling Cart Thursday,Traveling Merchant Thursday Item 6,"TRAVELING_MERCHANT", +947,Traveling Cart Thursday,Traveling Merchant Thursday Item 7,"TRAVELING_MERCHANT", +948,Traveling Cart Thursday,Traveling Merchant Thursday Item 8,"TRAVELING_MERCHANT", 951,Traveling Cart Friday,Traveling Merchant Friday Item 1,"MANDATORY,TRAVELING_MERCHANT", -952,Traveling Cart Friday,Traveling Merchant Friday Item 2,"MANDATORY,TRAVELING_MERCHANT", -953,Traveling Cart Friday,Traveling Merchant Friday Item 3,"MANDATORY,TRAVELING_MERCHANT", +952,Traveling Cart Friday,Traveling Merchant Friday Item 2,"TRAVELING_MERCHANT", +953,Traveling Cart Friday,Traveling Merchant Friday Item 3,"TRAVELING_MERCHANT", +954,Traveling Cart Friday,Traveling Merchant Friday Item 4,"TRAVELING_MERCHANT", +955,Traveling Cart Friday,Traveling Merchant Friday Item 5,"TRAVELING_MERCHANT", +956,Traveling Cart Friday,Traveling Merchant Friday Item 6,"TRAVELING_MERCHANT", +957,Traveling Cart Friday,Traveling Merchant Friday Item 7,"TRAVELING_MERCHANT", +958,Traveling Cart Friday,Traveling Merchant Friday Item 8,"TRAVELING_MERCHANT", 961,Traveling Cart Saturday,Traveling Merchant Saturday Item 1,"MANDATORY,TRAVELING_MERCHANT", -962,Traveling Cart Saturday,Traveling Merchant Saturday Item 2,"MANDATORY,TRAVELING_MERCHANT", -963,Traveling Cart Saturday,Traveling Merchant Saturday Item 3,"MANDATORY,TRAVELING_MERCHANT", +962,Traveling Cart Saturday,Traveling Merchant Saturday Item 2,"TRAVELING_MERCHANT", +963,Traveling Cart Saturday,Traveling Merchant Saturday Item 3,"TRAVELING_MERCHANT", +964,Traveling Cart Saturday,Traveling Merchant Saturday Item 4,"TRAVELING_MERCHANT", +965,Traveling Cart Saturday,Traveling Merchant Saturday Item 5,"TRAVELING_MERCHANT", +966,Traveling Cart Saturday,Traveling Merchant Saturday Item 6,"TRAVELING_MERCHANT", +967,Traveling Cart Saturday,Traveling Merchant Saturday Item 7,"TRAVELING_MERCHANT", +968,Traveling Cart Saturday,Traveling Merchant Saturday Item 8,"TRAVELING_MERCHANT", 1001,Fishing,Fishsanity: Carp,FISHSANITY, 1002,Fishing,Fishsanity: Herring,FISHSANITY, 1003,Fishing,Fishsanity: Smallmouth Bass,FISHSANITY, @@ -1022,7 +1102,7 @@ id,region,name,tags,mod_name 2012,Spirit's Eve,Rarecrow #2 (Witch),FESTIVAL, 2013,Festival of Ice,Win Fishing Competition,FESTIVAL, 2014,Festival of Ice,Rarecrow #4 (Snowman),FESTIVAL, -2015,Night Market,Mermaid Pearl,FESTIVAL, +2015,Night Market,Mermaid Show,FESTIVAL, 2016,Night Market,Cone Hat,FESTIVAL_HARD, 2017,Night Market,Iridium Fireplace,FESTIVAL_HARD, 2018,Night Market,Rarecrow #7 (Tanuki),FESTIVAL, @@ -1048,27 +1128,27 @@ id,region,name,tags,mod_name 2042,Desert Festival,Mummy Mask,FESTIVAL_HARD, 2043,Desert Festival,Calico Statue,FESTIVAL, 2044,Desert Festival,Emily's Outfit Services,FESTIVAL, -2045,Desert Festival,Earthy Mousse,DESERT_FESTIVAL_CHEF, -2046,Desert Festival,Sweet Bean Cake,DESERT_FESTIVAL_CHEF, -2047,Desert Festival,Skull Cave Casserole,DESERT_FESTIVAL_CHEF, -2048,Desert Festival,Spicy Tacos,DESERT_FESTIVAL_CHEF, -2049,Desert Festival,Mountain Chili,DESERT_FESTIVAL_CHEF, -2050,Desert Festival,Crystal Cake,DESERT_FESTIVAL_CHEF, -2051,Desert Festival,Cave Kebab,DESERT_FESTIVAL_CHEF, -2052,Desert Festival,Hot Log,DESERT_FESTIVAL_CHEF, -2053,Desert Festival,Sour Salad,DESERT_FESTIVAL_CHEF, -2054,Desert Festival,Superfood Cake,DESERT_FESTIVAL_CHEF, -2055,Desert Festival,Warrior Smoothie,DESERT_FESTIVAL_CHEF, -2056,Desert Festival,Rumpled Fruit Skin,DESERT_FESTIVAL_CHEF, -2057,Desert Festival,Calico Pizza,DESERT_FESTIVAL_CHEF, -2058,Desert Festival,Stuffed Mushrooms,DESERT_FESTIVAL_CHEF, -2059,Desert Festival,Elf Quesadilla,DESERT_FESTIVAL_CHEF, -2060,Desert Festival,Nachos Of The Desert,DESERT_FESTIVAL_CHEF, -2061,Desert Festival,Cioppino,DESERT_FESTIVAL_CHEF, -2062,Desert Festival,Rainforest Shrimp,DESERT_FESTIVAL_CHEF, -2063,Desert Festival,Shrimp Donut,DESERT_FESTIVAL_CHEF, -2064,Desert Festival,Smell Of The Sea,DESERT_FESTIVAL_CHEF, -2065,Desert Festival,Desert Gumbo,DESERT_FESTIVAL_CHEF, +2045,Desert Festival,Desert Chef: Earthy Mousse,DESERT_FESTIVAL_CHEF_MEAL, +2046,Desert Festival,Desert Chef: Sweet Bean Cake,DESERT_FESTIVAL_CHEF_MEAL, +2047,Desert Festival,Desert Chef: Skull Cave Casserole,DESERT_FESTIVAL_CHEF_MEAL, +2048,Desert Festival,Desert Chef: Spicy Tacos,DESERT_FESTIVAL_CHEF_MEAL, +2049,Desert Festival,Desert Chef: Mountain Chili,DESERT_FESTIVAL_CHEF_MEAL, +2050,Desert Festival,Desert Chef: Crystal Cake,DESERT_FESTIVAL_CHEF_MEAL, +2051,Desert Festival,Desert Chef: Cave Kebab,DESERT_FESTIVAL_CHEF_MEAL, +2052,Desert Festival,Desert Chef: Hot Log,DESERT_FESTIVAL_CHEF_MEAL, +2053,Desert Festival,Desert Chef: Sour Salad,DESERT_FESTIVAL_CHEF_MEAL, +2054,Desert Festival,Desert Chef: Superfood Cake,DESERT_FESTIVAL_CHEF_MEAL, +2055,Desert Festival,Desert Chef: Warrior Smoothie,DESERT_FESTIVAL_CHEF_MEAL, +2056,Desert Festival,Desert Chef: Rumpled Fruit Skin,DESERT_FESTIVAL_CHEF_MEAL, +2057,Desert Festival,Desert Chef: Calico Pizza,DESERT_FESTIVAL_CHEF_MEAL, +2058,Desert Festival,Desert Chef: Stuffed Mushrooms,DESERT_FESTIVAL_CHEF_MEAL, +2059,Desert Festival,Desert Chef: Elf Quesadilla,DESERT_FESTIVAL_CHEF_MEAL, +2060,Desert Festival,Desert Chef: Nachos Of The Desert,DESERT_FESTIVAL_CHEF_MEAL, +2061,Desert Festival,Desert Chef: Cioppino,DESERT_FESTIVAL_CHEF_MEAL, +2062,Desert Festival,Desert Chef: Rainforest Shrimp,DESERT_FESTIVAL_CHEF_MEAL, +2063,Desert Festival,Desert Chef: Shrimp Donut,DESERT_FESTIVAL_CHEF_MEAL, +2064,Desert Festival,Desert Chef: Smell Of The Sea,DESERT_FESTIVAL_CHEF_MEAL, +2065,Desert Festival,Desert Chef: Desert Gumbo,DESERT_FESTIVAL_CHEF_MEAL, 2066,Desert Festival,Free Cactis,FESTIVAL, 2067,Desert Festival,Monster Hunt,FESTIVAL_HARD, 2068,Desert Festival,Deep Dive,FESTIVAL_HARD, @@ -1095,6 +1175,7 @@ id,region,name,tags,mod_name 2089,SquidFest,SquidFest Day 2 Iron,FESTIVAL, 2090,SquidFest,SquidFest Day 2 Gold,FESTIVAL_HARD, 2091,SquidFest,SquidFest Day 2 Iridium,FESTIVAL_HARD, +2092,Desert Festival,Desert Chef,"DESERT_FESTIVAL_CHEF", 2101,Town,Island Ingredients,"GINGER_ISLAND,SPECIAL_ORDER_BOARD", 2102,The Mines - Floor 75,Cave Patrol,SPECIAL_ORDER_BOARD, 2103,Fishing,Aquatic Overpopulation,SPECIAL_ORDER_BOARD, @@ -1731,8 +1812,8 @@ id,region,name,tags,mod_name 2937,Shipping,Shipsanity: Deluxe Fertilizer,"GINGER_ISLAND,SHIPSANITY", 2938,Shipping,Shipsanity: Deluxe Retaining Soil,"GINGER_ISLAND,SHIPSANITY", 2939,Shipping,Shipsanity: Fairy Dust,"GINGER_ISLAND,SHIPSANITY", -2940,Shipping,Shipsanity: Hyper Speed-Gro,"GINGER_ISLAND,SHIPSANITY", -2941,Shipping,Shipsanity: Magic Bait,"GINGER_ISLAND,SHIPSANITY", +2940,Shipping,Shipsanity: Hyper Speed-Gro,"GINGER_ISLAND,SHIPSANITY,REQUIRES_QI_ORDERS", +2941,Shipping,Shipsanity: Magic Bait,"GINGER_ISLAND,SHIPSANITY,REQUIRES_QI_ORDERS", 2942,Shipping,Shipsanity: Banana,"GINGER_ISLAND,SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT", 2943,Shipping,Shipsanity: Mango,"GINGER_ISLAND,SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT", 2944,Shipping,Shipsanity: Pineapple,"GINGER_ISLAND,SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT", @@ -1793,10 +1874,10 @@ id,region,name,tags,mod_name 3033,Adventurer's Guild,Monster Eradication: 20 Skeletons,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", 3034,Adventurer's Guild,Monster Eradication: 30 Skeletons,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", 3035,Adventurer's Guild,Monster Eradication: 40 Skeletons,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", -3036,Adventurer's Guild,Monster Eradication: 25 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", -3037,Adventurer's Guild,Monster Eradication: 50 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", -3038,Adventurer's Guild,Monster Eradication: 75 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", -3039,Adventurer's Guild,Monster Eradication: 100 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", +3036,Adventurer's Guild,Monster Eradication: 16 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", +3037,Adventurer's Guild,Monster Eradication: 32 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", +3038,Adventurer's Guild,Monster Eradication: 48 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", +3039,Adventurer's Guild,Monster Eradication: 64 Cave Insects,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", 3040,Adventurer's Guild,Monster Eradication: 6 Duggies,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", 3041,Adventurer's Guild,Monster Eradication: 12 Duggies,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", 3042,Adventurer's Guild,Monster Eradication: 18 Duggies,"MONSTERSANITY,MONSTERSANITY_PROGRESSIVE_GOALS", @@ -2014,194 +2095,194 @@ id,region,name,tags,mod_name 3379,The Queen of Sauce,Trout Soup Recipe,"CHEFSANITY,CHEFSANITY_QOS", 3380,Farm,Vegetable Medley Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP", 3381,Farm,Moss Soup Recipe,"CHEFSANITY,CHEFSANITY_SKILL", -3401,Farm,Craft Cherry Bomb,CRAFTSANITY, -3402,Farm,Craft Bomb,CRAFTSANITY, -3403,Farm,Craft Mega Bomb,CRAFTSANITY, -3404,Farm,Craft Gate,CRAFTSANITY, -3405,Farm,Craft Wood Fence,CRAFTSANITY, -3406,Farm,Craft Stone Fence,CRAFTSANITY, -3407,Farm,Craft Iron Fence,CRAFTSANITY, -3408,Farm,Craft Hardwood Fence,CRAFTSANITY, -3409,Farm,Craft Sprinkler,CRAFTSANITY, -3410,Farm,Craft Quality Sprinkler,CRAFTSANITY, -3411,Farm,Craft Iridium Sprinkler,CRAFTSANITY, -3412,Farm,Craft Bee House,CRAFTSANITY, -3413,Farm,Craft Cask,CRAFTSANITY, -3414,Farm,Craft Cheese Press,CRAFTSANITY, -3415,Farm,Craft Keg,CRAFTSANITY, -3416,Farm,Craft Loom,CRAFTSANITY, -3417,Farm,Craft Mayonnaise Machine,CRAFTSANITY, -3418,Farm,Craft Oil Maker,CRAFTSANITY, -3419,Farm,Craft Preserves Jar,CRAFTSANITY, -3420,Farm,Craft Basic Fertilizer,CRAFTSANITY, -3421,Farm,Craft Quality Fertilizer,CRAFTSANITY, -3422,Farm,Craft Deluxe Fertilizer,CRAFTSANITY, -3423,Farm,Craft Speed-Gro,CRAFTSANITY, -3424,Farm,Craft Deluxe Speed-Gro,CRAFTSANITY, -3425,Farm,Craft Hyper Speed-Gro,"CRAFTSANITY,GINGER_ISLAND", -3426,Farm,Craft Basic Retaining Soil,CRAFTSANITY, -3427,Farm,Craft Quality Retaining Soil,CRAFTSANITY, -3428,Farm,Craft Deluxe Retaining Soil,"CRAFTSANITY,GINGER_ISLAND", -3429,Farm,Craft Tree Fertilizer,CRAFTSANITY, -3430,Farm,Craft Spring Seeds,CRAFTSANITY, -3431,Farm,Craft Summer Seeds,CRAFTSANITY, -3432,Farm,Craft Fall Seeds,CRAFTSANITY, -3433,Farm,Craft Winter Seeds,CRAFTSANITY, -3434,Farm,Craft Ancient Seeds,CRAFTSANITY, -3435,Farm,Craft Grass Starter,CRAFTSANITY, -3436,Farm,Craft Tea Sapling,CRAFTSANITY, -3437,Farm,Craft Fiber Seeds,CRAFTSANITY, -3438,Farm,Craft Wood Floor,CRAFTSANITY, -3439,Farm,Craft Rustic Plank Floor,CRAFTSANITY, -3440,Farm,Craft Straw Floor,CRAFTSANITY, -3441,Farm,Craft Weathered Floor,CRAFTSANITY, -3442,Farm,Craft Crystal Floor,CRAFTSANITY, -3443,Farm,Craft Stone Floor,CRAFTSANITY, -3444,Farm,Craft Stone Walkway Floor,CRAFTSANITY, -3445,Farm,Craft Brick Floor,CRAFTSANITY, -3446,Farm,Craft Wood Path,CRAFTSANITY, -3447,Farm,Craft Gravel Path,CRAFTSANITY, -3448,Farm,Craft Cobblestone Path,CRAFTSANITY, -3449,Farm,Craft Stepping Stone Path,CRAFTSANITY, -3450,Farm,Craft Crystal Path,CRAFTSANITY, -3451,Farm,Craft Spinner,CRAFTSANITY, -3452,Farm,Craft Trap Bobber,CRAFTSANITY, -3453,Farm,Craft Cork Bobber,CRAFTSANITY, -3454,Farm,Craft Quality Bobber,CRAFTSANITY, -3455,Farm,Craft Treasure Hunter,CRAFTSANITY, -3456,Farm,Craft Dressed Spinner,CRAFTSANITY, -3457,Farm,Craft Barbed Hook,CRAFTSANITY, -3458,Farm,Craft Magnet,CRAFTSANITY, -3459,Farm,Craft Bait,CRAFTSANITY, -3460,Farm,Craft Wild Bait,CRAFTSANITY, -3461,Farm,Craft Magic Bait,"CRAFTSANITY,GINGER_ISLAND", -3462,Farm,Craft Crab Pot,CRAFTSANITY, -3463,Farm,Craft Sturdy Ring,CRAFTSANITY, -3464,Farm,Craft Warrior Ring,CRAFTSANITY, -3465,Farm,Craft Ring of Yoba,CRAFTSANITY, -3466,Farm,Craft Thorns Ring,"CRAFTSANITY,GINGER_ISLAND", -3467,Farm,Craft Glowstone Ring,CRAFTSANITY, -3468,Farm,Craft Iridium Band,CRAFTSANITY, -3469,Farm,Craft Wedding Ring,CRAFTSANITY, -3470,Farm,Craft Field Snack,CRAFTSANITY, -3471,Farm,Craft Bug Steak,CRAFTSANITY, -3472,Farm,Craft Life Elixir,CRAFTSANITY, -3473,Farm,Craft Oil of Garlic,CRAFTSANITY, -3474,Farm,Craft Monster Musk,CRAFTSANITY, -3475,Farm,Craft Fairy Dust,"CRAFTSANITY,GINGER_ISLAND", -3476,Farm,Craft Warp Totem: Beach,CRAFTSANITY, -3477,Farm,Craft Warp Totem: Mountains,CRAFTSANITY, -3478,Farm,Craft Warp Totem: Farm,CRAFTSANITY, -3479,Farm,Craft Warp Totem: Desert,CRAFTSANITY, -3480,Farm,Craft Warp Totem: Island,"CRAFTSANITY,GINGER_ISLAND", -3481,Farm,Craft Rain Totem,CRAFTSANITY, -3482,Farm,Craft Torch,CRAFTSANITY, -3483,Farm,Craft Campfire,CRAFTSANITY, -3484,Farm,Craft Wooden Brazier,CRAFTSANITY, -3485,Farm,Craft Stone Brazier,CRAFTSANITY, -3486,Farm,Craft Gold Brazier,CRAFTSANITY, -3487,Farm,Craft Carved Brazier,CRAFTSANITY, -3488,Farm,Craft Stump Brazier,CRAFTSANITY, -3489,Farm,Craft Barrel Brazier,CRAFTSANITY, -3490,Farm,Craft Skull Brazier,CRAFTSANITY, -3491,Farm,Craft Marble Brazier,CRAFTSANITY, -3492,Farm,Craft Wood Lamp-post,CRAFTSANITY, -3493,Farm,Craft Iron Lamp-post,CRAFTSANITY, -3494,Farm,Craft Jack-O-Lantern,CRAFTSANITY, -3495,Farm,Craft Bone Mill,CRAFTSANITY, -3496,Farm,Craft Charcoal Kiln,CRAFTSANITY, -3497,Farm,Craft Crystalarium,CRAFTSANITY, -3498,Farm,Craft Furnace,CRAFTSANITY, -3499,Farm,Craft Geode Crusher,CRAFTSANITY, -3500,Farm,Craft Heavy Tapper,"CRAFTSANITY,GINGER_ISLAND", -3501,Farm,Craft Lightning Rod,CRAFTSANITY, -3502,Farm,Craft Ostrich Incubator,"CRAFTSANITY,GINGER_ISLAND", -3503,Farm,Craft Recycling Machine,CRAFTSANITY, -3504,Farm,Craft Seed Maker,CRAFTSANITY, -3505,Farm,Craft Slime Egg-Press,CRAFTSANITY, -3506,Farm,Craft Slime Incubator,CRAFTSANITY, -3507,Farm,Craft Solar Panel,"CRAFTSANITY,GINGER_ISLAND", -3508,Farm,Craft Tapper,CRAFTSANITY, -3509,Farm,Craft Worm Bin,CRAFTSANITY, -3510,Farm,Craft Tub o' Flowers,CRAFTSANITY, -3511,Farm,Craft Wicked Statue,CRAFTSANITY, -3512,Farm,Craft Flute Block,CRAFTSANITY, -3513,Farm,Craft Drum Block,CRAFTSANITY, -3514,Farm,Craft Chest,CRAFTSANITY, -3515,Farm,Craft Stone Chest,CRAFTSANITY, -3516,Farm,Craft Wood Sign,CRAFTSANITY, -3517,Farm,Craft Stone Sign,CRAFTSANITY, -3518,Farm,Craft Dark Sign,CRAFTSANITY, -3519,Farm,Craft Garden Pot,CRAFTSANITY, -3520,Farm,Craft Scarecrow,CRAFTSANITY, -3521,Farm,Craft Deluxe Scarecrow,CRAFTSANITY, -3522,Farm,Craft Staircase,CRAFTSANITY, -3523,Farm,Craft Explosive Ammo,CRAFTSANITY, -3524,Farm,Craft Transmute (Fe),CRAFTSANITY, -3525,Farm,Craft Transmute (Au),CRAFTSANITY, -3526,Farm,Craft Mini-Jukebox,CRAFTSANITY, -3527,Farm,Craft Mini-Obelisk,CRAFTSANITY, -3528,Farm,Craft Farm Computer,CRAFTSANITY, -3529,Farm,Craft Hopper,"CRAFTSANITY,GINGER_ISLAND", -3530,Farm,Craft Cookout Kit,CRAFTSANITY, -3531,Farm,Craft Fish Smoker,"CRAFTSANITY", -3532,Farm,Craft Dehydrator,"CRAFTSANITY", -3533,Farm,Craft Blue Grass Starter,"CRAFTSANITY,GINGER_ISLAND", -3534,Farm,Craft Mystic Tree Seed,"CRAFTSANITY,REQUIRES_MASTERIES", -3535,Farm,Craft Sonar Bobber,"CRAFTSANITY", -3536,Farm,Craft Challenge Bait,"CRAFTSANITY,REQUIRES_MASTERIES", -3537,Farm,Craft Treasure Totem,"CRAFTSANITY,REQUIRES_MASTERIES", -3538,Farm,Craft Heavy Furnace,"CRAFTSANITY,REQUIRES_MASTERIES", -3539,Farm,Craft Deluxe Worm Bin,"CRAFTSANITY", -3540,Farm,Craft Mushroom Log,"CRAFTSANITY", -3541,Farm,Craft Big Chest,"CRAFTSANITY", -3542,Farm,Craft Big Stone Chest,"CRAFTSANITY", -3543,Farm,Craft Text Sign,"CRAFTSANITY", -3544,Farm,Craft Tent Kit,"CRAFTSANITY", -3545,Farm,Craft Statue Of The Dwarf King,"CRAFTSANITY,REQUIRES_MASTERIES", -3546,Farm,Craft Statue Of Blessings,"CRAFTSANITY,REQUIRES_MASTERIES", -3547,Farm,Craft Anvil,"CRAFTSANITY,REQUIRES_MASTERIES", -3548,Farm,Craft Mini-Forge,"CRAFTSANITY,GINGER_ISLAND,REQUIRES_MASTERIES", -3549,Farm,Craft Deluxe Bait,"CRAFTSANITY", -3550,Farm,Craft Bait Maker,"CRAFTSANITY", -3551,Pierre's General Store,Grass Starter Recipe,CRAFTSANITY, -3552,Carpenter Shop,Wood Floor Recipe,CRAFTSANITY, -3553,Carpenter Shop,Rustic Plank Floor Recipe,CRAFTSANITY, -3554,Carpenter Shop,Straw Floor Recipe,CRAFTSANITY, -3555,Mines Dwarf Shop,Weathered Floor Recipe,CRAFTSANITY, -3556,Sewer,Crystal Floor Recipe,CRAFTSANITY, -3557,Carpenter Shop,Stone Floor Recipe,CRAFTSANITY, -3558,Carpenter Shop,Stone Walkway Floor Recipe,CRAFTSANITY, -3559,Carpenter Shop,Brick Floor Recipe,CRAFTSANITY, -3560,Carpenter Shop,Stepping Stone Path Recipe,CRAFTSANITY, -3561,Carpenter Shop,Crystal Path Recipe,CRAFTSANITY, -3562,Traveling Cart,Wedding Ring Recipe,CRAFTSANITY, -3563,Volcano Dwarf Shop,Warp Totem: Island Recipe,"CRAFTSANITY,GINGER_ISLAND", -3564,Carpenter Shop,Wooden Brazier Recipe,CRAFTSANITY, -3565,Carpenter Shop,Stone Brazier Recipe,CRAFTSANITY, -3566,Carpenter Shop,Gold Brazier Recipe,CRAFTSANITY, -3567,Carpenter Shop,Carved Brazier Recipe,CRAFTSANITY, -3568,Carpenter Shop,Stump Brazier Recipe,CRAFTSANITY, -3569,Carpenter Shop,Barrel Brazier Recipe,CRAFTSANITY, -3570,Carpenter Shop,Skull Brazier Recipe,CRAFTSANITY, -3571,Carpenter Shop,Marble Brazier Recipe,CRAFTSANITY, -3572,Carpenter Shop,Wood Lamp-post Recipe,CRAFTSANITY, -3573,Carpenter Shop,Iron Lamp-post Recipe,CRAFTSANITY, -3574,Sewer,Wicked Statue Recipe,CRAFTSANITY, -3575,Desert,Warp Totem: Desert Recipe,"CRAFTSANITY", -3576,Island Trader,Deluxe Retaining Soil Recipe,"CRAFTSANITY,GINGER_ISLAND", -3577,Willy's Fish Shop,Fish Smoker Recipe,CRAFTSANITY, -3578,Pierre's General Store,Dehydrator Recipe,CRAFTSANITY, -3579,Carpenter Shop,Big Chest Recipe,CRAFTSANITY, -3580,Mines Dwarf Shop,Big Stone Chest Recipe,CRAFTSANITY, -3701,Raccoon Bundles,Raccoon Request 1,"BUNDLE,RACCOON_BUNDLES", -3702,Raccoon Bundles,Raccoon Request 2,"BUNDLE,RACCOON_BUNDLES", -3703,Raccoon Bundles,Raccoon Request 3,"BUNDLE,RACCOON_BUNDLES", -3704,Raccoon Bundles,Raccoon Request 4,"BUNDLE,RACCOON_BUNDLES", -3705,Raccoon Bundles,Raccoon Request 5,"BUNDLE,RACCOON_BUNDLES", -3706,Raccoon Bundles,Raccoon Request 6,"BUNDLE,RACCOON_BUNDLES", -3707,Raccoon Bundles,Raccoon Request 7,"BUNDLE,RACCOON_BUNDLES", -3708,Raccoon Bundles,Raccoon Request 8,"BUNDLE,RACCOON_BUNDLES", +3401,Farm,Craft Cherry Bomb,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3402,Farm,Craft Bomb,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3403,Farm,Craft Mega Bomb,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3404,Farm,Craft Gate,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3405,Farm,Craft Wood Fence,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3406,Farm,Craft Stone Fence,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3407,Farm,Craft Iron Fence,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3408,Farm,Craft Hardwood Fence,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3409,Farm,Craft Sprinkler,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3410,Farm,Craft Quality Sprinkler,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3411,Farm,Craft Iridium Sprinkler,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3412,Farm,Craft Bee House,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3413,Farm,Craft Cask,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3414,Farm,Craft Cheese Press,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3415,Farm,Craft Keg,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3416,Farm,Craft Loom,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3417,Farm,Craft Mayonnaise Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3418,Farm,Craft Oil Maker,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3419,Farm,Craft Preserves Jar,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3420,Farm,Craft Basic Fertilizer,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3421,Farm,Craft Quality Fertilizer,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3422,Farm,Craft Deluxe Fertilizer,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3423,Farm,Craft Speed-Gro,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3424,Farm,Craft Deluxe Speed-Gro,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3425,Farm,Craft Hyper Speed-Gro,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND,REQUIRES_QI_ORDERS", +3426,Farm,Craft Basic Retaining Soil,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3427,Farm,Craft Quality Retaining Soil,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3428,Farm,Craft Deluxe Retaining Soil,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3429,Farm,Craft Tree Fertilizer,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3430,Farm,Craft Spring Seeds,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3431,Farm,Craft Summer Seeds,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3432,Farm,Craft Fall Seeds,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3433,Farm,Craft Winter Seeds,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3434,Farm,Craft Ancient Seeds,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3435,Farm,Craft Grass Starter,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3436,Farm,Craft Tea Sapling,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3437,Farm,Craft Fiber Seeds,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3438,Farm,Craft Wood Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3439,Farm,Craft Rustic Plank Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3440,Farm,Craft Straw Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3441,Farm,Craft Weathered Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3442,Farm,Craft Crystal Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3443,Farm,Craft Stone Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3444,Farm,Craft Stone Walkway Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3445,Farm,Craft Brick Floor,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3446,Farm,Craft Wood Path,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3447,Farm,Craft Gravel Path,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3448,Farm,Craft Cobblestone Path,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3449,Farm,Craft Stepping Stone Path,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3450,Farm,Craft Crystal Path,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3451,Farm,Craft Spinner,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3452,Farm,Craft Trap Bobber,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3453,Farm,Craft Cork Bobber,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3454,Farm,Craft Quality Bobber,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3455,Farm,Craft Treasure Hunter,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3456,Farm,Craft Dressed Spinner,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3457,Farm,Craft Barbed Hook,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3458,Farm,Craft Magnet,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3459,Farm,Craft Bait,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3460,Farm,Craft Wild Bait,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3461,Farm,Craft Magic Bait,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND,REQUIRES_QI_ORDERS", +3462,Farm,Craft Crab Pot,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3463,Farm,Craft Sturdy Ring,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3464,Farm,Craft Warrior Ring,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3465,Farm,Craft Ring of Yoba,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3466,Farm,Craft Thorns Ring,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3467,Farm,Craft Glowstone Ring,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3468,Farm,Craft Iridium Band,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3469,Farm,Craft Wedding Ring,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3470,Farm,Craft Field Snack,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3471,Farm,Craft Bug Steak,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3472,Farm,Craft Life Elixir,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3473,Farm,Craft Oil of Garlic,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3474,Farm,Craft Monster Musk,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3475,Farm,Craft Fairy Dust,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3476,Farm,Craft Warp Totem: Beach,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3477,Farm,Craft Warp Totem: Mountains,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3478,Farm,Craft Warp Totem: Farm,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3479,Farm,Craft Warp Totem: Desert,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3480,Farm,Craft Warp Totem: Island,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3481,Farm,Craft Rain Totem,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3482,Farm,Craft Torch,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3483,Farm,Craft Campfire,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3484,Farm,Craft Wooden Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3485,Farm,Craft Stone Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3486,Farm,Craft Gold Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3487,Farm,Craft Carved Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3488,Farm,Craft Stump Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3489,Farm,Craft Barrel Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3490,Farm,Craft Skull Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3491,Farm,Craft Marble Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3492,Farm,Craft Wood Lamp-post,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3493,Farm,Craft Iron Lamp-post,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3494,Farm,Craft Jack-O-Lantern,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3495,Farm,Craft Bone Mill,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3496,Farm,Craft Charcoal Kiln,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3497,Farm,Craft Crystalarium,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3498,Farm,Craft Furnace,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3499,Farm,Craft Geode Crusher,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3500,Farm,Craft Heavy Tapper,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND,REQUIRES_QI_ORDERS", +3501,Farm,Craft Lightning Rod,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3502,Farm,Craft Ostrich Incubator,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3503,Farm,Craft Recycling Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3504,Farm,Craft Seed Maker,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3505,Farm,Craft Slime Egg-Press,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3506,Farm,Craft Slime Incubator,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3507,Farm,Craft Solar Panel,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3508,Farm,Craft Tapper,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3509,Farm,Craft Worm Bin,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3510,Farm,Craft Tub o' Flowers,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3511,Farm,Craft Wicked Statue,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3512,Farm,Craft Flute Block,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3513,Farm,Craft Drum Block,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3514,Farm,Craft Chest,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3515,Farm,Craft Stone Chest,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3516,Farm,Craft Wood Sign,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3517,Farm,Craft Stone Sign,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3518,Farm,Craft Dark Sign,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3519,Farm,Craft Garden Pot,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3520,Farm,Craft Scarecrow,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3521,Farm,Craft Deluxe Scarecrow,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3522,Farm,Craft Staircase,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3523,Farm,Craft Explosive Ammo,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3524,Farm,Craft Transmute (Fe),"CRAFTSANITY,CRAFTSANITY_CRAFT", +3525,Farm,Craft Transmute (Au),"CRAFTSANITY,CRAFTSANITY_CRAFT", +3526,Farm,Craft Mini-Jukebox,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3527,Farm,Craft Mini-Obelisk,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3528,Farm,Craft Farm Computer,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3529,Farm,Craft Hopper,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND,REQUIRES_QI_ORDERS", +3530,Farm,Craft Cookout Kit,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3531,Farm,Craft Fish Smoker,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3532,Farm,Craft Dehydrator,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3533,Farm,Craft Blue Grass Starter,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND", +3534,Farm,Craft Mystic Tree Seed,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3535,Farm,Craft Sonar Bobber,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3536,Farm,Craft Challenge Bait,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3537,Farm,Craft Treasure Totem,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3538,Farm,Craft Heavy Furnace,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3539,Farm,Craft Deluxe Worm Bin,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3540,Farm,Craft Mushroom Log,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3541,Farm,Craft Big Chest,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3542,Farm,Craft Big Stone Chest,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3543,Farm,Craft Text Sign,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3544,Farm,Craft Tent Kit,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3545,Farm,Craft Statue Of The Dwarf King,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3546,Farm,Craft Statue Of Blessings,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3547,Farm,Craft Anvil,"CRAFTSANITY,CRAFTSANITY_CRAFT,REQUIRES_MASTERIES", +3548,Farm,Craft Mini-Forge,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND,REQUIRES_MASTERIES", +3549,Farm,Craft Deluxe Bait,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3550,Farm,Craft Bait Maker,"CRAFTSANITY,CRAFTSANITY_CRAFT", +3551,Pierre's General Store,Grass Starter Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3552,Carpenter Shop,Wood Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3553,Carpenter Shop,Rustic Plank Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3554,Carpenter Shop,Straw Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3555,Mines Dwarf Shop,Weathered Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3556,Sewer,Crystal Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3557,Carpenter Shop,Stone Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3558,Carpenter Shop,Stone Walkway Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3559,Carpenter Shop,Brick Floor Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3560,Carpenter Shop,Stepping Stone Path Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3561,Carpenter Shop,Crystal Path Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3562,Traveling Cart,Wedding Ring Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3563,Volcano Dwarf Shop,Warp Totem: Island Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE,GINGER_ISLAND", +3564,Carpenter Shop,Wooden Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3565,Carpenter Shop,Stone Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3566,Carpenter Shop,Gold Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3567,Carpenter Shop,Carved Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3568,Carpenter Shop,Stump Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3569,Carpenter Shop,Barrel Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3570,Carpenter Shop,Skull Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3571,Carpenter Shop,Marble Brazier Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3572,Carpenter Shop,Wood Lamp-post Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3573,Carpenter Shop,Iron Lamp-post Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3574,Sewer,Wicked Statue Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3575,Desert,Warp Totem: Desert Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3576,Island Trader,Deluxe Retaining Soil Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE,GINGER_ISLAND", +3577,Willy's Fish Shop,Fish Smoker Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3578,Pierre's General Store,Dehydrator Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3579,Carpenter Shop,Big Chest Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3580,Mines Dwarf Shop,Big Stone Chest Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE", +3701,Raccoon Request 1,Raccoon Request 1,"BUNDLE,RACCOON_BUNDLES", +3702,Raccoon Request 2,Raccoon Request 2,"BUNDLE,RACCOON_BUNDLES", +3703,Raccoon Request 3,Raccoon Request 3,"BUNDLE,RACCOON_BUNDLES", +3704,Raccoon Request 4,Raccoon Request 4,"BUNDLE,RACCOON_BUNDLES", +3705,Raccoon Request 5,Raccoon Request 5,"BUNDLE,RACCOON_BUNDLES", +3706,Raccoon Request 6,Raccoon Request 6,"BUNDLE,RACCOON_BUNDLES", +3707,Raccoon Request 7,Raccoon Request 7,"BUNDLE,RACCOON_BUNDLES", +3708,Raccoon Request 8,Raccoon Request 8,"BUNDLE,RACCOON_BUNDLES", 3801,Shipping,Shipsanity: Goby,"SHIPSANITY,SHIPSANITY_FISH", 3802,Shipping,Shipsanity: Fireworks (Red),"SHIPSANITY", 3803,Shipping,Shipsanity: Fireworks (Purple),"SHIPSANITY", @@ -2298,7 +2379,7 @@ id,region,name,tags,mod_name 4051,Museum,Read Tips on Farming,"BOOKSANITY,BOOKSANITY_LOST", 4052,Museum,Read This is a book by Marnie,"BOOKSANITY,BOOKSANITY_LOST", 4053,Museum,Read On Foraging,"BOOKSANITY,BOOKSANITY_LOST", -4054,Museum,"Read The Fisherman, Act 1","BOOKSANITY,BOOKSANITY_LOST", +4054,Museum,"Read The Fisherman, Act I","BOOKSANITY,BOOKSANITY_LOST", 4055,Museum,Read How Deep do the mines go?,"BOOKSANITY,BOOKSANITY_LOST", 4056,Museum,Read An Old Farmer's Journal,"BOOKSANITY,BOOKSANITY_LOST", 4057,Museum,Read Scarecrows,"BOOKSANITY,BOOKSANITY_LOST", @@ -2410,883 +2491,1548 @@ id,region,name,tags,mod_name 4192,Pirate Cove,Walnutsanity: Pirate Darts 2,"WALNUTSANITY,WALNUTSANITY_PUZZLE", 4193,Pirate Cove,Walnutsanity: Pirate Darts 3,"WALNUTSANITY,WALNUTSANITY_PUZZLE", 4194,Pirate Cove,Walnutsanity: Pirate Cove Patch Of Sand,"WALNUTSANITY,WALNUTSANITY_DIG", -5001,Stardew Valley,Level 1 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5002,Stardew Valley,Level 2 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5003,Stardew Valley,Level 3 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5004,Stardew Valley,Level 4 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5005,Stardew Valley,Level 5 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5006,Stardew Valley,Level 6 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5007,Stardew Valley,Level 7 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5008,Stardew Valley,Level 8 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5009,Stardew Valley,Level 9 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5010,Stardew Valley,Level 10 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill -5011,Stardew Valley,Level 1 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5012,Stardew Valley,Level 2 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5013,Stardew Valley,Level 3 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5014,Stardew Valley,Level 4 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5015,Stardew Valley,Level 5 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5016,Stardew Valley,Level 6 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5017,Stardew Valley,Level 7 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5018,Stardew Valley,Level 8 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5019,Stardew Valley,Level 9 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5020,Stardew Valley,Level 10 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill -5021,Magic Altar,Level 1 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5022,Magic Altar,Level 2 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5023,Magic Altar,Level 3 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5024,Magic Altar,Level 4 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5025,Magic Altar,Level 5 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5026,Magic Altar,Level 6 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5027,Magic Altar,Level 7 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5028,Magic Altar,Level 8 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5029,Magic Altar,Level 9 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5030,Magic Altar,Level 10 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic -5031,Town,Level 1 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5032,Town,Level 2 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5033,Town,Level 3 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5034,Town,Level 4 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5035,Town,Level 5 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5036,Town,Level 6 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5037,Town,Level 7 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5038,Town,Level 8 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5039,Town,Level 9 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5040,Town,Level 10 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill -5041,Stardew Valley,Level 1 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5042,Stardew Valley,Level 2 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5043,Stardew Valley,Level 3 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5044,Stardew Valley,Level 4 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5045,Stardew Valley,Level 5 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5046,Stardew Valley,Level 6 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5047,Stardew Valley,Level 7 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5048,Stardew Valley,Level 8 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5049,Stardew Valley,Level 9 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5050,Stardew Valley,Level 10 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology -5051,Stardew Valley,Level 1 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5052,Stardew Valley,Level 2 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5053,Stardew Valley,Level 3 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5054,Stardew Valley,Level 4 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5055,Stardew Valley,Level 5 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5056,Stardew Valley,Level 6 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5057,Stardew Valley,Level 7 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5058,Stardew Valley,Level 8 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5059,Stardew Valley,Level 9 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5060,Stardew Valley,Level 10 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill -5501,Magic Altar,Analyze: Clear Debris,MANDATORY,Magic -5502,Magic Altar,Analyze: Till,MANDATORY,Magic -5503,Magic Altar,Analyze: Water,MANDATORY,Magic -5504,Magic Altar,Analyze All Toil School Locations,MANDATORY,Magic -5505,Magic Altar,Analyze: Evac,MANDATORY,Magic -5506,Magic Altar,Analyze: Haste,MANDATORY,Magic -5507,Magic Altar,Analyze: Heal,MANDATORY,Magic -5508,Magic Altar,Analyze All Life School Locations,MANDATORY,Magic -5509,Magic Altar,Analyze: Descend,MANDATORY,Magic -5510,Magic Altar,Analyze: Fireball,MANDATORY,Magic -5511,Magic Altar,Analyze: Frostbolt,MANDATORY,Magic -5512,Magic Altar,Analyze All Elemental School Locations,MANDATORY,Magic -5513,Magic Altar,Analyze: Lantern,MANDATORY,Magic -5514,Magic Altar,Analyze: Tendrils,MANDATORY,Magic -5515,Magic Altar,Analyze: Shockwave,MANDATORY,Magic -5516,Magic Altar,Analyze All Nature School Locations,MANDATORY,Magic -5517,Magic Altar,Analyze: Meteor,MANDATORY,Magic -5518,Magic Altar,Analyze: Lucksteal,MANDATORY,Magic -5519,Magic Altar,Analyze: Bloodmana,MANDATORY,Magic -5520,Magic Altar,Analyze All Eldritch School Locations,MANDATORY,Magic -5521,Magic Altar,Analyze Every Magic School Location,MANDATORY,Magic -6001,Museum,Friendsanity: Jasper 1 <3,FRIENDSANITY,Professor Jasper Thomas -6002,Museum,Friendsanity: Jasper 2 <3,FRIENDSANITY,Professor Jasper Thomas -6003,Museum,Friendsanity: Jasper 3 <3,FRIENDSANITY,Professor Jasper Thomas -6004,Museum,Friendsanity: Jasper 4 <3,FRIENDSANITY,Professor Jasper Thomas -6005,Museum,Friendsanity: Jasper 5 <3,FRIENDSANITY,Professor Jasper Thomas -6006,Museum,Friendsanity: Jasper 6 <3,FRIENDSANITY,Professor Jasper Thomas -6007,Museum,Friendsanity: Jasper 7 <3,FRIENDSANITY,Professor Jasper Thomas -6008,Museum,Friendsanity: Jasper 8 <3,FRIENDSANITY,Professor Jasper Thomas -6009,Museum,Friendsanity: Jasper 9 <3,FRIENDSANITY,Professor Jasper Thomas -6010,Museum,Friendsanity: Jasper 10 <3,FRIENDSANITY,Professor Jasper Thomas -6011,Museum,Friendsanity: Jasper 11 <3,FRIENDSANITY,Professor Jasper Thomas -6012,Museum,Friendsanity: Jasper 12 <3,FRIENDSANITY,Professor Jasper Thomas -6013,Museum,Friendsanity: Jasper 13 <3,FRIENDSANITY,Professor Jasper Thomas -6014,Museum,Friendsanity: Jasper 14 <3,FRIENDSANITY,Professor Jasper Thomas -6015,Yoba's Clearing,Friendsanity: Yoba 1 <3,FRIENDSANITY,Custom NPC - Yoba -6016,Yoba's Clearing,Friendsanity: Yoba 2 <3,FRIENDSANITY,Custom NPC - Yoba -6017,Yoba's Clearing,Friendsanity: Yoba 3 <3,FRIENDSANITY,Custom NPC - Yoba -6018,Yoba's Clearing,Friendsanity: Yoba 4 <3,FRIENDSANITY,Custom NPC - Yoba -6019,Yoba's Clearing,Friendsanity: Yoba 5 <3,FRIENDSANITY,Custom NPC - Yoba -6020,Yoba's Clearing,Friendsanity: Yoba 6 <3,FRIENDSANITY,Custom NPC - Yoba -6021,Yoba's Clearing,Friendsanity: Yoba 7 <3,FRIENDSANITY,Custom NPC - Yoba -6022,Yoba's Clearing,Friendsanity: Yoba 8 <3,FRIENDSANITY,Custom NPC - Yoba -6023,Yoba's Clearing,Friendsanity: Yoba 9 <3,FRIENDSANITY,Custom NPC - Yoba -6024,Yoba's Clearing,Friendsanity: Yoba 10 <3,FRIENDSANITY,Custom NPC - Yoba -6025,Marnie's Ranch,Friendsanity: Mr. Ginger 1 <3,FRIENDSANITY,Mister Ginger (cat npc) -6026,Marnie's Ranch,Friendsanity: Mr. Ginger 2 <3,FRIENDSANITY,Mister Ginger (cat npc) -6027,Marnie's Ranch,Friendsanity: Mr. Ginger 3 <3,FRIENDSANITY,Mister Ginger (cat npc) -6028,Marnie's Ranch,Friendsanity: Mr. Ginger 4 <3,FRIENDSANITY,Mister Ginger (cat npc) -6029,Marnie's Ranch,Friendsanity: Mr. Ginger 5 <3,FRIENDSANITY,Mister Ginger (cat npc) -6030,Marnie's Ranch,Friendsanity: Mr. Ginger 6 <3,FRIENDSANITY,Mister Ginger (cat npc) -6031,Marnie's Ranch,Friendsanity: Mr. Ginger 7 <3,FRIENDSANITY,Mister Ginger (cat npc) -6032,Marnie's Ranch,Friendsanity: Mr. Ginger 8 <3,FRIENDSANITY,Mister Ginger (cat npc) -6033,Marnie's Ranch,Friendsanity: Mr. Ginger 9 <3,FRIENDSANITY,Mister Ginger (cat npc) -6034,Marnie's Ranch,Friendsanity: Mr. Ginger 10 <3,FRIENDSANITY,Mister Ginger (cat npc) -6035,Town,Friendsanity: Ayeisha 1 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6036,Town,Friendsanity: Ayeisha 2 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6037,Town,Friendsanity: Ayeisha 3 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6038,Town,Friendsanity: Ayeisha 4 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6039,Town,Friendsanity: Ayeisha 5 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6040,Town,Friendsanity: Ayeisha 6 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6041,Town,Friendsanity: Ayeisha 7 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6042,Town,Friendsanity: Ayeisha 8 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6043,Town,Friendsanity: Ayeisha 9 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6044,Town,Friendsanity: Ayeisha 10 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) -6045,Saloon,Friendsanity: Shiko 1 <3,FRIENDSANITY,Shiko - New Custom NPC -6046,Saloon,Friendsanity: Shiko 2 <3,FRIENDSANITY,Shiko - New Custom NPC -6047,Saloon,Friendsanity: Shiko 3 <3,FRIENDSANITY,Shiko - New Custom NPC -6048,Saloon,Friendsanity: Shiko 4 <3,FRIENDSANITY,Shiko - New Custom NPC -6049,Saloon,Friendsanity: Shiko 5 <3,FRIENDSANITY,Shiko - New Custom NPC -6050,Saloon,Friendsanity: Shiko 6 <3,FRIENDSANITY,Shiko - New Custom NPC -6051,Saloon,Friendsanity: Shiko 7 <3,FRIENDSANITY,Shiko - New Custom NPC -6052,Saloon,Friendsanity: Shiko 8 <3,FRIENDSANITY,Shiko - New Custom NPC -6053,Saloon,Friendsanity: Shiko 9 <3,FRIENDSANITY,Shiko - New Custom NPC -6054,Saloon,Friendsanity: Shiko 10 <3,FRIENDSANITY,Shiko - New Custom NPC -6055,Saloon,Friendsanity: Shiko 11 <3,FRIENDSANITY,Shiko - New Custom NPC -6056,Saloon,Friendsanity: Shiko 12 <3,FRIENDSANITY,Shiko - New Custom NPC -6057,Saloon,Friendsanity: Shiko 13 <3,FRIENDSANITY,Shiko - New Custom NPC -6058,Saloon,Friendsanity: Shiko 14 <3,FRIENDSANITY,Shiko - New Custom NPC -6059,Wizard Tower,Friendsanity: Wellwick 1 <3,FRIENDSANITY,'Prophet' Wellwick -6060,Wizard Tower,Friendsanity: Wellwick 2 <3,FRIENDSANITY,'Prophet' Wellwick -6061,Wizard Tower,Friendsanity: Wellwick 3 <3,FRIENDSANITY,'Prophet' Wellwick -6062,Wizard Tower,Friendsanity: Wellwick 4 <3,FRIENDSANITY,'Prophet' Wellwick -6063,Wizard Tower,Friendsanity: Wellwick 5 <3,FRIENDSANITY,'Prophet' Wellwick -6064,Wizard Tower,Friendsanity: Wellwick 6 <3,FRIENDSANITY,'Prophet' Wellwick -6065,Wizard Tower,Friendsanity: Wellwick 7 <3,FRIENDSANITY,'Prophet' Wellwick -6066,Wizard Tower,Friendsanity: Wellwick 8 <3,FRIENDSANITY,'Prophet' Wellwick -6067,Wizard Tower,Friendsanity: Wellwick 9 <3,FRIENDSANITY,'Prophet' Wellwick -6068,Wizard Tower,Friendsanity: Wellwick 10 <3,FRIENDSANITY,'Prophet' Wellwick -6069,Wizard Tower,Friendsanity: Wellwick 11 <3,FRIENDSANITY,'Prophet' Wellwick -6070,Wizard Tower,Friendsanity: Wellwick 12 <3,FRIENDSANITY,'Prophet' Wellwick -6071,Wizard Tower,Friendsanity: Wellwick 13 <3,FRIENDSANITY,'Prophet' Wellwick -6072,Wizard Tower,Friendsanity: Wellwick 14 <3,FRIENDSANITY,'Prophet' Wellwick -6073,Forest,Friendsanity: Delores 1 <3,FRIENDSANITY,Delores - Custom NPC -6074,Forest,Friendsanity: Delores 2 <3,FRIENDSANITY,Delores - Custom NPC -6075,Forest,Friendsanity: Delores 3 <3,FRIENDSANITY,Delores - Custom NPC -6076,Forest,Friendsanity: Delores 4 <3,FRIENDSANITY,Delores - Custom NPC -6077,Forest,Friendsanity: Delores 5 <3,FRIENDSANITY,Delores - Custom NPC -6078,Forest,Friendsanity: Delores 6 <3,FRIENDSANITY,Delores - Custom NPC -6079,Forest,Friendsanity: Delores 7 <3,FRIENDSANITY,Delores - Custom NPC -6080,Forest,Friendsanity: Delores 8 <3,FRIENDSANITY,Delores - Custom NPC -6081,Forest,Friendsanity: Delores 9 <3,FRIENDSANITY,Delores - Custom NPC -6082,Forest,Friendsanity: Delores 10 <3,FRIENDSANITY,Delores - Custom NPC -6083,Forest,Friendsanity: Delores 11 <3,FRIENDSANITY,Delores - Custom NPC -6084,Forest,Friendsanity: Delores 12 <3,FRIENDSANITY,Delores - Custom NPC -6085,Forest,Friendsanity: Delores 13 <3,FRIENDSANITY,Delores - Custom NPC -6086,Forest,Friendsanity: Delores 14 <3,FRIENDSANITY,Delores - Custom NPC -6087,Alec's Pet Shop,Friendsanity: Alec 1 <3,FRIENDSANITY,Alec Revisited -6088,Alec's Pet Shop,Friendsanity: Alec 2 <3,FRIENDSANITY,Alec Revisited -6089,Alec's Pet Shop,Friendsanity: Alec 3 <3,FRIENDSANITY,Alec Revisited -6090,Alec's Pet Shop,Friendsanity: Alec 4 <3,FRIENDSANITY,Alec Revisited -6091,Alec's Pet Shop,Friendsanity: Alec 5 <3,FRIENDSANITY,Alec Revisited -6092,Alec's Pet Shop,Friendsanity: Alec 6 <3,FRIENDSANITY,Alec Revisited -6093,Alec's Pet Shop,Friendsanity: Alec 7 <3,FRIENDSANITY,Alec Revisited -6094,Alec's Pet Shop,Friendsanity: Alec 8 <3,FRIENDSANITY,Alec Revisited -6095,Alec's Pet Shop,Friendsanity: Alec 9 <3,FRIENDSANITY,Alec Revisited -6096,Alec's Pet Shop,Friendsanity: Alec 10 <3,FRIENDSANITY,Alec Revisited -6097,Alec's Pet Shop,Friendsanity: Alec 11 <3,FRIENDSANITY,Alec Revisited -6098,Alec's Pet Shop,Friendsanity: Alec 12 <3,FRIENDSANITY,Alec Revisited -6099,Alec's Pet Shop,Friendsanity: Alec 13 <3,FRIENDSANITY,Alec Revisited -6100,Alec's Pet Shop,Friendsanity: Alec 14 <3,FRIENDSANITY,Alec Revisited -6101,Eugene's Garden,Friendsanity: Eugene 1 <3,FRIENDSANITY,Custom NPC Eugene -6102,Eugene's Garden,Friendsanity: Eugene 2 <3,FRIENDSANITY,Custom NPC Eugene -6103,Eugene's Garden,Friendsanity: Eugene 3 <3,FRIENDSANITY,Custom NPC Eugene -6104,Eugene's Garden,Friendsanity: Eugene 4 <3,FRIENDSANITY,Custom NPC Eugene -6105,Eugene's Garden,Friendsanity: Eugene 5 <3,FRIENDSANITY,Custom NPC Eugene -6106,Eugene's Garden,Friendsanity: Eugene 6 <3,FRIENDSANITY,Custom NPC Eugene -6107,Eugene's Garden,Friendsanity: Eugene 7 <3,FRIENDSANITY,Custom NPC Eugene -6108,Eugene's Garden,Friendsanity: Eugene 8 <3,FRIENDSANITY,Custom NPC Eugene -6109,Eugene's Garden,Friendsanity: Eugene 9 <3,FRIENDSANITY,Custom NPC Eugene -6110,Eugene's Garden,Friendsanity: Eugene 10 <3,FRIENDSANITY,Custom NPC Eugene -6111,Eugene's Garden,Friendsanity: Eugene 11 <3,FRIENDSANITY,Custom NPC Eugene -6112,Eugene's Garden,Friendsanity: Eugene 12 <3,FRIENDSANITY,Custom NPC Eugene -6113,Eugene's Garden,Friendsanity: Eugene 13 <3,FRIENDSANITY,Custom NPC Eugene -6114,Eugene's Garden,Friendsanity: Eugene 14 <3,FRIENDSANITY,Custom NPC Eugene -6115,Forest,Friendsanity: Juna 1 <3,FRIENDSANITY,Juna - Roommate NPC -6116,Forest,Friendsanity: Juna 2 <3,FRIENDSANITY,Juna - Roommate NPC -6117,Forest,Friendsanity: Juna 3 <3,FRIENDSANITY,Juna - Roommate NPC -6118,Forest,Friendsanity: Juna 4 <3,FRIENDSANITY,Juna - Roommate NPC -6119,Forest,Friendsanity: Juna 5 <3,FRIENDSANITY,Juna - Roommate NPC -6120,Forest,Friendsanity: Juna 6 <3,FRIENDSANITY,Juna - Roommate NPC -6121,Forest,Friendsanity: Juna 7 <3,FRIENDSANITY,Juna - Roommate NPC -6122,Forest,Friendsanity: Juna 8 <3,FRIENDSANITY,Juna - Roommate NPC -6123,Forest,Friendsanity: Juna 9 <3,FRIENDSANITY,Juna - Roommate NPC -6124,Forest,Friendsanity: Juna 10 <3,FRIENDSANITY,Juna - Roommate NPC -6125,Riley's House,Friendsanity: Riley 1 <3,FRIENDSANITY,Custom NPC - Riley -6126,Riley's House,Friendsanity: Riley 2 <3,FRIENDSANITY,Custom NPC - Riley -6127,Riley's House,Friendsanity: Riley 3 <3,FRIENDSANITY,Custom NPC - Riley -6128,Riley's House,Friendsanity: Riley 4 <3,FRIENDSANITY,Custom NPC - Riley -6129,Riley's House,Friendsanity: Riley 5 <3,FRIENDSANITY,Custom NPC - Riley -6130,Riley's House,Friendsanity: Riley 6 <3,FRIENDSANITY,Custom NPC - Riley -6131,Riley's House,Friendsanity: Riley 7 <3,FRIENDSANITY,Custom NPC - Riley -6132,Riley's House,Friendsanity: Riley 8 <3,FRIENDSANITY,Custom NPC - Riley -6133,Riley's House,Friendsanity: Riley 9 <3,FRIENDSANITY,Custom NPC - Riley -6134,Riley's House,Friendsanity: Riley 10 <3,FRIENDSANITY,Custom NPC - Riley -6135,Riley's House,Friendsanity: Riley 11 <3,FRIENDSANITY,Custom NPC - Riley -6136,Riley's House,Friendsanity: Riley 12 <3,FRIENDSANITY,Custom NPC - Riley -6137,Riley's House,Friendsanity: Riley 13 <3,FRIENDSANITY,Custom NPC - Riley -6138,Riley's House,Friendsanity: Riley 14 <3,FRIENDSANITY,Custom NPC - Riley -6139,JojaMart,Friendsanity: Claire 1 <3,FRIENDSANITY,Stardew Valley Expanded -6140,JojaMart,Friendsanity: Claire 2 <3,FRIENDSANITY,Stardew Valley Expanded -6141,JojaMart,Friendsanity: Claire 3 <3,FRIENDSANITY,Stardew Valley Expanded -6142,JojaMart,Friendsanity: Claire 4 <3,FRIENDSANITY,Stardew Valley Expanded -6143,JojaMart,Friendsanity: Claire 5 <3,FRIENDSANITY,Stardew Valley Expanded -6144,JojaMart,Friendsanity: Claire 6 <3,FRIENDSANITY,Stardew Valley Expanded -6145,JojaMart,Friendsanity: Claire 7 <3,FRIENDSANITY,Stardew Valley Expanded -6146,JojaMart,Friendsanity: Claire 8 <3,FRIENDSANITY,Stardew Valley Expanded -6147,JojaMart,Friendsanity: Claire 9 <3,FRIENDSANITY,Stardew Valley Expanded -6148,JojaMart,Friendsanity: Claire 10 <3,FRIENDSANITY,Stardew Valley Expanded -6149,JojaMart,Friendsanity: Claire 11 <3,FRIENDSANITY,Stardew Valley Expanded -6150,JojaMart,Friendsanity: Claire 12 <3,FRIENDSANITY,Stardew Valley Expanded -6151,JojaMart,Friendsanity: Claire 13 <3,FRIENDSANITY,Stardew Valley Expanded -6152,JojaMart,Friendsanity: Claire 14 <3,FRIENDSANITY,Stardew Valley Expanded -6153,Galmoran Outpost,Friendsanity: Lance 1 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6154,Galmoran Outpost,Friendsanity: Lance 2 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6155,Galmoran Outpost,Friendsanity: Lance 3 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6156,Galmoran Outpost,Friendsanity: Lance 4 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6157,Galmoran Outpost,Friendsanity: Lance 5 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6158,Galmoran Outpost,Friendsanity: Lance 6 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6159,Galmoran Outpost,Friendsanity: Lance 7 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6160,Galmoran Outpost,Friendsanity: Lance 8 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6161,Galmoran Outpost,Friendsanity: Lance 9 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6162,Galmoran Outpost,Friendsanity: Lance 10 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6163,Galmoran Outpost,Friendsanity: Lance 11 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6164,Galmoran Outpost,Friendsanity: Lance 12 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6165,Galmoran Outpost,Friendsanity: Lance 13 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6166,Galmoran Outpost,Friendsanity: Lance 14 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded -6167,Jenkins' Residence,Friendsanity: Olivia 1 <3,FRIENDSANITY,Stardew Valley Expanded -6168,Jenkins' Residence,Friendsanity: Olivia 2 <3,FRIENDSANITY,Stardew Valley Expanded -6169,Jenkins' Residence,Friendsanity: Olivia 3 <3,FRIENDSANITY,Stardew Valley Expanded -6170,Jenkins' Residence,Friendsanity: Olivia 4 <3,FRIENDSANITY,Stardew Valley Expanded -6171,Jenkins' Residence,Friendsanity: Olivia 5 <3,FRIENDSANITY,Stardew Valley Expanded -6172,Jenkins' Residence,Friendsanity: Olivia 6 <3,FRIENDSANITY,Stardew Valley Expanded -6173,Jenkins' Residence,Friendsanity: Olivia 7 <3,FRIENDSANITY,Stardew Valley Expanded -6174,Jenkins' Residence,Friendsanity: Olivia 8 <3,FRIENDSANITY,Stardew Valley Expanded -6175,Jenkins' Residence,Friendsanity: Olivia 9 <3,FRIENDSANITY,Stardew Valley Expanded -6176,Jenkins' Residence,Friendsanity: Olivia 10 <3,FRIENDSANITY,Stardew Valley Expanded -6177,Jenkins' Residence,Friendsanity: Olivia 11 <3,FRIENDSANITY,Stardew Valley Expanded -6178,Jenkins' Residence,Friendsanity: Olivia 12 <3,FRIENDSANITY,Stardew Valley Expanded -6179,Jenkins' Residence,Friendsanity: Olivia 13 <3,FRIENDSANITY,Stardew Valley Expanded -6180,Jenkins' Residence,Friendsanity: Olivia 14 <3,FRIENDSANITY,Stardew Valley Expanded -6181,Wizard Tower,Friendsanity: Wizard 11 <3,FRIENDSANITY,Stardew Valley Expanded -6182,Wizard Tower,Friendsanity: Wizard 12 <3,FRIENDSANITY,Stardew Valley Expanded -6183,Wizard Tower,Friendsanity: Wizard 13 <3,FRIENDSANITY,Stardew Valley Expanded -6184,Wizard Tower,Friendsanity: Wizard 14 <3,FRIENDSANITY,Stardew Valley Expanded -6185,Blue Moon Vineyard,Friendsanity: Sophia 1 <3,FRIENDSANITY,Stardew Valley Expanded -6186,Blue Moon Vineyard,Friendsanity: Sophia 2 <3,FRIENDSANITY,Stardew Valley Expanded -6187,Blue Moon Vineyard,Friendsanity: Sophia 3 <3,FRIENDSANITY,Stardew Valley Expanded -6188,Blue Moon Vineyard,Friendsanity: Sophia 4 <3,FRIENDSANITY,Stardew Valley Expanded -6189,Blue Moon Vineyard,Friendsanity: Sophia 5 <3,FRIENDSANITY,Stardew Valley Expanded -6190,Blue Moon Vineyard,Friendsanity: Sophia 6 <3,FRIENDSANITY,Stardew Valley Expanded -6191,Blue Moon Vineyard,Friendsanity: Sophia 7 <3,FRIENDSANITY,Stardew Valley Expanded -6192,Blue Moon Vineyard,Friendsanity: Sophia 8 <3,FRIENDSANITY,Stardew Valley Expanded -6193,Blue Moon Vineyard,Friendsanity: Sophia 9 <3,FRIENDSANITY,Stardew Valley Expanded -6194,Blue Moon Vineyard,Friendsanity: Sophia 10 <3,FRIENDSANITY,Stardew Valley Expanded -6195,Blue Moon Vineyard,Friendsanity: Sophia 11 <3,FRIENDSANITY,Stardew Valley Expanded -6196,Blue Moon Vineyard,Friendsanity: Sophia 12 <3,FRIENDSANITY,Stardew Valley Expanded -6197,Blue Moon Vineyard,Friendsanity: Sophia 13 <3,FRIENDSANITY,Stardew Valley Expanded -6198,Blue Moon Vineyard,Friendsanity: Sophia 14 <3,FRIENDSANITY,Stardew Valley Expanded -6199,Jenkins' Residence,Friendsanity: Victor 1 <3,FRIENDSANITY,Stardew Valley Expanded -6200,Jenkins' Residence,Friendsanity: Victor 2 <3,FRIENDSANITY,Stardew Valley Expanded -6201,Jenkins' Residence,Friendsanity: Victor 3 <3,FRIENDSANITY,Stardew Valley Expanded -6202,Jenkins' Residence,Friendsanity: Victor 4 <3,FRIENDSANITY,Stardew Valley Expanded -6203,Jenkins' Residence,Friendsanity: Victor 5 <3,FRIENDSANITY,Stardew Valley Expanded -6204,Jenkins' Residence,Friendsanity: Victor 6 <3,FRIENDSANITY,Stardew Valley Expanded -6205,Jenkins' Residence,Friendsanity: Victor 7 <3,FRIENDSANITY,Stardew Valley Expanded -6206,Jenkins' Residence,Friendsanity: Victor 8 <3,FRIENDSANITY,Stardew Valley Expanded -6207,Jenkins' Residence,Friendsanity: Victor 9 <3,FRIENDSANITY,Stardew Valley Expanded -6208,Jenkins' Residence,Friendsanity: Victor 10 <3,FRIENDSANITY,Stardew Valley Expanded -6209,Jenkins' Residence,Friendsanity: Victor 11 <3,FRIENDSANITY,Stardew Valley Expanded -6210,Jenkins' Residence,Friendsanity: Victor 12 <3,FRIENDSANITY,Stardew Valley Expanded -6211,Jenkins' Residence,Friendsanity: Victor 13 <3,FRIENDSANITY,Stardew Valley Expanded -6212,Jenkins' Residence,Friendsanity: Victor 14 <3,FRIENDSANITY,Stardew Valley Expanded -6213,Fairhaven Farm,Friendsanity: Andy 1 <3,FRIENDSANITY,Stardew Valley Expanded -6214,Fairhaven Farm,Friendsanity: Andy 2 <3,FRIENDSANITY,Stardew Valley Expanded -6215,Fairhaven Farm,Friendsanity: Andy 3 <3,FRIENDSANITY,Stardew Valley Expanded -6216,Fairhaven Farm,Friendsanity: Andy 4 <3,FRIENDSANITY,Stardew Valley Expanded -6217,Fairhaven Farm,Friendsanity: Andy 5 <3,FRIENDSANITY,Stardew Valley Expanded -6218,Fairhaven Farm,Friendsanity: Andy 6 <3,FRIENDSANITY,Stardew Valley Expanded -6219,Fairhaven Farm,Friendsanity: Andy 7 <3,FRIENDSANITY,Stardew Valley Expanded -6220,Fairhaven Farm,Friendsanity: Andy 8 <3,FRIENDSANITY,Stardew Valley Expanded -6221,Fairhaven Farm,Friendsanity: Andy 9 <3,FRIENDSANITY,Stardew Valley Expanded -6222,Fairhaven Farm,Friendsanity: Andy 10 <3,FRIENDSANITY,Stardew Valley Expanded -6223,Aurora Vineyard,Friendsanity: Apples 1 <3,FRIENDSANITY,Stardew Valley Expanded -6224,Aurora Vineyard,Friendsanity: Apples 2 <3,FRIENDSANITY,Stardew Valley Expanded -6225,Aurora Vineyard,Friendsanity: Apples 3 <3,FRIENDSANITY,Stardew Valley Expanded -6226,Aurora Vineyard,Friendsanity: Apples 4 <3,FRIENDSANITY,Stardew Valley Expanded -6227,Aurora Vineyard,Friendsanity: Apples 5 <3,FRIENDSANITY,Stardew Valley Expanded -6228,Aurora Vineyard,Friendsanity: Apples 6 <3,FRIENDSANITY,Stardew Valley Expanded -6229,Aurora Vineyard,Friendsanity: Apples 7 <3,FRIENDSANITY,Stardew Valley Expanded -6230,Aurora Vineyard,Friendsanity: Apples 8 <3,FRIENDSANITY,Stardew Valley Expanded -6231,Aurora Vineyard,Friendsanity: Apples 9 <3,FRIENDSANITY,Stardew Valley Expanded -6232,Aurora Vineyard,Friendsanity: Apples 10 <3,FRIENDSANITY,Stardew Valley Expanded -6233,Museum,Friendsanity: Gunther 1 <3,FRIENDSANITY,Stardew Valley Expanded -6234,Museum,Friendsanity: Gunther 2 <3,FRIENDSANITY,Stardew Valley Expanded -6235,Museum,Friendsanity: Gunther 3 <3,FRIENDSANITY,Stardew Valley Expanded -6236,Museum,Friendsanity: Gunther 4 <3,FRIENDSANITY,Stardew Valley Expanded -6237,Museum,Friendsanity: Gunther 5 <3,FRIENDSANITY,Stardew Valley Expanded -6238,Museum,Friendsanity: Gunther 6 <3,FRIENDSANITY,Stardew Valley Expanded -6239,Museum,Friendsanity: Gunther 7 <3,FRIENDSANITY,Stardew Valley Expanded -6240,Museum,Friendsanity: Gunther 8 <3,FRIENDSANITY,Stardew Valley Expanded -6241,Museum,Friendsanity: Gunther 9 <3,FRIENDSANITY,Stardew Valley Expanded -6242,Museum,Friendsanity: Gunther 10 <3,FRIENDSANITY,Stardew Valley Expanded -6243,JojaMart,Friendsanity: Martin 1 <3,FRIENDSANITY,Stardew Valley Expanded -6244,JojaMart,Friendsanity: Martin 2 <3,FRIENDSANITY,Stardew Valley Expanded -6245,JojaMart,Friendsanity: Martin 3 <3,FRIENDSANITY,Stardew Valley Expanded -6246,JojaMart,Friendsanity: Martin 4 <3,FRIENDSANITY,Stardew Valley Expanded -6247,JojaMart,Friendsanity: Martin 5 <3,FRIENDSANITY,Stardew Valley Expanded -6248,JojaMart,Friendsanity: Martin 6 <3,FRIENDSANITY,Stardew Valley Expanded -6249,JojaMart,Friendsanity: Martin 7 <3,FRIENDSANITY,Stardew Valley Expanded -6250,JojaMart,Friendsanity: Martin 8 <3,FRIENDSANITY,Stardew Valley Expanded -6251,JojaMart,Friendsanity: Martin 9 <3,FRIENDSANITY,Stardew Valley Expanded -6252,JojaMart,Friendsanity: Martin 10 <3,FRIENDSANITY,Stardew Valley Expanded -6253,Adventurer's Guild,Friendsanity: Marlon 1 <3,FRIENDSANITY,Stardew Valley Expanded -6254,Adventurer's Guild,Friendsanity: Marlon 2 <3,FRIENDSANITY,Stardew Valley Expanded -6255,Adventurer's Guild,Friendsanity: Marlon 3 <3,FRIENDSANITY,Stardew Valley Expanded -6256,Adventurer's Guild,Friendsanity: Marlon 4 <3,FRIENDSANITY,Stardew Valley Expanded -6257,Adventurer's Guild,Friendsanity: Marlon 5 <3,FRIENDSANITY,Stardew Valley Expanded -6258,Adventurer's Guild,Friendsanity: Marlon 6 <3,FRIENDSANITY,Stardew Valley Expanded -6259,Adventurer's Guild,Friendsanity: Marlon 7 <3,FRIENDSANITY,Stardew Valley Expanded -6260,Adventurer's Guild,Friendsanity: Marlon 8 <3,FRIENDSANITY,Stardew Valley Expanded -6261,Adventurer's Guild,Friendsanity: Marlon 9 <3,FRIENDSANITY,Stardew Valley Expanded -6262,Adventurer's Guild,Friendsanity: Marlon 10 <3,FRIENDSANITY,Stardew Valley Expanded -6263,Wizard Tower,Friendsanity: Morgan 1 <3,FRIENDSANITY,Stardew Valley Expanded -6264,Wizard Tower,Friendsanity: Morgan 2 <3,FRIENDSANITY,Stardew Valley Expanded -6265,Wizard Tower,Friendsanity: Morgan 3 <3,FRIENDSANITY,Stardew Valley Expanded -6266,Wizard Tower,Friendsanity: Morgan 4 <3,FRIENDSANITY,Stardew Valley Expanded -6267,Wizard Tower,Friendsanity: Morgan 5 <3,FRIENDSANITY,Stardew Valley Expanded -6268,Wizard Tower,Friendsanity: Morgan 6 <3,FRIENDSANITY,Stardew Valley Expanded -6269,Wizard Tower,Friendsanity: Morgan 7 <3,FRIENDSANITY,Stardew Valley Expanded -6270,Wizard Tower,Friendsanity: Morgan 8 <3,FRIENDSANITY,Stardew Valley Expanded -6271,Wizard Tower,Friendsanity: Morgan 9 <3,FRIENDSANITY,Stardew Valley Expanded -6272,Wizard Tower,Friendsanity: Morgan 10 <3,FRIENDSANITY,Stardew Valley Expanded -6273,Scarlett's House,Friendsanity: Scarlett 1 <3,FRIENDSANITY,Stardew Valley Expanded -6274,Scarlett's House,Friendsanity: Scarlett 2 <3,FRIENDSANITY,Stardew Valley Expanded -6275,Scarlett's House,Friendsanity: Scarlett 3 <3,FRIENDSANITY,Stardew Valley Expanded -6276,Scarlett's House,Friendsanity: Scarlett 4 <3,FRIENDSANITY,Stardew Valley Expanded -6277,Scarlett's House,Friendsanity: Scarlett 5 <3,FRIENDSANITY,Stardew Valley Expanded -6278,Scarlett's House,Friendsanity: Scarlett 6 <3,FRIENDSANITY,Stardew Valley Expanded -6279,Scarlett's House,Friendsanity: Scarlett 7 <3,FRIENDSANITY,Stardew Valley Expanded -6280,Scarlett's House,Friendsanity: Scarlett 8 <3,FRIENDSANITY,Stardew Valley Expanded -6281,Scarlett's House,Friendsanity: Scarlett 9 <3,FRIENDSANITY,Stardew Valley Expanded -6282,Scarlett's House,Friendsanity: Scarlett 10 <3,FRIENDSANITY,Stardew Valley Expanded -6283,Susan's House,Friendsanity: Susan 1 <3,FRIENDSANITY,Stardew Valley Expanded -6284,Susan's House,Friendsanity: Susan 2 <3,FRIENDSANITY,Stardew Valley Expanded -6285,Susan's House,Friendsanity: Susan 3 <3,FRIENDSANITY,Stardew Valley Expanded -6286,Susan's House,Friendsanity: Susan 4 <3,FRIENDSANITY,Stardew Valley Expanded -6287,Susan's House,Friendsanity: Susan 5 <3,FRIENDSANITY,Stardew Valley Expanded -6288,Susan's House,Friendsanity: Susan 6 <3,FRIENDSANITY,Stardew Valley Expanded -6289,Susan's House,Friendsanity: Susan 7 <3,FRIENDSANITY,Stardew Valley Expanded -6290,Susan's House,Friendsanity: Susan 8 <3,FRIENDSANITY,Stardew Valley Expanded -6291,Susan's House,Friendsanity: Susan 9 <3,FRIENDSANITY,Stardew Valley Expanded -6292,Susan's House,Friendsanity: Susan 10 <3,FRIENDSANITY,Stardew Valley Expanded -6293,JojaMart,Friendsanity: Morris 1 <3,FRIENDSANITY,Stardew Valley Expanded -6294,JojaMart,Friendsanity: Morris 2 <3,FRIENDSANITY,Stardew Valley Expanded -6295,JojaMart,Friendsanity: Morris 3 <3,FRIENDSANITY,Stardew Valley Expanded -6296,JojaMart,Friendsanity: Morris 4 <3,FRIENDSANITY,Stardew Valley Expanded -6297,JojaMart,Friendsanity: Morris 5 <3,FRIENDSANITY,Stardew Valley Expanded -6298,JojaMart,Friendsanity: Morris 6 <3,FRIENDSANITY,Stardew Valley Expanded -6299,JojaMart,Friendsanity: Morris 7 <3,FRIENDSANITY,Stardew Valley Expanded -6300,JojaMart,Friendsanity: Morris 8 <3,FRIENDSANITY,Stardew Valley Expanded -6301,JojaMart,Friendsanity: Morris 9 <3,FRIENDSANITY,Stardew Valley Expanded -6302,JojaMart,Friendsanity: Morris 10 <3,FRIENDSANITY,Stardew Valley Expanded -6303,Witch's Swamp,Friendsanity: Zic 1 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6304,Witch's Swamp,Friendsanity: Zic 2 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6305,Witch's Swamp,Friendsanity: Zic 3 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6306,Witch's Swamp,Friendsanity: Zic 4 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6307,Witch's Swamp,Friendsanity: Zic 5 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6308,Witch's Swamp,Friendsanity: Zic 6 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6309,Witch's Swamp,Friendsanity: Zic 7 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6310,Witch's Swamp,Friendsanity: Zic 8 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6311,Witch's Swamp,Friendsanity: Zic 9 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6312,Witch's Swamp,Friendsanity: Zic 10 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul -6313,Witch's Attic,Friendsanity: Alecto 1 <3,FRIENDSANITY,Alecto the Witch -6314,Witch's Attic,Friendsanity: Alecto 2 <3,FRIENDSANITY,Alecto the Witch -6315,Witch's Attic,Friendsanity: Alecto 3 <3,FRIENDSANITY,Alecto the Witch -6316,Witch's Attic,Friendsanity: Alecto 4 <3,FRIENDSANITY,Alecto the Witch -6317,Witch's Attic,Friendsanity: Alecto 5 <3,FRIENDSANITY,Alecto the Witch -6318,Witch's Attic,Friendsanity: Alecto 6 <3,FRIENDSANITY,Alecto the Witch -6319,Witch's Attic,Friendsanity: Alecto 7 <3,FRIENDSANITY,Alecto the Witch -6320,Witch's Attic,Friendsanity: Alecto 8 <3,FRIENDSANITY,Alecto the Witch -6321,Witch's Attic,Friendsanity: Alecto 9 <3,FRIENDSANITY,Alecto the Witch -6322,Witch's Attic,Friendsanity: Alecto 10 <3,FRIENDSANITY,Alecto the Witch -6323,Mouse House,Friendsanity: Lacey 1 <3,FRIENDSANITY,Hat Mouse Lacey -6324,Mouse House,Friendsanity: Lacey 2 <3,FRIENDSANITY,Hat Mouse Lacey -6325,Mouse House,Friendsanity: Lacey 3 <3,FRIENDSANITY,Hat Mouse Lacey -6326,Mouse House,Friendsanity: Lacey 4 <3,FRIENDSANITY,Hat Mouse Lacey -6327,Mouse House,Friendsanity: Lacey 5 <3,FRIENDSANITY,Hat Mouse Lacey -6328,Mouse House,Friendsanity: Lacey 6 <3,FRIENDSANITY,Hat Mouse Lacey -6329,Mouse House,Friendsanity: Lacey 7 <3,FRIENDSANITY,Hat Mouse Lacey -6330,Mouse House,Friendsanity: Lacey 8 <3,FRIENDSANITY,Hat Mouse Lacey -6331,Mouse House,Friendsanity: Lacey 9 <3,FRIENDSANITY,Hat Mouse Lacey -6332,Mouse House,Friendsanity: Lacey 10 <3,FRIENDSANITY,Hat Mouse Lacey -6333,Mouse House,Friendsanity: Lacey 11 <3,FRIENDSANITY,Hat Mouse Lacey -6334,Mouse House,Friendsanity: Lacey 12 <3,FRIENDSANITY,Hat Mouse Lacey -6335,Mouse House,Friendsanity: Lacey 13 <3,FRIENDSANITY,Hat Mouse Lacey -6336,Mouse House,Friendsanity: Lacey 14 <3,FRIENDSANITY,Hat Mouse Lacey -6337,Boarding House - First Floor,Friendsanity: Joel 1 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6338,Boarding House - First Floor,Friendsanity: Joel 2 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6339,Boarding House - First Floor,Friendsanity: Joel 3 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6340,Boarding House - First Floor,Friendsanity: Joel 4 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6341,Boarding House - First Floor,Friendsanity: Joel 5 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6342,Boarding House - First Floor,Friendsanity: Joel 6 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6343,Boarding House - First Floor,Friendsanity: Joel 7 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6344,Boarding House - First Floor,Friendsanity: Joel 8 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6345,Boarding House - First Floor,Friendsanity: Joel 9 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6346,Boarding House - First Floor,Friendsanity: Joel 10 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6347,Boarding House - First Floor,Friendsanity: Sheila 1 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6348,Boarding House - First Floor,Friendsanity: Sheila 2 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6349,Boarding House - First Floor,Friendsanity: Sheila 3 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6350,Boarding House - First Floor,Friendsanity: Sheila 4 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6351,Boarding House - First Floor,Friendsanity: Sheila 5 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6352,Boarding House - First Floor,Friendsanity: Sheila 6 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6353,Boarding House - First Floor,Friendsanity: Sheila 7 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6354,Boarding House - First Floor,Friendsanity: Sheila 8 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6355,Boarding House - First Floor,Friendsanity: Sheila 9 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6356,Boarding House - First Floor,Friendsanity: Sheila 10 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6357,Boarding House - First Floor,Friendsanity: Sheila 11 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6358,Boarding House - First Floor,Friendsanity: Sheila 12 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6359,Boarding House - First Floor,Friendsanity: Sheila 13 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6360,Boarding House - First Floor,Friendsanity: Sheila 14 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6361,The Lost Valley,Friendsanity: Gregory 1 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6362,The Lost Valley,Friendsanity: Gregory 2 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6363,The Lost Valley,Friendsanity: Gregory 3 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6364,The Lost Valley,Friendsanity: Gregory 4 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6365,The Lost Valley,Friendsanity: Gregory 5 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6366,The Lost Valley,Friendsanity: Gregory 6 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6367,The Lost Valley,Friendsanity: Gregory 7 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6368,The Lost Valley,Friendsanity: Gregory 8 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6369,The Lost Valley,Friendsanity: Gregory 9 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6370,The Lost Valley,Friendsanity: Gregory 10 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6371,The Lost Valley,Friendsanity: Gregory 11 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6372,The Lost Valley,Friendsanity: Gregory 12 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6373,The Lost Valley,Friendsanity: Gregory 13 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -6374,The Lost Valley,Friendsanity: Gregory 14 <3,FRIENDSANITY,Boarding House and Bus Stop Extension -7001,Pierre's General Store,Premium Pack,BACKPACK,Bigger Backpack -7002,Carpenter Shop,Tractor Garage Blueprint,BUILDING_BLUEPRINT,Tractor Mod -7003,The Deep Woods Depth 100,Pet the Deep Woods Unicorn,MANDATORY,DeepWoods -7004,The Deep Woods Depth 50,Breaking Up Deep Woods Gingerbread House,MANDATORY,DeepWoods -7005,The Deep Woods Depth 50,Drinking From Deep Woods Fountain,MANDATORY,DeepWoods -7006,The Deep Woods Depth 100,Deep Woods Treasure Chest,MANDATORY,DeepWoods -7007,The Deep Woods Depth 100,Deep Woods Trash Bin,MANDATORY,DeepWoods -7008,The Deep Woods Depth 50,Chop Down a Deep Woods Iridium Tree,MANDATORY,DeepWoods -7009,The Deep Woods Depth 10,The Deep Woods: Depth 10,ELEVATOR,DeepWoods -7010,The Deep Woods Depth 20,The Deep Woods: Depth 20,ELEVATOR,DeepWoods -7011,The Deep Woods Depth 30,The Deep Woods: Depth 30,ELEVATOR,DeepWoods -7012,The Deep Woods Depth 40,The Deep Woods: Depth 40,ELEVATOR,DeepWoods -7013,The Deep Woods Depth 50,The Deep Woods: Depth 50,ELEVATOR,DeepWoods -7014,The Deep Woods Depth 60,The Deep Woods: Depth 60,ELEVATOR,DeepWoods -7015,The Deep Woods Depth 70,The Deep Woods: Depth 70,ELEVATOR,DeepWoods -7016,The Deep Woods Depth 80,The Deep Woods: Depth 80,ELEVATOR,DeepWoods -7017,The Deep Woods Depth 90,The Deep Woods: Depth 90,ELEVATOR,DeepWoods -7018,The Deep Woods Depth 100,The Deep Woods: Depth 100,ELEVATOR,DeepWoods -7019,The Deep Woods Depth 50,Purify an Infested Lichtung,MANDATORY,DeepWoods -7020,Skull Cavern Floor 25,Skull Cavern: Floor 25,ELEVATOR,Skull Cavern Elevator -7021,Skull Cavern Floor 50,Skull Cavern: Floor 50,ELEVATOR,Skull Cavern Elevator -7022,Skull Cavern Floor 75,Skull Cavern: Floor 75,ELEVATOR,Skull Cavern Elevator -7023,Skull Cavern Floor 100,Skull Cavern: Floor 100,ELEVATOR,Skull Cavern Elevator -7024,Skull Cavern Floor 125,Skull Cavern: Floor 125,ELEVATOR,Skull Cavern Elevator -7025,Skull Cavern Floor 150,Skull Cavern: Floor 150,ELEVATOR,Skull Cavern Elevator -7026,Skull Cavern Floor 175,Skull Cavern: Floor 175,ELEVATOR,Skull Cavern Elevator -7027,Skull Cavern Floor 200,Skull Cavern: Floor 200,ELEVATOR,Skull Cavern Elevator -7028,The Deep Woods Depth 100,The Sword in the Stone,MANDATORY,DeepWoods -7051,Abandoned Mines - 1A,Abandoned Treasure - Floor 1A,MANDATORY,Boarding House and Bus Stop Extension -7052,Abandoned Mines - 1B,Abandoned Treasure - Floor 1B,MANDATORY,Boarding House and Bus Stop Extension -7053,Abandoned Mines - 2A,Abandoned Treasure - Floor 2A,MANDATORY,Boarding House and Bus Stop Extension -7054,Abandoned Mines - 2B,Abandoned Treasure - Floor 2B,MANDATORY,Boarding House and Bus Stop Extension -7055,Abandoned Mines - 3,Abandoned Treasure - Floor 3,MANDATORY,Boarding House and Bus Stop Extension -7056,Abandoned Mines - 4,Abandoned Treasure - Floor 4,MANDATORY,Boarding House and Bus Stop Extension -7057,Abandoned Mines - 5,Abandoned Treasure - Floor 5,MANDATORY,Boarding House and Bus Stop Extension -7401,Farm,Cook Magic Elixir,COOKSANITY,Magic -7402,Farm,Craft Travel Core,CRAFTSANITY,Magic -7403,Farm,Craft Haste Elixir,CRAFTSANITY,Stardew Valley Expanded -7404,Farm,Craft Hero Elixir,CRAFTSANITY,Stardew Valley Expanded -7405,Farm,Craft Armor Elixir,CRAFTSANITY,Stardew Valley Expanded -7406,Witch's Swamp,Craft Ginger Tincture,"CRAFTSANITY,GINGER_ISLAND",Distant Lands - Witch Swamp Overhaul -7407,Farm,Craft Glass Path,CRAFTSANITY,Archaeology -7408,Farm,Craft Glass Brazier,CRAFTSANITY,Archaeology -7409,Farm,Craft Glass Fence,CRAFTSANITY,Archaeology -7410,Farm,Craft Bone Path,CRAFTSANITY,Archaeology -7411,Farm,Craft Water Shifter,CRAFTSANITY,Archaeology -7412,Farm,Craft Wooden Display,CRAFTSANITY,Archaeology -7413,Farm,Craft Hardwood Display,CRAFTSANITY,Archaeology -7414,Farm,Craft Dwarf Gadget: Infinite Volcano Simulation,"CRAFTSANITY,GINGER_ISLAND",Archaeology -7415,Farm,Craft Grinder,CRAFTSANITY,Archaeology -7416,Farm,Craft Preservation Chamber,CRAFTSANITY,Archaeology -7417,Farm,Craft Hardwood Preservation Chamber,CRAFTSANITY,Archaeology -7418,Farm,Craft Ancient Battery Production Station,CRAFTSANITY,Archaeology -7419,Farm,Craft Neanderthal Skeleton,CRAFTSANITY,Boarding House and Bus Stop Extension -7420,Farm,Craft Pterodactyl Skeleton L,CRAFTSANITY,Boarding House and Bus Stop Extension -7421,Farm,Craft Pterodactyl Skeleton M,CRAFTSANITY,Boarding House and Bus Stop Extension -7422,Farm,Craft Pterodactyl Skeleton R,CRAFTSANITY,Boarding House and Bus Stop Extension -7423,Farm,Craft T-Rex Skeleton L,CRAFTSANITY,Boarding House and Bus Stop Extension -7424,Farm,Craft T-Rex Skeleton M,CRAFTSANITY,Boarding House and Bus Stop Extension -7425,Farm,Craft T-Rex Skeleton R,CRAFTSANITY,Boarding House and Bus Stop Extension -7426,Farm,Craft Restoration Table,CRAFTSANITY,Archaeology -7427,Farm,Craft Rusty Path,CRAFTSANITY,Archaeology -7428,Farm,Craft Rusty Brazier,CRAFTSANITY,Archaeology -7429,Farm,Craft Lucky Ring,CRAFTSANITY,Archaeology -7430,Farm,Craft Bone Fence,CRAFTSANITY,Archaeology -7431,Farm,Craft Bouquet,CRAFTSANITY,Socializing Skill -7432,Farm,Craft Trash Bin,CRAFTSANITY,Binning Skill -7433,Farm,Craft Composter,CRAFTSANITY,Binning Skill -7434,Farm,Craft Recycling Bin,CRAFTSANITY,Binning Skill -7435,Farm,Craft Advanced Recycling Machine,CRAFTSANITY,Binning Skill -7440,Farm,Craft Copper Slot Machine,"CRAFTSANITY",Luck Skill -7441,Farm,Craft Gold Slot Machine,"CRAFTSANITY",Luck Skill -7442,Farm,Craft Iridium Slot Machine,"CRAFTSANITY",Luck Skill -7443,Farm,Craft Radioactive Slot Machine,"CRAFTSANITY,GINGER_ISLAND",Luck Skill -7451,Adventurer's Guild,Magic Elixir Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Magic -7452,Adventurer's Guild,Travel Core Recipe,CRAFTSANITY,Magic -7453,Alesia Shop,Haste Elixir Recipe,CRAFTSANITY,Stardew Valley Expanded -7454,Isaac Shop,Hero Elixir Recipe,CRAFTSANITY,Stardew Valley Expanded -7455,Alesia Shop,Armor Elixir Recipe,CRAFTSANITY,Stardew Valley Expanded -7501,Mountain,Missing Envelope,"STORY_QUEST",Ayeisha - The Postal Worker (Custom NPC) -7502,Forest,Ayeisha's Lost Ring,"STORY_QUEST",Ayeisha - The Postal Worker (Custom NPC) -7503,Forest,Mr.Ginger's request,"STORY_QUEST",Mister Ginger (cat npc) -7504,Forest,Juna's Drink Request,"STORY_QUEST",Juna - Roommate NPC -7505,Forest,Juna's BFF Request,"STORY_QUEST",Juna - Roommate NPC -7506,Forest,Juna's Monster Mash,SPECIAL_ORDER_BOARD,Juna - Roommate NPC -7507,Adventurer's Guild,Marlon's Boat,"STORY_QUEST,GINGER_ISLAND",Stardew Valley Expanded -7508,Railroad,The Railroad Boulder,"STORY_QUEST",Stardew Valley Expanded -7509,Grandpa's Shed Interior,Grandpa's Shed,"STORY_QUEST",Stardew Valley Expanded -7510,Aurora Vineyard,Aurora Vineyard,"STORY_QUEST",Stardew Valley Expanded -7511,Lance's House Main,Monster Crops,"STORY_QUEST,GINGER_ISLAND",Stardew Valley Expanded -7512,Sewer,Void Soul Retrieval,"STORY_QUEST,GINGER_ISLAND",Stardew Valley Expanded -7513,Fairhaven Farm,Andy's Cellar,SPECIAL_ORDER_BOARD,Stardew Valley Expanded -7514,Adventurer's Guild,A Mysterious Venture,SPECIAL_ORDER_BOARD,Stardew Valley Expanded -7515,Jenkins' Residence,An Elegant Reception,SPECIAL_ORDER_BOARD,Stardew Valley Expanded -7516,Sophia's House,Fairy Garden,"SPECIAL_ORDER_BOARD,GINGER_ISLAND",Stardew Valley Expanded -7517,Susan's House,Homemade Fertilizer,SPECIAL_ORDER_BOARD,Stardew Valley Expanded -7519,Witch's Swamp,Corrupted Crops Task,GINGER_ISLAND,Distant Lands - Witch Swamp Overhaul -7520,Witch's Swamp,A New Pot,STORY_QUEST,Distant Lands - Witch Swamp Overhaul -7521,Witch's Swamp,Fancy Blanket Task,STORY_QUEST,Distant Lands - Witch Swamp Overhaul -7522,Witch's Swamp,Witch's order,GINGER_ISLAND,Distant Lands - Witch Swamp Overhaul -7523,Boarding House - First Floor,Pumpkin Soup,STORY_QUEST,Boarding House and Bus Stop Extension -7524,Museum,Geode Order,SPECIAL_ORDER_BOARD,Professor Jasper Thomas -7525,Museum,Dwarven Scrolls,SPECIAL_ORDER_BOARD,Professor Jasper Thomas -7526,Mouse House,Hats for the Hat Mouse,STORY_QUEST,Hat Mouse Lacey -7551,Kitchen,Cook Baked Berry Oatmeal,COOKSANITY,Stardew Valley Expanded -7552,Kitchen,Cook Flower Cookie,COOKSANITY,Stardew Valley Expanded -7553,Kitchen,Cook Big Bark Burger,COOKSANITY,Stardew Valley Expanded -7554,Kitchen,Cook Frog Legs,COOKSANITY,Stardew Valley Expanded -7555,Kitchen,Cook Glazed Butterfish,COOKSANITY,Stardew Valley Expanded -7556,Kitchen,Cook Mixed Berry Pie,COOKSANITY,Stardew Valley Expanded -7557,Kitchen,Cook Mushroom Berry Rice,COOKSANITY,Stardew Valley Expanded -7558,Kitchen,Cook Seaweed Salad,COOKSANITY,Stardew Valley Expanded -7559,Kitchen,Cook Void Delight,COOKSANITY,Stardew Valley Expanded -7560,Kitchen,Cook Void Salmon Sushi,COOKSANITY,Stardew Valley Expanded -7561,Kitchen,Cook Mushroom Kebab,COOKSANITY,Distant Lands - Witch Swamp Overhaul -7562,Kitchen,Cook Crayfish Soup,COOKSANITY,Distant Lands - Witch Swamp Overhaul -7563,Kitchen,Cook Pemmican,COOKSANITY,Distant Lands - Witch Swamp Overhaul -7564,Kitchen,Cook Void Mint Tea,COOKSANITY,Distant Lands - Witch Swamp Overhaul -7565,Kitchen,Cook Special Pumpkin Soup,COOKSANITY,Boarding House and Bus Stop Extension -7566,Kitchen,Cook Digger's Delight,COOKSANITY,Archaeology -7567,Kitchen,Cook Rocky Root Coffee,COOKSANITY,Archaeology -7568,Kitchen,Cook Ancient Jello,COOKSANITY,Archaeology -7569,Kitchen,Cook Grilled Cheese,COOKSANITY,Binning Skill -7570,Kitchen,Cook Fish Casserole,COOKSANITY,Binning Skill -7601,Bear Shop,Baked Berry Oatmeal Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded -7602,Bear Shop,Flower Cookie Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded -7603,Saloon,Big Bark Burger Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded -7604,Adventurer's Guild,Frog Legs Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded -7605,Saloon,Glazed Butterfish Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded -7606,Saloon,Mixed Berry Pie Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded -7607,Adventurer's Guild,Mushroom Berry Rice Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded -7608,Adventurer's Guild,Seaweed Salad Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded -7609,Sewer,Void Delight Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded -7610,Sewer,Void Salmon Sushi Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded -7611,Witch's Swamp,Mushroom Kebab Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul -7613,Witch's Swamp,Pemmican Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul -7614,Witch's Swamp,Void Mint Tea Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul -7616,Mines Dwarf Shop,Neanderthal Skeleton Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7617,Mines Dwarf Shop,Pterodactyl Skeleton L Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7618,Mines Dwarf Shop,Pterodactyl Skeleton M Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7619,Mines Dwarf Shop,Pterodactyl Skeleton R Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7620,Mines Dwarf Shop,T-Rex Skeleton L Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7621,Mines Dwarf Shop,T-Rex Skeleton M Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7622,Mines Dwarf Shop,T-Rex Skeleton R Recipe,CRAFTSANITY,Boarding House and Bus Stop Extension -7623,Farm,Digger's Delight Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology -7624,Farm,Rocky Root Coffee Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology -7625,Farm,Ancient Jello Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology -7627,Farm,Grilled Cheese Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Binning Skill -7628,Farm,Fish Casserole Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Binning Skill -7651,Alesia Shop,Tempered Galaxy Dagger,MANDATORY,Stardew Valley Expanded -7652,Isaac Shop,Tempered Galaxy Sword,MANDATORY,Stardew Valley Expanded -7653,Isaac Shop,Tempered Galaxy Hammer,MANDATORY,Stardew Valley Expanded -7701,Island South,Fishsanity: Baby Lunaloo,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7702,Crimson Badlands,Fishsanity: Bonefish,FISHSANITY,Stardew Valley Expanded -7703,Forest,Fishsanity: Bull Trout,FISHSANITY,Stardew Valley Expanded -7704,Forest West,Fishsanity: Butterfish,FISHSANITY,Stardew Valley Expanded -7705,Island South,Fishsanity: Clownfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7706,Highlands Outside,Fishsanity: Daggerfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7707,Mountain,Fishsanity: Frog,FISHSANITY,Stardew Valley Expanded -7708,Highlands Outside,Fishsanity: Gemfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7709,Sprite Spring,Fishsanity: Goldenfish,FISHSANITY,Stardew Valley Expanded -7710,Secret Woods,Fishsanity: Grass Carp,FISHSANITY,Stardew Valley Expanded -7711,Forest West,Fishsanity: King Salmon,FISHSANITY,Stardew Valley Expanded -7712,Island West,Fishsanity: Lunaloo,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7713,Sprite Spring,Fishsanity: Meteor Carp,FISHSANITY,Stardew Valley Expanded -7714,Town,Fishsanity: Minnow,FISHSANITY,Stardew Valley Expanded -7715,Forest West,Fishsanity: Puppyfish,FISHSANITY,Stardew Valley Expanded -7716,Sewer,Fishsanity: Radioactive Bass,FISHSANITY,Stardew Valley Expanded -7717,Island West,Fishsanity: Seahorse,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7718,Island West,Fishsanity: Sea Sponge,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7719,Island South,Fishsanity: Shiny Lunaloo,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7720,Mutant Bug Lair,Fishsanity: Snatcher Worm,FISHSANITY,Stardew Valley Expanded -7721,Beach,Fishsanity: Starfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7722,Fable Reef,Fishsanity: Torpedo Trout,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded -7723,Witch's Swamp,Fishsanity: Void Eel,FISHSANITY,Stardew Valley Expanded -7724,Mutant Bug Lair,Fishsanity: Water Grub,FISHSANITY,Stardew Valley Expanded -7725,Crimson Badlands,Fishsanity: Undeadfish,FISHSANITY,Stardew Valley Expanded -7726,Shearwater Bridge,Fishsanity: Kittyfish,FISHSANITY,Stardew Valley Expanded -7728,Witch's Swamp,Fishsanity: Void Minnow,FISHSANITY,Distant Lands - Witch Swamp Overhaul -7729,Witch's Swamp,Fishsanity: Swamp Leech,FISHSANITY,Distant Lands - Witch Swamp Overhaul -7730,Witch's Swamp,Fishsanity: Giant Horsehoe Crab,FISHSANITY,Distant Lands - Witch Swamp Overhaul -7731,Witch's Swamp,Fishsanity: Purple Algae,FISHSANITY,Distant Lands - Witch Swamp Overhaul -7901,Farm,Harvest Monster Fruit,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded -7902,Farm,Harvest Salal Berry,CROPSANITY,Stardew Valley Expanded -7903,Farm,Harvest Slime Berry,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded -7904,Farm,Harvest Ancient Fiber,CROPSANITY,Stardew Valley Expanded -7905,Farm,Harvest Monster Mushroom,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded -7906,Farm,Harvest Void Root,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded -7907,Farm,Harvest Void Mint Leaves,CROPSANITY,Distant Lands - Witch Swamp Overhaul -7908,Farm,Harvest Vile Ancient Fruit,CROPSANITY,Distant Lands - Witch Swamp Overhaul -8001,Shipping,Shipsanity: Magic Elixir,SHIPSANITY,Magic -8002,Shipping,Shipsanity: Travel Core,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Magic -8003,Shipping,Shipsanity: Aegis Elixir,SHIPSANITY,Stardew Valley Expanded -8004,Shipping,Shipsanity: Aged Blue Moon Wine,SHIPSANITY,Stardew Valley Expanded -8005,Shipping,Shipsanity: Ancient Fern Seed,SHIPSANITY,Stardew Valley Expanded -8006,Shipping,Shipsanity: Ancient Fiber,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8007,Shipping,Shipsanity: Armor Elixir,SHIPSANITY,Stardew Valley Expanded -8008,Shipping,Shipsanity: Baby Lunaloo,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8009,Shipping,Shipsanity: Baked Berry Oatmeal,SHIPSANITY,Stardew Valley Expanded -8010,Shipping,Shipsanity: Barbarian Elixir,SHIPSANITY,Stardew Valley Expanded -8011,Shipping,Shipsanity: Bearberry,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8012,Shipping,Shipsanity: Big Bark Burger,SHIPSANITY,Stardew Valley Expanded -8013,Shipping,Shipsanity: Conch,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8014,Shipping,Shipsanity: Blue Moon Wine,SHIPSANITY,Stardew Valley Expanded -8015,Shipping,Shipsanity: Bonefish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8016,Shipping,Shipsanity: Bull Trout,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8017,Shipping,Shipsanity: Butterfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8018,Shipping,Shipsanity: Clownfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8019,Shipping,Shipsanity: Daggerfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8020,Shipping,Shipsanity: Dewdrop Berry,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8021,Shipping,Shipsanity: Sand Dollar,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8023,Shipping,Shipsanity: Ferngill Primrose,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8024,Shipping,Shipsanity: Flower Cookie,SHIPSANITY,Stardew Valley Expanded -8025,Shipping,Shipsanity: Frog,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8026,Shipping,Shipsanity: Frog Legs,SHIPSANITY,Stardew Valley Expanded -8027,Shipping,Shipsanity: Fungus Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded -8029,Shipping,Shipsanity: Gemfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8030,Shipping,Shipsanity: Glazed Butterfish,"SHIPSANITY",Stardew Valley Expanded -8031,Shipping,Shipsanity: Golden Ocean Flower,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8032,Shipping,Shipsanity: Goldenfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8033,Shipping,Shipsanity: Goldenrod,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8034,Shipping,Shipsanity: Grampleton Orange Chicken,SHIPSANITY,Stardew Valley Expanded -8035,Shipping,Shipsanity: Grass Carp,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8036,Shipping,Shipsanity: Gravity Elixir,SHIPSANITY,Stardew Valley Expanded -8037,Shipping,Shipsanity: Green Mushroom,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8038,Shipping,Shipsanity: Haste Elixir,SHIPSANITY,Stardew Valley Expanded -8039,Shipping,Shipsanity: Hero Elixir,SHIPSANITY,Stardew Valley Expanded -8040,Shipping,Shipsanity: King Salmon,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8050,Shipping,Shipsanity: Kittyfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8051,Shipping,Shipsanity: Lightning Elixir,SHIPSANITY,Stardew Valley Expanded -8052,Shipping,Shipsanity: Four Leaf Clover,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8053,Shipping,Shipsanity: Lunaloo,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8054,Shipping,Shipsanity: Meteor Carp,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8055,Shipping,Shipsanity: Minnow,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8056,Shipping,Shipsanity: Mixed Berry Pie,SHIPSANITY,Stardew Valley Expanded -8057,Shipping,Shipsanity: Monster Fruit,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8058,Shipping,Shipsanity: Monster Mushroom,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8059,Shipping,Shipsanity: Mushroom Berry Rice,SHIPSANITY,Stardew Valley Expanded -8060,Shipping,Shipsanity: Mushroom Colony,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8061,Shipping,Shipsanity: Ornate Treasure Chest,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded -8062,Shipping,Shipsanity: Poison Mushroom,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8063,Shipping,Shipsanity: Puppyfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8064,Shipping,Shipsanity: Radioactive Bass,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8065,Shipping,Shipsanity: Red Baneberry,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8066,Shipping,Shipsanity: Rusty Blade,SHIPSANITY,Stardew Valley Expanded -8067,Shipping,Shipsanity: Salal Berry,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8068,Shipping,Shipsanity: Sea Sponge,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8069,Shipping,Shipsanity: Seahorse,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8070,Shipping,Shipsanity: Seaweed Salad,SHIPSANITY,Stardew Valley Expanded -8071,Shipping,Shipsanity: Shiny Lunaloo,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8072,Shipping,Shipsanity: Shrub Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded -8073,Shipping,Shipsanity: Slime Berry,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8074,Shipping,Shipsanity: Slime Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded -8075,Shipping,Shipsanity: Rafflesia,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8076,Shipping,Shipsanity: Sports Drink,SHIPSANITY,Stardew Valley Expanded -8077,Shipping,Shipsanity: Stalk Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded -8078,Shipping,Shipsanity: Stamina Capsule,SHIPSANITY,Stardew Valley Expanded -8079,Shipping,Shipsanity: Starfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8080,Shipping,Shipsanity: Swirl Stone,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8081,Shipping,Shipsanity: Thistle,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8082,Shipping,Shipsanity: Torpedo Trout,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded -8083,Shipping,Shipsanity: Undeadfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8084,Shipping,Shipsanity: Void Delight,SHIPSANITY,Stardew Valley Expanded -8085,Shipping,Shipsanity: Void Eel,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8086,Shipping,Shipsanity: Void Pebble,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8087,Shipping,Shipsanity: Void Root,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded -8088,Shipping,Shipsanity: Void Salmon Sushi,SHIPSANITY,Stardew Valley Expanded -8089,Shipping,Shipsanity: Void Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded -8090,Shipping,Shipsanity: Void Shard,SHIPSANITY,Stardew Valley Expanded -8091,Shipping,Shipsanity: Void Soul,SHIPSANITY,Stardew Valley Expanded -8092,Shipping,Shipsanity: Water Grub,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded -8093,Shipping,Shipsanity: Winter Star Rose,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded -8094,Shipping,Shipsanity: Wooden Display: Amphibian Fossil,SHIPSANITY,Archaeology -8095,Shipping,Shipsanity: Hardwood Display: Amphibian Fossil,SHIPSANITY,Archaeology -8096,Shipping,Shipsanity: Wooden Display: Anchor,SHIPSANITY,Archaeology -8097,Shipping,Shipsanity: Hardwood Display: Anchor,SHIPSANITY,Archaeology -8098,Shipping,Shipsanity: Wooden Display: Ancient Doll,SHIPSANITY,Archaeology -8099,Shipping,Shipsanity: Hardwood Display: Ancient Doll,SHIPSANITY,Archaeology -8100,Shipping,Shipsanity: Wooden Display: Ancient Drum,SHIPSANITY,Archaeology -8101,Shipping,Shipsanity: Hardwood Display: Ancient Drum,SHIPSANITY,Archaeology -8102,Shipping,Shipsanity: Wooden Display: Ancient Seed,SHIPSANITY,Archaeology -8103,Shipping,Shipsanity: Hardwood Display: Ancient Seed,SHIPSANITY,Archaeology -8104,Shipping,Shipsanity: Wooden Display: Ancient Sword,SHIPSANITY,Archaeology -8105,Shipping,Shipsanity: Hardwood Display: Ancient Sword,SHIPSANITY,Archaeology -8106,Shipping,Shipsanity: Wooden Display: Arrowhead,SHIPSANITY,Archaeology -8107,Shipping,Shipsanity: Hardwood Display: Arrowhead,SHIPSANITY,Archaeology -8108,Shipping,Shipsanity: Wooden Display: Bone Flute,SHIPSANITY,Archaeology -8109,Shipping,Shipsanity: Hardwood Display: Bone Flute,SHIPSANITY,Archaeology -8110,Shipping,Shipsanity: Wooden Display: Chewing Stick,SHIPSANITY,Archaeology -8111,Shipping,Shipsanity: Hardwood Display: Chewing Stick,SHIPSANITY,Archaeology -8112,Shipping,Shipsanity: Wooden Display: Chicken Statue,SHIPSANITY,Archaeology -8113,Shipping,Shipsanity: Hardwood Display: Chicken Statue,SHIPSANITY,Archaeology -8114,Shipping,Shipsanity: Wooden Display: Chipped Amphora,SHIPSANITY,Archaeology -8115,Shipping,Shipsanity: Hardwood Display: Chipped Amphora,SHIPSANITY,Archaeology -8116,Shipping,Shipsanity: Wooden Display: Dinosaur Egg,SHIPSANITY,Archaeology -8117,Shipping,Shipsanity: Hardwood Display: Dinosaur Egg,SHIPSANITY,Archaeology -8118,Shipping,Shipsanity: Wooden Display: Dried Starfish,SHIPSANITY,Archaeology -8119,Shipping,Shipsanity: Hardwood Display: Dried Starfish,SHIPSANITY,Archaeology -8120,Shipping,Shipsanity: Wooden Display: Dwarf Gadget,SHIPSANITY,Archaeology -8121,Shipping,Shipsanity: Hardwood Display: Dwarf Gadget,SHIPSANITY,Archaeology -8122,Shipping,Shipsanity: Wooden Display: Dwarf Scroll I,SHIPSANITY,Archaeology -8123,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll I,SHIPSANITY,Archaeology -8124,Shipping,Shipsanity: Wooden Display: Dwarf Scroll II,SHIPSANITY,Archaeology -8125,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll II,SHIPSANITY,Archaeology -8126,Shipping,Shipsanity: Wooden Display: Dwarf Scroll III,SHIPSANITY,Archaeology -8127,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll III,SHIPSANITY,Archaeology -8128,Shipping,Shipsanity: Wooden Display: Dwarf Scroll IV,SHIPSANITY,Archaeology -8129,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll IV,SHIPSANITY,Archaeology -8130,Shipping,Shipsanity: Wooden Display: Dwarvish Helm,SHIPSANITY,Archaeology -8131,Shipping,Shipsanity: Hardwood Display: Dwarvish Helm,SHIPSANITY,Archaeology -8132,Shipping,Shipsanity: Wooden Display: Elvish Jewelry,SHIPSANITY,Archaeology -8133,Shipping,Shipsanity: Hardwood Display: Elvish Jewelry,SHIPSANITY,Archaeology -8134,Shipping,Shipsanity: Wooden Display: Fossilized Leg,"SHIPSANITY,GINGER_ISLAND",Archaeology -8135,Shipping,Shipsanity: Hardwood Display: Fossilized Leg,"SHIPSANITY,GINGER_ISLAND",Archaeology -8136,Shipping,Shipsanity: Wooden Display: Fossilized Ribs,"SHIPSANITY,GINGER_ISLAND",Archaeology -8137,Shipping,Shipsanity: Hardwood Display: Fossilized Ribs,"SHIPSANITY,GINGER_ISLAND",Archaeology -8138,Shipping,Shipsanity: Wooden Display: Fossilized Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology -8139,Shipping,Shipsanity: Hardwood Display: Fossilized Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology -8140,Shipping,Shipsanity: Wooden Display: Fossilized Spine,"SHIPSANITY,GINGER_ISLAND",Archaeology -8141,Shipping,Shipsanity: Hardwood Display: Fossilized Spine,"SHIPSANITY,GINGER_ISLAND",Archaeology -8142,Shipping,Shipsanity: Wooden Display: Fossilized Tail,"SHIPSANITY,GINGER_ISLAND",Archaeology -8143,Shipping,Shipsanity: Hardwood Display: Fossilized Tail,"SHIPSANITY,GINGER_ISLAND",Archaeology -8144,Shipping,Shipsanity: Wooden Display: Glass Shards,SHIPSANITY,Archaeology -8145,Shipping,Shipsanity: Hardwood Display: Glass Shards,SHIPSANITY,Archaeology -8146,Shipping,Shipsanity: Wooden Display: Golden Mask,SHIPSANITY,Archaeology -8147,Shipping,Shipsanity: Hardwood Display: Golden Mask,SHIPSANITY,Archaeology -8148,Shipping,Shipsanity: Wooden Display: Golden Relic,SHIPSANITY,Archaeology -8149,Shipping,Shipsanity: Hardwood Display: Golden Relic,SHIPSANITY,Archaeology -8150,Shipping,Shipsanity: Wooden Display: Mummified Bat,"SHIPSANITY,GINGER_ISLAND",Archaeology -8151,Shipping,Shipsanity: Hardwood Display: Mummified Bat,"SHIPSANITY,GINGER_ISLAND",Archaeology -8152,Shipping,Shipsanity: Wooden Display: Mummified Frog,"SHIPSANITY,GINGER_ISLAND",Archaeology -8153,Shipping,Shipsanity: Hardwood Display: Mummified Frog,"SHIPSANITY,GINGER_ISLAND",Archaeology -8154,Shipping,Shipsanity: Wooden Display: Nautilus Fossil,SHIPSANITY,Archaeology -8155,Shipping,Shipsanity: Hardwood Display: Nautilus Fossil,SHIPSANITY,Archaeology -8156,Shipping,Shipsanity: Wooden Display: Ornamental Fan,SHIPSANITY,Archaeology -8157,Shipping,Shipsanity: Hardwood Display: Ornamental Fan,SHIPSANITY,Archaeology -8158,Shipping,Shipsanity: Wooden Display: Palm Fossil,SHIPSANITY,Archaeology -8159,Shipping,Shipsanity: Hardwood Display: Palm Fossil,SHIPSANITY,Archaeology -8160,Shipping,Shipsanity: Wooden Display: Prehistoric Handaxe,SHIPSANITY,Archaeology -8161,Shipping,Shipsanity: Hardwood Display: Prehistoric Handaxe,SHIPSANITY,Archaeology -8162,Shipping,Shipsanity: Wooden Display: Prehistoric Rib,SHIPSANITY,Archaeology -8163,Shipping,Shipsanity: Hardwood Display: Prehistoric Rib,SHIPSANITY,Archaeology -8164,Shipping,Shipsanity: Wooden Display: Prehistoric Scapula,SHIPSANITY,Archaeology -8165,Shipping,Shipsanity: Hardwood Display: Prehistoric Scapula,SHIPSANITY,Archaeology -8166,Shipping,Shipsanity: Wooden Display: Prehistoric Skull,SHIPSANITY,Archaeology -8167,Shipping,Shipsanity: Hardwood Display: Prehistoric Skull,SHIPSANITY,Archaeology -8168,Shipping,Shipsanity: Wooden Display: Prehistoric Tibia,SHIPSANITY,Archaeology -8169,Shipping,Shipsanity: Hardwood Display: Prehistoric Tibia,SHIPSANITY,Archaeology -8170,Shipping,Shipsanity: Wooden Display: Prehistoric Tool,SHIPSANITY,Archaeology -8171,Shipping,Shipsanity: Hardwood Display: Prehistoric Tool,SHIPSANITY,Archaeology -8172,Shipping,Shipsanity: Wooden Display: Prehistoric Vertebra,SHIPSANITY,Archaeology -8173,Shipping,Shipsanity: Hardwood Display: Prehistoric Vertebra,SHIPSANITY,Archaeology -8174,Shipping,Shipsanity: Wooden Display: Rare Disc,SHIPSANITY,Archaeology -8175,Shipping,Shipsanity: Hardwood Display: Rare Disc,SHIPSANITY,Archaeology -8176,Shipping,Shipsanity: Wooden Display: Rusty Cog,SHIPSANITY,Archaeology -8177,Shipping,Shipsanity: Hardwood Display: Rusty Cog,SHIPSANITY,Archaeology -8178,Shipping,Shipsanity: Wooden Display: Rusty Spoon,SHIPSANITY,Archaeology -8179,Shipping,Shipsanity: Hardwood Display: Rusty Spoon,SHIPSANITY,Archaeology -8180,Shipping,Shipsanity: Wooden Display: Rusty Spur,SHIPSANITY,Archaeology -8181,Shipping,Shipsanity: Hardwood Display: Rusty Spur,SHIPSANITY,Archaeology -8182,Shipping,Shipsanity: Wooden Display: Skeletal Hand,SHIPSANITY,Archaeology -8183,Shipping,Shipsanity: Hardwood Display: Skeletal Hand,SHIPSANITY,Archaeology -8184,Shipping,Shipsanity: Wooden Display: Skeletal Tail,SHIPSANITY,Archaeology -8185,Shipping,Shipsanity: Hardwood Display: Skeletal Tail,SHIPSANITY,Archaeology -8186,Shipping,Shipsanity: Wooden Display: Snake Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology -8187,Shipping,Shipsanity: Hardwood Display: Snake Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology -8188,Shipping,Shipsanity: Wooden Display: Snake Vertebrae,"SHIPSANITY,GINGER_ISLAND",Archaeology -8189,Shipping,Shipsanity: Hardwood Display: Snake Vertebrae,"SHIPSANITY,GINGER_ISLAND",Archaeology -8190,Shipping,Shipsanity: Wooden Display: Strange Doll (Green),SHIPSANITY,Archaeology -8191,Shipping,Shipsanity: Hardwood Display: Strange Doll (Green),SHIPSANITY,Archaeology -8192,Shipping,Shipsanity: Wooden Display: Strange Doll,SHIPSANITY,Archaeology -8193,Shipping,Shipsanity: Hardwood Display: Strange Doll,SHIPSANITY,Archaeology -8194,Shipping,Shipsanity: Wooden Display: Trilobite Fossil,SHIPSANITY,Archaeology -8195,Shipping,Shipsanity: Hardwood Display: Trilobite Fossil,SHIPSANITY,Archaeology -8196,Shipping,Shipsanity: Bone Path,SHIPSANITY,Archaeology -8197,Shipping,Shipsanity: Glass Fence,SHIPSANITY,Archaeology -8198,Shipping,Shipsanity: Glass Path,SHIPSANITY,Archaeology -8199,Shipping,Shipsanity: Hardwood Display,SHIPSANITY,Archaeology -8200,Shipping,Shipsanity: Wooden Display,SHIPSANITY,Archaeology -8201,Shipping,Shipsanity: Dwarf Gadget: Infinite Volcano Simulation,"SHIPSANITY,GINGER_ISLAND",Archaeology -8202,Shipping,Shipsanity: Water Shifter,"SHIPSANITY,DEPRECATED",Archaeology -8203,Shipping,Shipsanity: Brown Amanita,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul -8204,Shipping,Shipsanity: Swamp Herb,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul -8205,Shipping,Shipsanity: Void Mint Seeds,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8206,Shipping,Shipsanity: Vile Ancient Fruit Seeds,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8207,Shipping,Shipsanity: Void Mint Leaves,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul -8208,Shipping,Shipsanity: Vile Ancient Fruit,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul -8209,Shipping,Shipsanity: Void Minnow,"SHIPSANITY,SHIPSANITY_FISH",Distant Lands - Witch Swamp Overhaul -8210,Shipping,Shipsanity: Swamp Leech,"SHIPSANITY,SHIPSANITY_FISH",Distant Lands - Witch Swamp Overhaul -8211,Shipping,Shipsanity: Purple Algae,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8212,Shipping,Shipsanity: Giant Horsehoe Crab,"SHIPSANITY,SHIPSANITY_FISH",Distant Lands - Witch Swamp Overhaul -8213,Shipping,Shipsanity: Mushroom Kebab,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8214,Shipping,Shipsanity: Crayfish Soup,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8215,Shipping,Shipsanity: Pemmican,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8216,Shipping,Shipsanity: Void Mint Tea,SHIPSANITY,Distant Lands - Witch Swamp Overhaul -8217,Shipping,Shipsanity: Ginger Tincture,"SHIPSANITY,GINGER_ISLAND",Distant Lands - Witch Swamp Overhaul -8218,Shipping,Shipsanity: Neanderthal Limb Bones,SHIPSANITY,Boarding House and Bus Stop Extension -8219,Shipping,Shipsanity: Dinosaur Claw,SHIPSANITY,Boarding House and Bus Stop Extension -8220,Shipping,Shipsanity: Special Pumpkin Soup,SHIPSANITY,Boarding House and Bus Stop Extension -8221,Shipping,Shipsanity: Pterodactyl L Wing Bone,SHIPSANITY,Boarding House and Bus Stop Extension -8222,Shipping,Shipsanity: Dinosaur Skull,SHIPSANITY,Boarding House and Bus Stop Extension -8223,Shipping,Shipsanity: Dinosaur Tooth,SHIPSANITY,Boarding House and Bus Stop Extension -8224,Shipping,Shipsanity: Pterodactyl Egg,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Boarding House and Bus Stop Extension -8225,Shipping,Shipsanity: Pterodactyl Ribs,SHIPSANITY,Boarding House and Bus Stop Extension -8226,Shipping,Shipsanity: Dinosaur Vertebra,SHIPSANITY,Boarding House and Bus Stop Extension -8227,Shipping,Shipsanity: Neanderthal Ribs,SHIPSANITY,Boarding House and Bus Stop Extension -8228,Shipping,Shipsanity: Dinosaur Pelvis,SHIPSANITY,Boarding House and Bus Stop Extension -8229,Shipping,Shipsanity: Dinosaur Ribs,SHIPSANITY,Boarding House and Bus Stop Extension -8230,Shipping,Shipsanity: Pterodactyl Phalange,SHIPSANITY,Boarding House and Bus Stop Extension -8231,Shipping,Shipsanity: Pterodactyl Vertebra,SHIPSANITY,Boarding House and Bus Stop Extension -8232,Shipping,Shipsanity: Neanderthal Pelvis,SHIPSANITY,Boarding House and Bus Stop Extension -8233,Shipping,Shipsanity: Pterodactyl Skull,SHIPSANITY,Boarding House and Bus Stop Extension -8234,Shipping,Shipsanity: Dinosaur Femur,SHIPSANITY,Boarding House and Bus Stop Extension -8235,Shipping,Shipsanity: Pterodactyl Claw,SHIPSANITY,Boarding House and Bus Stop Extension -8236,Shipping,Shipsanity: Neanderthal Skull,SHIPSANITY,Boarding House and Bus Stop Extension -8237,Shipping,Shipsanity: Pterodactyl R Wing Bone,SHIPSANITY,Boarding House and Bus Stop Extension -8238,Shipping,Shipsanity: Scrap Rust,SHIPSANITY,Archaeology -8239,Shipping,Shipsanity: Rusty Path,SHIPSANITY,Archaeology -8241,Shipping,Shipsanity: Digger's Delight,SHIPSANITY,Archaeology -8242,Shipping,Shipsanity: Rocky Root Coffee,SHIPSANITY,Archaeology -8243,Shipping,Shipsanity: Ancient Jello,SHIPSANITY,Archaeology -8244,Shipping,Shipsanity: Bone Fence,SHIPSANITY,Archaeology -8245,Shipping,Shipsanity: Grilled Cheese,SHIPSANITY,Binning Skill -8246,Shipping,Shipsanity: Fish Casserole,SHIPSANITY,Binning Skill -8247,Shipping,Shipsanity: Snatcher Worm,SHIPSANITY,Stardew Valley Expanded +4201,Luau,Secret: Poison The Governor,"SECRETSANITY,EASY_SECRET", +4202,Stardew Valley Fair,Secret: Grange Display Bribe,"SECRETSANITY,EASY_SECRET", +4203,Stardew Valley Fair,Secret: Purple Lettuce,"SECRETSANITY,EASY_SECRET", +4204,Marnie's Ranch,Secret: Make Marnie Laugh,"SECRETSANITY,EASY_SECRET", +4205,Mayor's Manor,Secret: Jumpscare Lewis,"SECRETSANITY,EASY_SECRET", +4206,Marnie's Ranch,Secret: Confront Marnie,"SECRETSANITY,EASY_SECRET", +4207,Fishing,Secret: Lucky Purple Bobber,"SECRETSANITY,EASY_SECRET", +4208,Witch's Hut,Secret: Free The Forsaken Souls,"SECRETSANITY,DIFFICULT_SECRET", +4209,Feast of the Winter Star,Secret: Something For Santa,"SECRETSANITY,EASY_SECRET", +4210,Menu,Secret: Thank the Devs,"SECRETSANITY,EASY_SECRET", +4211,Menu,Secret: Annoy the Moon Man,"SECRETSANITY,DIFFICULT_SECRET", +4212,Menu,Secret: Acknowledge the Lonely Stone,"SECRETSANITY,EASY_SECRET", +4213,Menu,Secret: Jungle Junimo,"SECRETSANITY,EASY_SECRET", +4214,Town,Secret: ??HMTGF??,"SECRETSANITY,EASY_SECRET", +4215,Saloon,Secret: ??Pinky Lemon??,"SECRETSANITY,EASY_SECRET", +4216,Sam's House,Secret: ??Foroguemon??,"SECRETSANITY,EASY_SECRET", +4217,Town,Secret: Galaxies Will Heed Your Cry,"SECRETSANITY,EASY_SECRET", +4219,Town,Secret: Strange Sighting,"SECRETSANITY,DIFFICULT_SECRET", +4220,Farm,Fishing Secret: 'Boat',"SECRETSANITY,BEACH_FARM,FISHING_SECRET", +4221,Town,Fishing Secret: Decorative Trash Can,"SECRETSANITY,FISHING_SECRET", +4222,Island North,Fishing Secret: Foliage Print,"SECRETSANITY,FISHING_SECRET,GINGER_ISLAND", +4223,Gourmand Frog Cave,Fishing Secret: Frog Hat,"SECRETSANITY,FISHING_SECRET,GINGER_ISLAND", +4224,Pirate Cove,Fishing Secret: Gourmand Statue,"SECRETSANITY,FISHING_SECRET,GINGER_ISLAND", +4225,Forest,Fishing Secret: Iridium Krobus,"SECRETSANITY,FISHING_SECRET", +4226,Boat Tunnel,Fishing Secret: Lifesaver,"SECRETSANITY,FISHING_SECRET,GINGER_ISLAND", +4227,Volcano - Floor 10,Fishing Secret: 'Physics 101',"SECRETSANITY,FISHING_SECRET,GINGER_ISLAND", +4228,Desert,Fishing Secret: Pyramid Decal,"SECRETSANITY,FISHING_SECRET", +4229,Volcano Secret Beach,Fishing Secret: Squirrel Figurine,"SECRETSANITY,FISHING_SECRET,GINGER_ISLAND", +4230,Railroad,Fishing Secret: 'Vista',"SECRETSANITY,FISHING_SECRET", +4231,Secret Woods,Fishing Secret: Wall Basket,"SECRETSANITY,FISHING_SECRET", +4232,The Mines - Floor 100,Secret: Summon Bone Serpent,"SECRETSANITY,EASY_SECRET", +4233,Wizard Basement,Secret: Meowmere,"SECRETSANITY,EASY_SECRET", +4234,Elliott's House,Secret: A Familiar Tune,"SECRETSANITY,EASY_SECRET", +4235,Beach,Secret: Sea Monster Sighting,"SECRETSANITY,DIFFICULT_SECRET", +4236,Forest,Secret: ...Bigfoot?,"SECRETSANITY,DIFFICULT_SECRET", +4237,Farmhouse,Secret: Flubber Experiment,"SECRETSANITY,EASY_SECRET", +4238,Wizard Basement,Secret: Seems Fishy,"SECRETSANITY,EASY_SECRET", +4239,Railroad,Secret: 'Me me me me me me me me me me me me me me me me',"SECRETSANITY,DIFFICULT_SECRET", +4240,Menu,Secret: Nice Try,"SECRETSANITY,EASY_SECRET", +4241,Menu,Secret: Enjoy your new life here,"SECRETSANITY,EASY_SECRET", +4242,Menu,Secret: 'What'd you expect?',"SECRETSANITY,EASY_SECRET", +4243,Farm,Secret: Secret Iridium Stackmaster Trophy,"SECRETSANITY,DIFFICULT_SECRET", +4244,Willy's Fish Shop,Secret: What kind of monster is this?,"SECRETSANITY,EASY_SECRET", +4245,Pierre's General Store,Secret: My mouth is watering already,"SECRETSANITY,EASY_SECRET", +4246,Sewer,Secret: A gift of lovely perfume,"SECRETSANITY,EASY_SECRET", +4247,Mines Dwarf Shop,Secret: Where exactly does this juice come from?,"SECRETSANITY,EASY_SECRET", +4261,Secret Notes,Secret Note #1: A Page From Abigail's Diary,"SECRETSANITY,SECRET_NOTE", +4262,Secret Notes,Secret Note #2: Sam's Holiday Shopping List,"SECRETSANITY,SECRET_NOTE", +4263,Secret Notes,Secret Note #3: Leah's Perfect Dinner,"SECRETSANITY,SECRET_NOTE", +4264,Secret Notes,Secret Note #4: Maru's Greatest Invention Yet,"SECRETSANITY,SECRET_NOTE", +4265,Secret Notes,Secret Note #5: Penny gets everyone something they love,"SECRETSANITY,SECRET_NOTE", +4266,Secret Notes,Secret Note #6: Stardrop Saloon Special Orders,"SECRETSANITY,SECRET_NOTE", +4267,Secret Notes,Secret Note #7: Older Bachelors In Town,"SECRETSANITY,SECRET_NOTE", +4268,Secret Notes,Secret Note #8: To Haley And Emily,"SECRETSANITY,SECRET_NOTE", +4269,Secret Notes,Secret Note #9: Alex's Strength Training Diet,"SECRETSANITY,SECRET_NOTE", +4270,Secret Notes,Secret Note #10: Cryptic Note,"SECRETSANITY,SECRET_NOTE,REPLACES_PREVIOUS_LOCATION", +4271,Secret Notes,Secret Note #11: Marnie's Memory,"SECRETSANITY,SECRET_NOTE", +4272,Secret Notes,Secret Note #12: Good Things In Garbage Cans,"SECRETSANITY,SECRET_NOTE", +4273,Secret Notes,Secret Note #13: Junimo Plush,"SECRETSANITY,SECRET_NOTE", +4274,Secret Notes,Secret Note #14: Stone Junimo,"SECRETSANITY,SECRET_NOTE", +4275,Secret Notes,Secret Note #15: Mermaid Show,"SECRETSANITY,SECRET_NOTE", +4276,Secret Notes,Secret Note #16: Treasure Chest,"SECRETSANITY,SECRET_NOTE", +4277,Secret Notes,Secret Note #17: Green Strange Doll,"SECRETSANITY,SECRET_NOTE", +4278,Secret Notes,Secret Note #18: Yellow Strange Doll,"SECRETSANITY,SECRET_NOTE", +4279,Secret Notes,Secret Note #19: Solid Gold Lewis,"SECRETSANITY,SECRET_NOTE", +4280,Secret Notes,Secret Note #19: In Town For All To See,"SECRETSANITY,SECRET_NOTE", +4281,Secret Notes,Secret Note #20: Special Charm,"SECRETSANITY,SECRET_NOTE", +4282,Secret Notes,Secret Note #21: A Date In Nature,"SECRETSANITY,SECRET_NOTE", +4283,Secret Notes,Secret Note #22: The Mysterious Qi,"SECRETSANITY,SECRET_NOTE,REPLACES_PREVIOUS_LOCATION", +4284,Secret Notes,Secret Note #23: Strange Note,"SECRETSANITY,SECRET_NOTE,REPLACES_PREVIOUS_LOCATION", +4285,Secret Notes,Secret Note #24: M. Jasper's Book On Junimos,"SECRETSANITY,SECRET_NOTE", +4286,Secret Notes,Secret Note #25: Ornate Necklace,"SECRETSANITY,SECRET_NOTE", +4287,Secret Notes,Secret Note #26: Ancient Farming Secrets,"SECRETSANITY,SECRET_NOTE", +4288,Secret Notes,Secret Note #27: A Compendium Of My Greatest Discoveries,"SECRETSANITY,SECRET_NOTE", +4289,Farm,Secret: Obtain my precious fruit whenever you like,"SECRETSANITY,EASY_SECRET,GINGER_ISLAND,REQUIRES_QI_ORDERS", +4301,Movie Theater,Watch A Movie,"ANY_MOVIE", +4302,Movie Theater,Watch The Brave Little Sapling,"MOVIE", +4303,Movie Theater,Watch Journey Of The Prairie King: The Motion Picture,"MOVIE", +4304,Movie Theater,Watch Mysterium,"MOVIE", +4305,Movie Theater,Watch The Miracle At Coldstar Ranch,"MOVIE", +4306,Movie Theater,Watch Natural Wonders: Exploring Our Vibrant World,"MOVIE", +4307,Movie Theater,Watch Wumbus,"MOVIE", +4308,Movie Theater,Watch It Howls In The Rain,"MOVIE", +4309,Movie Theater,Watch The Zuzu City Express,"MOVIE", +4311,Movie Theater,Share Apple Slices,"MOVIE_SNACK", +4312,Movie Theater,Share Black Licorice,"MOVIE_SNACK", +4313,Movie Theater,Share Cappuccino Mousse Cake,"MOVIE_SNACK", +4314,Movie Theater,Share Chocolate Popcorn,"MOVIE_SNACK", +4315,Movie Theater,Share Cotton Candy,"MOVIE_SNACK", +4316,Movie Theater,Share Fries,"MOVIE_SNACK", +4317,Movie Theater,Share Hummus Snack Pack,"MOVIE_SNACK", +4318,Movie Theater,Share Ice Cream Sandwich,"MOVIE_SNACK", +4319,Movie Theater,Share Jasmine Tea,"MOVIE_SNACK", +4320,Movie Theater,Share Jawbreaker,"MOVIE_SNACK", +4321,Movie Theater,Share Joja Cola,"MOVIE_SNACK", +4322,Movie Theater,Share JojaCorn,"MOVIE_SNACK", +4323,Movie Theater,Share Kale Smoothie,"MOVIE_SNACK", +4324,Movie Theater,Share Nachos,"MOVIE_SNACK", +4325,Movie Theater,Share Panzanella Salad,"MOVIE_SNACK", +4326,Movie Theater,Share Personal Pizza,"MOVIE_SNACK", +4327,Movie Theater,Share Popcorn,"MOVIE_SNACK", +4328,Movie Theater,Share Rock Candy,"MOVIE_SNACK", +4329,Movie Theater,Share Salmon Burger,"MOVIE_SNACK", +4330,Movie Theater,Share Salted Peanuts,"MOVIE_SNACK", +4331,Movie Theater,Share Sour Slimes,"MOVIE_SNACK", +4332,Movie Theater,Share Star Cookie,"MOVIE_SNACK", +4333,Movie Theater,Share Stardrop Sorbet,"MOVIE_SNACK", +4334,Movie Theater,Share Truffle Popcorn,"MOVIE_SNACK", +4401,Community Center,AAAA Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4402,Community Center,Amon's Fall Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4403,Community Center,Anything For Beyonce Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4404,Community Center,Archipela-Go! Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4405,Community Center,Balls Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4406,Community Center,Big Grapes Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4407,Community Center,Bundle Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4408,Community Center,Bun-dle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4409,Community Center,Permit A38 Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4410,Community Center,Burger King Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4411,Community Center,Burger King's Revenge Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4412,Community Center,Caffeinated Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4413,Community Center,Cap Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4414,Community Center,Capitalist's Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4415,Community Center,Chaos Emerald Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4416,Community Center,Cipher Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4417,Community Center,Clique Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4418,Community Center,Commitment Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4419,Community Center,Communism Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4420,Community Center,Cookie Clicker Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4421,Community Center,Crab Rave Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4422,Community Center,Death Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4423,Community Center,Doctor Angler Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4424,Community Center,Eg Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4425,Community Center,Exhaustion Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4426,Community Center,'Fruit' Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4427,Community Center,Hats Off To You Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4428,Community Center,Honorable Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4429,Community Center,Hurricane Tortilla Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4430,Community Center,Journalist's Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4431,Community Center,Kent C. Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4432,Community Center,Legendairy Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4433,Community Center,Lemonade Stand Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4434,Community Center,Look At These Chickens Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4435,Community Center,Mermaid Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4436,Community Center,Minecraft Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4437,Community Center,Not The Bees Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4438,Community Center,Obelisks Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4439,Community Center,Off Your Back Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4440,Community Center,Potato Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4441,Community Center,Reverse Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4442,Community Center,Rick Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4443,Community Center,Romance Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4444,Community Center,Sappy Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4445,Community Center,Screw You Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4446,Community Center,Sisyphus Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4447,Community Center,SMAPI Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4448,Community Center,Snitch Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4449,Community Center,Speedrunner's Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4450,Community Center,Sunmaid Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4451,Community Center,Tick Tock Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4452,Community Center,Tilesanity Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4453,Community Center,Trap Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4454,Community Center,Trout Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4455,Community Center,Vampire Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4456,Community Center,Vocaloid Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4457,Community Center,What The Rock Is Cooking Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4458,Community Center,The Floor Is Lava Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4459,Community Center,Fast Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4460,Community Center,Restraint Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4461,Community Center,Firstborn Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4462,Community Center,NFT Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4463,Community Center,Connection Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4464,Community Center,Flashbang Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4465,Community Center,This Is Fine Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4466,Community Center,IKEA Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4467,Community Center,Schrodinger's Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4468,Community Center,ANIMAL WELL Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4469,Community Center,Automation Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4470,Community Center,Celeste Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4471,Community Center,Crap Pot Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4472,Community Center,Emmalution Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4473,Community Center,Joetg Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4474,Community Center,Honeywell Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4475,Community Center,Bad Farmer Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4476,Community Center,Bad Fisherman Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4477,Community Center,Gacha Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4478,Community Center,Hibernation Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4479,Community Center,Crowdfunding Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4480,Community Center,Clickbait Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4481,Community Center,DeathLink Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4482,Community Center,Humble Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4483,Community Center,ASMR Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4484,Community Center,Puzzle Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4485,Community Center,Cooperation Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4486,Community Center,Pomnut Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4487,Community Center,Ministry of Madness Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4488,Community Center,Loser Club Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4489,Community Center,Frazzleduck Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4490,Community Center,ArgonMatrix Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4491,Community Center,Pool Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4492,Community Center,Blossom Garden Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4493,Community Center,Scam Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4494,Community Center,Doctor Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4495,Community Center,Very Sticky Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4496,Community Center,Stanley Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4497,Community Center,Hairy Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4498,Community Center,Square Hole Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4499,Community Center,Hint Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4500,Community Center,Colored Crystals Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4501,Community Center,Catch And Release Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4502,Community Center,Pollution Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4503,Community Center,Sacrifice Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4504,Community Center,Dr Seuss Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4505,Community Center,TheAlGoreRhythm Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4506,Community Center,Distracted Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4507,Community Center,Reconnection Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MEME_BUNDLE", +4601,Farm,Wear ???,"HATSANITY,HAT_POST_PERFECTION,GINGER_ISLAND", +4602,Farm,Wear Abigail's Bow,"HATSANITY,HAT_RNG", +4603,Farm,Wear Arcane Hat,"HATSANITY,HAT_NEAR_PERFECTION", +4604,Farm,Wear Archer's Cap,"HATSANITY,HAT_NEAR_PERFECTION,GINGER_ISLAND", +4605,Farm,Wear Beanie,"HATSANITY,HAT_TAILORING", +4606,Farm,Wear Blobfish Mask,"HATSANITY,HAT_TAILORING", +4607,Farm,Wear Blue Bonnet,"HATSANITY,HAT_MEDIUM", +4608,Farm,Wear Blue Bow,"HATSANITY,HAT_EASY", +4609,Farm,Wear Blue Cowboy Hat,"HATSANITY,HAT_RNG", +4610,Farm,Wear Blue Ribbon,"HATSANITY,HAT_MEDIUM", +4611,Farm,Wear Bluebird Mask,"HATSANITY,HAT_MEDIUM,GINGER_ISLAND", +4612,Farm,Wear Bowler Hat,"HATSANITY,HAT_DIFFICULT", +4613,Farm,Wear Bridal Veil,"HATSANITY,HAT_TAILORING", +4614,Farm,Wear Bucket Hat,"HATSANITY,HAT_MEDIUM", +4615,Farm,Wear Butterfly Bow,"HATSANITY,HAT_EASY", +4616,Farm,Wear Cat Ears,"HATSANITY,HAT_MEDIUM", +4617,Farm,Wear Chef Hat,"HATSANITY,HAT_NEAR_PERFECTION,GINGER_ISLAND", +4618,Farm,Wear Chicken Mask,"HATSANITY,HAT_NEAR_PERFECTION", +4619,Farm,Wear Cone Hat,"HATSANITY,HAT_EASY", +4620,Farm,Wear Cool Cap,"HATSANITY,HAT_MEDIUM", +4621,Farm,Wear Copper Pan,"HATSANITY,HAT_EASY", +4622,Farm,Wear Cowboy Hat,"HATSANITY,HAT_NEAR_PERFECTION", +4623,Farm,Wear Cowgal Hat,"HATSANITY,HAT_DIFFICULT", +4624,Farm,Wear Cowpoke Hat,"HATSANITY,HAT_DIFFICULT,GINGER_ISLAND", +4625,Farm,Wear Daisy,"HATSANITY,HAT_EASY", +4626,Farm,Wear Dark Ballcap,"HATSANITY,HAT_RNG", +4627,Farm,Wear Dark Cowboy Hat,"HATSANITY,HAT_RNG", +4628,Farm,Wear Dark Velvet Bow,"HATSANITY,HAT_EASY", +4629,Farm,Wear Delicate Bow,"HATSANITY,HAT_EASY", +4630,Farm,Wear Deluxe Cowboy Hat,"HATSANITY,HAT_MEDIUM,GINGER_ISLAND", +4631,Farm,Wear Deluxe Pirate Hat,"HATSANITY,HAT_RNG,GINGER_ISLAND", +4632,Farm,Wear Dinosaur Hat,"HATSANITY,HAT_TAILORING", +4633,Farm,Wear Earmuffs,"HATSANITY,HAT_MEDIUM", +4634,Farm,Wear Elegant Turban,"HATSANITY,HAT_POST_PERFECTION,GINGER_ISLAND", +4635,Farm,Wear Emily's Magic Hat,"HATSANITY,HAT_MEDIUM", +4636,Farm,Wear Eye Patch,"HATSANITY,HAT_NEAR_PERFECTION,GINGER_ISLAND", +4637,Farm,Wear Fashion Hat,"HATSANITY,HAT_TAILORING", +4638,Farm,Wear Fedora,"HATSANITY,HAT_EASY", +4639,Farm,Wear Fishing Hat,"HATSANITY,HAT_TAILORING", +4640,Farm,Wear Flat Topped Hat,"HATSANITY,HAT_TAILORING", +4641,Farm,Wear Floppy Beanie,"HATSANITY,HAT_TAILORING", +4642,Farm,Wear Forager's Hat,"HATSANITY,HAT_TAILORING,GINGER_ISLAND", +4643,Farm,Wear Frog Hat,"HATSANITY,HAT_MEDIUM,GINGER_ISLAND", +4644,Farm,Wear Garbage Hat,"HATSANITY,HAT_RNG", +4645,Farm,Wear Gil's Hat,"HATSANITY,HAT_DIFFICULT", +4646,Farm,Wear Gnome's Cap,"HATSANITY,HAT_NEAR_PERFECTION,GINGER_ISLAND,REQUIRES_QI_ORDERS", +4647,Farm,Wear Goblin Mask,"HATSANITY,HAT_NEAR_PERFECTION,GINGER_ISLAND", +4648,Farm,Wear Goggles,"HATSANITY,HAT_TAILORING", +4649,Farm,Wear Gold Pan,"HATSANITY,HAT_MEDIUM", +4650,Farm,Wear Golden Helmet,"HATSANITY,HAT_RNG,GINGER_ISLAND", +4651,Farm,Wear Golden Mask,"HATSANITY,HAT_TAILORING", +4652,Farm,Wear Good Ol' Cap,"HATSANITY,HAT_EASY", +4653,Farm,Wear Governor's Hat,"HATSANITY,HAT_MEDIUM", +4654,Farm,Wear Green Turban,"HATSANITY,HAT_MEDIUM", +4655,Farm,Wear Hair Bone,"HATSANITY,HAT_TAILORING", +4656,Farm,Wear Hard Hat,"HATSANITY,HAT_DIFFICULT", +4657,Farm,Wear Hunter's Cap,"HATSANITY,HAT_MEDIUM", +4658,Farm,Wear Infinity Crown,"HATSANITY,HAT_DIFFICULT,GINGER_ISLAND", +4659,Farm,Wear Iridium Pan,"HATSANITY,HAT_MEDIUM", +4660,Farm,Wear Jester Hat,"HATSANITY,HAT_EASY", +4661,Farm,Wear Joja Cap,"HATSANITY,HAT_RNG", +4662,Farm,Wear Junimo Hat,"HATSANITY,HAT_POST_PERFECTION,GINGER_ISLAND", +4663,Farm,Wear Knight's Helmet,"HATSANITY,HAT_NEAR_PERFECTION", +4664,Farm,Wear Laurel Wreath Crown,"HATSANITY,HAT_RNG", +4665,Farm,Wear Leprechaun Hat,"HATSANITY,HAT_EASY", +4666,Farm,Wear Living Hat,"HATSANITY,HAT_RNG", +4667,Farm,Wear Logo Cap,"HATSANITY,HAT_TAILORING", +4668,Farm,Wear Lucky Bow,"HATSANITY,HAT_MEDIUM", +4669,Farm,Wear Magic Cowboy Hat,"HATSANITY,HAT_DIFFICULT", +4670,Farm,Wear Magic Turban,"HATSANITY,HAT_DIFFICULT", +4671,Farm,Wear Mouse Ears,"HATSANITY,HAT_EASY", +4672,Farm,Wear Mr. Qi's Hat,"HATSANITY,HAT_MEDIUM,GINGER_ISLAND", +4673,Farm,Wear Mummy Mask,"HATSANITY,HAT_EASY", +4674,Farm,Wear Mushroom Cap,"HATSANITY,HAT_RNG", +4675,Farm,Wear Mystery Hat,"HATSANITY,HAT_RNG", +4676,Farm,Wear Official Cap,"HATSANITY,HAT_MEDIUM", +4677,Farm,Wear Pageboy Cap,"HATSANITY,HAT_NEAR_PERFECTION,GINGER_ISLAND", +4678,Farm,Wear Panda Hat,"HATSANITY,HAT_IMPOSSIBLE,DEPRECATED", +4679,Farm,Wear Paper Hat,"HATSANITY,HAT_EASY,GINGER_ISLAND", +4680,Farm,Wear Party Hat (Blue),"HATSANITY,HAT_TAILORING", +4681,Farm,Wear Party Hat (Green),"HATSANITY,HAT_TAILORING", +4682,Farm,Wear Party Hat (Red),"HATSANITY,HAT_TAILORING", +4683,Farm,Wear Pink Bow,"HATSANITY,HAT_EASY,GINGER_ISLAND", +4684,Farm,Wear Pirate Hat,"HATSANITY,HAT_TAILORING", +4685,Farm,Wear Plum Chapeau,"HATSANITY,HAT_DIFFICULT", +4686,Farm,Wear Polka Bow,"HATSANITY,HAT_MEDIUM", +4687,Farm,Wear Propeller Hat,"HATSANITY,HAT_TAILORING", +4688,Farm,Wear Pumpkin Mask,"HATSANITY,HAT_TAILORING", +4689,Farm,Wear Qi Mask,"HATSANITY,HAT_TAILORING,GINGER_ISLAND", +4690,Farm,Wear Raccoon Hat,"HATSANITY,HAT_MEDIUM", +4691,Farm,Wear Radioactive Goggles,"HATSANITY,HAT_TAILORING,GINGER_ISLAND,REQUIRES_QI_ORDERS", +4692,Farm,Wear Red Cowboy Hat,"HATSANITY,HAT_RNG", +4693,Farm,Wear Red Fez,"HATSANITY,HAT_MEDIUM", +4694,Farm,Wear Sailor's Cap,"HATSANITY,HAT_EASY", +4695,Farm,Wear Santa Hat,"HATSANITY,HAT_MEDIUM", +4696,Farm,Wear Skeleton Mask,"HATSANITY,HAT_MEDIUM", +4697,Farm,Wear Small Cap,"HATSANITY,HAT_MEDIUM,GINGER_ISLAND", +4698,Farm,Wear Sombrero,"HATSANITY,HAT_NEAR_PERFECTION", +4699,Farm,Wear Sou'wester,"HATSANITY,HAT_EASY", +4700,Farm,Wear Space Helmet,"HATSANITY,HAT_DIFFICULT,GINGER_ISLAND", +4701,Farm,Wear Sports Cap,"HATSANITY,HAT_MEDIUM", +4702,Farm,Wear Spotted Headscarf,"HATSANITY,HAT_TAILORING", +4703,Farm,Wear Squid Hat,"HATSANITY,HAT_MEDIUM", +4704,Farm,Wear Squire's Helmet,"HATSANITY,HAT_RNG", +4705,Farm,Wear Star Helmet,"HATSANITY,HAT_TAILORING,GINGER_ISLAND", +4706,Farm,Wear Steel Pan,"HATSANITY,HAT_MEDIUM", +4707,Farm,Wear Straw Hat,"HATSANITY,HAT_MEDIUM", +4708,Farm,Wear Sunglasses,"HATSANITY,HAT_TAILORING,GINGER_ISLAND", +4709,Farm,Wear Swashbuckler Hat,"HATSANITY,HAT_TAILORING,GINGER_ISLAND", +4710,Farm,Wear Tiara,"HATSANITY,HAT_EASY", +4711,Farm,Wear Tiger Hat,"HATSANITY,HAT_RNG,GINGER_ISLAND", +4712,Farm,Wear Top Hat,"HATSANITY,HAT_EASY", +4713,Farm,Wear Totem Mask,"HATSANITY,HAT_TAILORING", +4714,Farm,Wear Tricorn Hat,"HATSANITY,HAT_RNG", +4715,Farm,Wear Tropiclip,"HATSANITY,HAT_EASY", +4716,Farm,Wear Trucker Hat,"HATSANITY,HAT_MEDIUM", +4717,Farm,Wear Warrior Helmet,"HATSANITY,HAT_TAILORING,GINGER_ISLAND", +4718,Farm,Wear Watermelon Band,"HATSANITY,HAT_DIFFICULT", +4719,Farm,Wear Wearable Dwarf Helm,"HATSANITY,HAT_TAILORING", +4720,Farm,Wear White Bow,"HATSANITY,HAT_DIFFICULT", +4721,Farm,Wear White Turban,"HATSANITY,HAT_TAILORING", +4722,Farm,Wear Witch Hat,"HATSANITY,HAT_TAILORING", +4751,Farm,Eat Wild Horseradish,"EATSANITY,EATSANITY_CROP", +4752,Farm,Eat Daffodil,"EATSANITY,EATSANITY_CROP", +4753,Farm,Eat Leek,"EATSANITY,EATSANITY_CROP", +4754,Farm,Eat Dandelion,"EATSANITY,EATSANITY_CROP", +4755,Farm,Eat Parsnip,"EATSANITY,EATSANITY_CROP", +4756,Farm,Eat Cave Carrot,"EATSANITY,EATSANITY_CROP", +4757,Farm,Eat Cactus Fruit,"EATSANITY,EATSANITY_CROP", +4758,Farm,Eat Green Bean,"EATSANITY,EATSANITY_CROP", +4759,Farm,Eat Cauliflower,"EATSANITY,EATSANITY_CROP", +4760,Farm,Eat Potato,"EATSANITY,EATSANITY_CROP", +4761,Farm,Eat Garlic,"EATSANITY,EATSANITY_CROP", +4762,Farm,Eat Kale,"EATSANITY,EATSANITY_CROP", +4763,Farm,Eat Melon,"EATSANITY,EATSANITY_CROP", +4764,Farm,Eat Tomato,"EATSANITY,EATSANITY_CROP", +4765,Farm,Eat Morel,"EATSANITY,EATSANITY_CROP", +4766,Farm,Eat Blueberry,"EATSANITY,EATSANITY_CROP", +4767,Farm,Eat Fiddlehead Fern,"EATSANITY,EATSANITY_CROP", +4768,Farm,Eat Hot Pepper,"EATSANITY,EATSANITY_CROP", +4769,Farm,Eat Radish,"EATSANITY,EATSANITY_CROP", +4770,Farm,Eat Red Cabbage,"EATSANITY,EATSANITY_CROP", +4771,Farm,Eat Starfruit,"EATSANITY,EATSANITY_CROP", +4772,Farm,Eat Corn,"EATSANITY,EATSANITY_CROP", +4773,Farm,Eat Unmilled Rice,"EATSANITY,EATSANITY_CROP", +4774,Farm,Eat Eggplant,"EATSANITY,EATSANITY_CROP", +4775,Farm,Eat Artichoke,"EATSANITY,EATSANITY_CROP", +4776,Farm,Eat Bok Choy,"EATSANITY,EATSANITY_CROP", +4777,Farm,Eat Yam,"EATSANITY,EATSANITY_CROP", +4778,Farm,Eat Chanterelle,"EATSANITY,EATSANITY_CROP", +4779,Farm,Eat Cranberries,"EATSANITY,EATSANITY_CROP", +4780,Farm,Eat Beet,"EATSANITY,EATSANITY_CROP", +4781,Farm,Eat Salmonberry,"EATSANITY,EATSANITY_CROP", +4782,Farm,Eat Amaranth,"EATSANITY,EATSANITY_CROP", +4783,Farm,Eat Hops,"EATSANITY,EATSANITY_CROP", +4784,Farm,Eat Spice Berry,"EATSANITY,EATSANITY_CROP", +4785,Farm,Eat Grape,"EATSANITY,EATSANITY_CROP", +4786,Farm,Eat Spring Onion,"EATSANITY,EATSANITY_CROP", +4787,Farm,Eat Strawberry,"EATSANITY,EATSANITY_CROP", +4788,Farm,Eat Sweet Pea,"EATSANITY,EATSANITY_CROP", +4789,Farm,Eat Common Mushroom,"EATSANITY,EATSANITY_CROP", +4790,Farm,Eat Wild Plum,"EATSANITY,EATSANITY_CROP", +4791,Farm,Eat Hazelnut,"EATSANITY,EATSANITY_CROP", +4792,Farm,Eat Blackberry,"EATSANITY,EATSANITY_CROP", +4793,Farm,Eat Winter Root,"EATSANITY,EATSANITY_CROP", +4794,Farm,Eat Crystal Fruit,"EATSANITY,EATSANITY_CROP", +4795,Farm,Eat Snow Yam,"EATSANITY,EATSANITY_CROP", +4796,Farm,Eat Crocus,"EATSANITY,EATSANITY_CROP", +4797,Farm,Eat Sunflower,"EATSANITY,EATSANITY_CROP", +4798,Farm,Eat Purple Mushroom,"EATSANITY,EATSANITY_CROP", +4799,Farm,Eat Tulip,"EATSANITY,EATSANITY_CROP", +4800,Farm,Eat Summer Spangle,"EATSANITY,EATSANITY_CROP", +4801,Farm,Eat Fairy Rose,"EATSANITY,EATSANITY_CROP", +4802,Farm,Eat Blue Jazz,"EATSANITY,EATSANITY_CROP", +4803,Farm,Eat Poppy,"EATSANITY,EATSANITY_CROP", +4804,Farm,Eat Apple,"EATSANITY,EATSANITY_CROP", +4805,Farm,Eat Apricot,"EATSANITY,EATSANITY_CROP", +4806,Farm,Eat Orange,"EATSANITY,EATSANITY_CROP", +4807,Farm,Eat Peach,"EATSANITY,EATSANITY_CROP", +4808,Farm,Eat Pomegranate,"EATSANITY,EATSANITY_CROP", +4809,Farm,Eat Cherry,"EATSANITY,EATSANITY_CROP", +4810,Farm,Eat Banana,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND", +4811,Farm,Eat Ginger,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND", +4812,Farm,Eat Taro Root,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND", +4813,Farm,Eat Pineapple,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND", +4814,Farm,Eat Mango,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND", +4815,Farm,Eat Magma Cap,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND", +4816,Farm,Eat Qi Fruit,"EATSANITY,EATSANITY_CROP,GINGER_ISLAND,REQUIRES_QI_ORDERS", +4817,Farm,Eat Carrot,"EATSANITY,EATSANITY_CROP", +4818,Farm,Eat Summer Squash,"EATSANITY,EATSANITY_CROP", +4819,Farm,Eat Broccoli,"EATSANITY,EATSANITY_CROP", +4820,Farm,Eat Powdermelon,"EATSANITY,EATSANITY_CROP", +4831,Farm,Eat Fried Egg,"EATSANITY,EATSANITY_COOKING", +4832,Farm,Eat Omelet,"EATSANITY,EATSANITY_COOKING", +4833,Farm,Eat Salad,"EATSANITY,EATSANITY_COOKING", +4834,Farm,Eat Cheese Cauliflower,"EATSANITY,EATSANITY_COOKING", +4835,Farm,Eat Baked Fish,"EATSANITY,EATSANITY_COOKING", +4836,Farm,Eat Parsnip Soup,"EATSANITY,EATSANITY_COOKING", +4837,Farm,Eat Vegetable Medley,"EATSANITY,EATSANITY_COOKING", +4838,Farm,Eat Complete Breakfast,"EATSANITY,EATSANITY_COOKING", +4839,Farm,Eat Fried Calamari,"EATSANITY,EATSANITY_COOKING", +4840,Farm,Eat Strange Bun,"EATSANITY,EATSANITY_COOKING", +4841,Farm,Eat Lucky Lunch,"EATSANITY,EATSANITY_COOKING", +4842,Farm,Eat Fried Mushroom,"EATSANITY,EATSANITY_COOKING", +4843,Farm,Eat Pizza,"EATSANITY,EATSANITY_COOKING", +4844,Farm,Eat Bean Hotpot,"EATSANITY,EATSANITY_COOKING", +4845,Farm,Eat Glazed Yams,"EATSANITY,EATSANITY_COOKING", +4846,Farm,Eat Carp Surprise,"EATSANITY,EATSANITY_COOKING", +4847,Farm,Eat Hashbrowns,"EATSANITY,EATSANITY_COOKING", +4848,Farm,Eat Pancakes,"EATSANITY,EATSANITY_COOKING", +4849,Farm,Eat Salmon Dinner,"EATSANITY,EATSANITY_COOKING", +4850,Farm,Eat Fish Taco,"EATSANITY,EATSANITY_COOKING", +4851,Farm,Eat Crispy Bass,"EATSANITY,EATSANITY_COOKING", +4852,Farm,Eat Pepper Poppers,"EATSANITY,EATSANITY_COOKING", +4853,Farm,Eat Bread,"EATSANITY,EATSANITY_COOKING", +4854,Farm,Eat Tom Kha Soup,"EATSANITY,EATSANITY_COOKING", +4855,Farm,Eat Trout Soup,"EATSANITY,EATSANITY_COOKING", +4856,Farm,Eat Chocolate Cake,"EATSANITY,EATSANITY_COOKING", +4857,Farm,Eat Pink Cake,"EATSANITY,EATSANITY_COOKING", +4858,Farm,Eat Rhubarb Pie,"EATSANITY,EATSANITY_COOKING", +4859,Farm,Eat Cookies,"EATSANITY,EATSANITY_COOKING", +4860,Farm,Eat Spaghetti,"EATSANITY,EATSANITY_COOKING", +4861,Farm,Eat Fried Eel,"EATSANITY,EATSANITY_COOKING", +4862,Farm,Eat Spicy Eel,"EATSANITY,EATSANITY_COOKING", +4863,Farm,Eat Sashimi,"EATSANITY,EATSANITY_COOKING", +4864,Farm,Eat Maki Roll,"EATSANITY,EATSANITY_COOKING", +4865,Farm,Eat Tortilla,"EATSANITY,EATSANITY_COOKING", +4866,Farm,Eat Red Plate,"EATSANITY,EATSANITY_COOKING", +4867,Farm,Eat Eggplant Parmesan,"EATSANITY,EATSANITY_COOKING", +4868,Farm,Eat Rice Pudding,"EATSANITY,EATSANITY_COOKING", +4869,Farm,Eat Ice Cream,"EATSANITY,EATSANITY_COOKING", +4870,Farm,Eat Blueberry Tart,"EATSANITY,EATSANITY_COOKING", +4871,Farm,Eat Autumn's Bounty,"EATSANITY,EATSANITY_COOKING", +4872,Farm,Eat Pumpkin Soup,"EATSANITY,EATSANITY_COOKING", +4873,Farm,Eat Super Meal,"EATSANITY,EATSANITY_COOKING", +4874,Farm,Eat Cranberry Sauce,"EATSANITY,EATSANITY_COOKING", +4875,Farm,Eat Stuffing,"EATSANITY,EATSANITY_COOKING", +4876,Farm,Eat Farmer's Lunch,"EATSANITY,EATSANITY_COOKING", +4877,Farm,Eat Survival Burger,"EATSANITY,EATSANITY_COOKING", +4878,Farm,Eat Dish O' The Sea,"EATSANITY,EATSANITY_COOKING", +4879,Farm,Eat Miner's Treat,"EATSANITY,EATSANITY_COOKING", +4880,Farm,Eat Roots Platter,"EATSANITY,EATSANITY_COOKING", +4881,Farm,Drink Triple Shot Espresso,"EATSANITY,EATSANITY_COOKING", +4882,Farm,Eat Field Snack,"EATSANITY,EATSANITY_COOKING", +4883,Farm,Eat Algae Soup,"EATSANITY,EATSANITY_COOKING", +4884,Farm,Eat Pale Broth,"EATSANITY,EATSANITY_COOKING", +4885,Farm,Eat Plum Pudding,"EATSANITY,EATSANITY_COOKING", +4886,Farm,Eat Artichoke Dip,"EATSANITY,EATSANITY_COOKING", +4887,Farm,Eat Stir Fry,"EATSANITY,EATSANITY_COOKING", +4888,Farm,Eat Roasted Hazelnuts,"EATSANITY,EATSANITY_COOKING", +4889,Farm,Eat Pumpkin Pie,"EATSANITY,EATSANITY_COOKING", +4890,Farm,Eat Radish Salad,"EATSANITY,EATSANITY_COOKING", +4891,Farm,Eat Fruit Salad,"EATSANITY,EATSANITY_COOKING", +4892,Farm,Eat Blackberry Cobbler,"EATSANITY,EATSANITY_COOKING", +4893,Farm,Drink Cranberry Candy,"EATSANITY,EATSANITY_COOKING", +4894,Farm,Eat Bruschetta,"EATSANITY,EATSANITY_COOKING", +4895,Farm,Eat Coleslaw,"EATSANITY,EATSANITY_COOKING", +4896,Farm,Eat Fiddlehead Risotto,"EATSANITY,EATSANITY_COOKING", +4897,Farm,Eat Poppyseed Muffin,"EATSANITY,EATSANITY_COOKING", +4898,Farm,Eat Chowder,"EATSANITY,EATSANITY_COOKING", +4899,Farm,Eat Lobster Bisque,"EATSANITY,EATSANITY_COOKING", +4900,Farm,Eat Fish Stew,"EATSANITY,EATSANITY_COOKING", +4901,Farm,Eat Escargot,"EATSANITY,EATSANITY_COOKING", +4902,Farm,Eat Maple Bar,"EATSANITY,EATSANITY_COOKING", +4903,Farm,Eat Crab Cakes,"EATSANITY,EATSANITY_COOKING", +4904,Farm,Eat Shrimp Cocktail,"EATSANITY,EATSANITY_COOKING", +4905,Farm,Drink Oil of Garlic,"EATSANITY,EATSANITY_COOKING", +4906,Farm,Drink Life Elixir,"EATSANITY,EATSANITY_COOKING", +4907,Farm,Eat Seafoam Pudding,"EATSANITY,EATSANITY_COOKING", +4908,Farm,Eat Bug Steak,"EATSANITY,EATSANITY_COOKING", +4909,Farm,Drink Ginger Ale,"EATSANITY,EATSANITY_COOKING,GINGER_ISLAND", +4910,Farm,Eat Banana Pudding,"EATSANITY,EATSANITY_COOKING,GINGER_ISLAND", +4911,Farm,Eat Mango Sticky Rice,"EATSANITY,EATSANITY_COOKING,GINGER_ISLAND", +4912,Farm,Eat Poi,"EATSANITY,EATSANITY_COOKING,GINGER_ISLAND", +4913,Farm,Eat Tropical Curry,"EATSANITY,EATSANITY_COOKING,GINGER_ISLAND", +4914,Farm,Eat Squid Ink Ravioli,"EATSANITY,EATSANITY_COOKING", +4915,Farm,Drink Moss Soup,"EATSANITY,EATSANITY_COOKING", +4921,Farm,Eat Egg,"EATSANITY,EATSANITY_ARTISAN", +4922,Farm,Eat Large Egg,"EATSANITY,EATSANITY_ARTISAN", +4923,Farm,Eat Egg (Brown),"EATSANITY,EATSANITY_ARTISAN", +4924,Farm,Eat Large Egg (Brown),"EATSANITY,EATSANITY_ARTISAN", +4925,Farm,Drink Milk,"EATSANITY,EATSANITY_ARTISAN", +4926,Farm,Drink Large Milk,"EATSANITY,EATSANITY_ARTISAN", +4927,Farm,Drink Pale Ale,"EATSANITY,EATSANITY_ARTISAN", +4928,Farm,Drink Mayonnaise,"EATSANITY,EATSANITY_ARTISAN", +4929,Farm,Drink Duck Mayonnaise,"EATSANITY,EATSANITY_ARTISAN", +4930,Farm,Drink Beer,"EATSANITY,EATSANITY_ARTISAN", +4931,Farm,Drink Wine,"EATSANITY,EATSANITY_ARTISAN", +4932,Farm,Drink Juice,"EATSANITY,EATSANITY_ARTISAN", +4933,Farm,Drink Coffee,"EATSANITY,EATSANITY_ARTISAN", +4934,Farm,Eat Cheese,"EATSANITY,EATSANITY_ARTISAN", +4935,Farm,Eat Goat Cheese,"EATSANITY,EATSANITY_ARTISAN", +4936,Farm,Eat Truffle,"EATSANITY,EATSANITY_ARTISAN", +4937,Farm,Drink Truffle Oil,"EATSANITY,EATSANITY_ARTISAN", +4938,Farm,Drink Goat Milk,"EATSANITY,EATSANITY_ARTISAN", +4939,Farm,Drink Large Goat Milk,"EATSANITY,EATSANITY_ARTISAN", +4940,Farm,Eat Duck Egg,"EATSANITY,EATSANITY_ARTISAN", +4941,Farm,Eat Aged Roe,"EATSANITY,EATSANITY_ARTISAN", +4942,Farm,Drink Mead,"EATSANITY,EATSANITY_ARTISAN", +4943,Farm,Drink Green Tea,"EATSANITY,EATSANITY_ARTISAN", +4944,Farm,Drink Maple Syrup,"EATSANITY,EATSANITY_ARTISAN", +4945,Farm,Drink Dinosaur Mayonnaise,"EATSANITY,EATSANITY_ARTISAN", +4946,Farm,Eat Roe,"EATSANITY,EATSANITY_ARTISAN", +4947,Farm,Eat Caviar,"EATSANITY,EATSANITY_ARTISAN", +4948,Farm,Eat Ostrich Egg,"EATSANITY,EATSANITY_ARTISAN,GINGER_ISLAND", +4949,Farm,Eat Golden Egg,"EATSANITY,EATSANITY_ARTISAN", +4950,Farm,Drink Mystic Syrup,"EATSANITY,EATSANITY_ARTISAN,REQUIRES_MASTERIES", +4951,Farm,Eat Raisins,"EATSANITY,EATSANITY_ARTISAN", +4952,Farm,Drink Jelly,"EATSANITY,EATSANITY_ARTISAN", +4953,Farm,Drink Pickles,"EATSANITY,EATSANITY_ARTISAN", +4954,Farm,Eat Rice,"EATSANITY,EATSANITY_ARTISAN", +4955,Farm,Eat Sugar,"EATSANITY,EATSANITY_ARTISAN", +4956,Farm,Eat Wheat Flour,"EATSANITY,EATSANITY_ARTISAN", +4957,Farm,Drink Oil,"EATSANITY,EATSANITY_ARTISAN", +4958,Farm,Drink Vinegar,"EATSANITY,EATSANITY_ARTISAN", +4959,Farm,Eat Smoked Fish,"EATSANITY,EATSANITY_ARTISAN", +4960,Farm,Eat Dried Fruit,"EATSANITY,EATSANITY_ARTISAN", +4961,Farm,Eat Dried Mushrooms,"EATSANITY,EATSANITY_ARTISAN", +4971,Farm,Eat Anchovy,"EATSANITY,EATSANITY_FISH", +4972,Farm,Eat Tuna,"EATSANITY,EATSANITY_FISH", +4973,Farm,Eat Sardine,"EATSANITY,EATSANITY_FISH", +4974,Farm,Eat Bream,"EATSANITY,EATSANITY_FISH", +4975,Farm,Eat Largemouth Bass,"EATSANITY,EATSANITY_FISH", +4976,Farm,Eat Smallmouth Bass,"EATSANITY,EATSANITY_FISH", +4977,Farm,Eat Rainbow Trout,"EATSANITY,EATSANITY_FISH", +4978,Farm,Eat Salmon,"EATSANITY,EATSANITY_FISH", +4979,Farm,Eat Walleye,"EATSANITY,EATSANITY_FISH", +4980,Farm,Eat Perch,"EATSANITY,EATSANITY_FISH", +4981,Farm,Eat Carp,"EATSANITY,EATSANITY_FISH", +4982,Farm,Eat Catfish,"EATSANITY,EATSANITY_FISH", +4983,Farm,Eat Pike,"EATSANITY,EATSANITY_FISH", +4984,Farm,Eat Sunfish,"EATSANITY,EATSANITY_FISH", +4985,Farm,Eat Red Mullet,"EATSANITY,EATSANITY_FISH", +4986,Farm,Eat Herring,"EATSANITY,EATSANITY_FISH", +4987,Farm,Eat Eel,"EATSANITY,EATSANITY_FISH", +4988,Farm,Eat Red Snapper,"EATSANITY,EATSANITY_FISH", +4989,Farm,Eat Squid,"EATSANITY,EATSANITY_FISH", +4990,Farm,Eat Seaweed,"EATSANITY,EATSANITY_FISH", +4991,Farm,Eat Green Algae,"EATSANITY,EATSANITY_FISH", +4992,Farm,Eat Super Cucumber,"EATSANITY,EATSANITY_FISH", +4993,Farm,Eat Ghostfish,"EATSANITY,EATSANITY_FISH", +4994,Farm,Eat White Algae,"EATSANITY,EATSANITY_FISH", +4995,Farm,Eat Crimsonfish,"EATSANITY,EATSANITY_FISH", +4996,Farm,Eat Angler,"EATSANITY,EATSANITY_FISH", +4997,Farm,Eat Ice Pip,"EATSANITY,EATSANITY_FISH", +4998,Farm,Eat Lava Eel,"EATSANITY,EATSANITY_FISH", +4999,Farm,Eat Legend,"EATSANITY,EATSANITY_FISH", +5000,Farm,Eat Sandfish,"EATSANITY,EATSANITY_FISH", +5001,Farm,Eat Mutant Carp,"EATSANITY,EATSANITY_FISH", +5002,Farm,Eat Sturgeon,"EATSANITY,EATSANITY_FISH", +5003,Farm,Eat Tiger Trout,"EATSANITY,EATSANITY_FISH", +5004,Farm,Eat Bullhead,"EATSANITY,EATSANITY_FISH", +5005,Farm,Eat Tilapia,"EATSANITY,EATSANITY_FISH", +5006,Farm,Eat Chub,"EATSANITY,EATSANITY_FISH", +5007,Farm,Eat Dorado,"EATSANITY,EATSANITY_FISH", +5008,Farm,Eat Albacore,"EATSANITY,EATSANITY_FISH", +5009,Farm,Eat Shad,"EATSANITY,EATSANITY_FISH", +5010,Farm,Eat Lingcod,"EATSANITY,EATSANITY_FISH", +5011,Farm,Eat Halibut,"EATSANITY,EATSANITY_FISH", +5012,Farm,Eat Woodskip,"EATSANITY,EATSANITY_FISH", +5013,Farm,Eat Glacierfish,"EATSANITY,EATSANITY_FISH", +5014,Farm,Eat Void Salmon,"EATSANITY,EATSANITY_FISH", +5015,Farm,Eat Slimejack,"EATSANITY,EATSANITY_FISH", +5016,Farm,Eat Midnight Squid,"EATSANITY,EATSANITY_FISH", +5017,Farm,Eat Spook Fish,"EATSANITY,EATSANITY_FISH", +5018,Farm,Eat Blobfish,"EATSANITY,EATSANITY_FISH", +5019,Farm,Eat Flounder,"EATSANITY,EATSANITY_FISH", +5020,Farm,Eat Midnight Carp,"EATSANITY,EATSANITY_FISH", +5021,Farm,Eat Stingray,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND", +5022,Farm,Eat Lionfish,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND", +5023,Farm,Eat Blue Discus,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND", +5024,Farm,Eat Son of Crimsonfish,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5025,Farm,Eat Ms. Angler,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5026,Farm,Eat Legend II,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5027,Farm,Eat Radioactive Carp,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5028,Farm,Eat Glacierfish Jr.,"EATSANITY,EATSANITY_FISH,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5029,Farm,Eat Sea Jelly,"EATSANITY,EATSANITY_FISH", +5030,Farm,Eat Cave Jelly,"EATSANITY,EATSANITY_FISH", +5031,Farm,Eat River Jelly,"EATSANITY,EATSANITY_FISH", +5032,Farm,Drink Joja Cola,"EATSANITY,EATSANITY_FISH", +5041,Farm,Eat Magic Rock Candy,"EATSANITY,EATSANITY_SHOP", +5042,Farm,Drink Energy Tonic,"EATSANITY,EATSANITY_SHOP", +5043,Farm,Drink Muscle Remedy,"EATSANITY,EATSANITY_SHOP", +5044,Farm,Drink Pina Colada,"EATSANITY,EATSANITY_SHOP,GINGER_ISLAND", +5045,Farm,Eat Stardrop,"EATSANITY,EATSANITY_SHOP", +5051,Farm,Eat Sap,"EATSANITY,EATSANITY_CROP,EATSANITY_POISONOUS", +5052,Farm,Eat Holly,"EATSANITY,EATSANITY_CROP,EATSANITY_POISONOUS", +5053,Farm,Eat Red Mushroom,"EATSANITY,EATSANITY_CROP,EATSANITY_POISONOUS", +5054,Farm,Eat Void Egg,"EATSANITY,EATSANITY_ARTISAN,EATSANITY_POISONOUS", +5055,Farm,Drink Void Mayonnaise,"EATSANITY,EATSANITY_ARTISAN,EATSANITY_POISONOUS", +5056,Farm,Eat Pufferfish,"EATSANITY,EATSANITY_FISH,EATSANITY_POISONOUS", +5057,Farm,Eat Sea Cucumber,"EATSANITY,EATSANITY_FISH,EATSANITY_POISONOUS", +5058,Farm,Eat Goby,"EATSANITY,EATSANITY_FISH,EATSANITY_POISONOUS", +5059,Farm,Eat Scorpion Carp,"EATSANITY,EATSANITY_FISH,EATSANITY_POISONOUS", +5060,Farm,Drink Iridium Snake Milk,"EATSANITY,EATSANITY_ARTISAN,EATSANITY_POISONOUS", +5101,Wizard Blueprints,Earth Obelisk Blueprint,"ENDGAME_LOCATIONS", +5102,Wizard Blueprints,Water Obelisk Blueprint,"ENDGAME_LOCATIONS", +5103,Wizard Blueprints,Desert Obelisk Blueprint,"ENDGAME_LOCATIONS", +5104,Wizard Blueprints,Island Obelisk Blueprint,"ENDGAME_LOCATIONS,GINGER_ISLAND", +5105,Wizard Blueprints,Junimo Hut Blueprint,"ENDGAME_LOCATIONS", +5106,Wizard Blueprints,Gold Clock Blueprint,"ENDGAME_LOCATIONS", +5107,Sewer,Purchase Return Scepter,"ENDGAME_LOCATIONS", +5108,Carpenter Shop,Pam House Blueprint,"ENDGAME_LOCATIONS", +5109,Carpenter Shop,Forest To Beach Shortcut Blueprint,"ENDGAME_LOCATIONS", +5110,Carpenter Shop,Mountain Shortcuts Blueprint,"ENDGAME_LOCATIONS", +5112,Carpenter Shop,Town To Tide Pools Shortcut Blueprint,"ENDGAME_LOCATIONS", +5113,Carpenter Shop,Tunnel To Backwoods Shortcut Blueprint,"ENDGAME_LOCATIONS", +5115,Qi's Walnut Room,Purchase Horse Flute,"ENDGAME_LOCATIONS,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5116,Qi's Walnut Room,Purchase Pierre's Missing Stocklist,"ENDGAME_LOCATIONS,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5117,Qi's Walnut Room,Purchase Key To The Town,"ENDGAME_LOCATIONS,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5118,Qi's Walnut Room,Purchase Mini-Shipping Bin,"ENDGAME_LOCATIONS,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5119,Qi's Walnut Room,Purchase Exotic Double Bed,"ENDGAME_LOCATIONS,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5120,Qi's Walnut Room,Purchase Golden Egg,"ENDGAME_LOCATIONS,GINGER_ISLAND,REQUIRES_QI_ORDERS", +5121,Casino,Purchase Statue Of Endless Fortune,"ENDGAME_LOCATIONS", +5122,Pierre's General Store,Purchase Catalogue,"ENDGAME_LOCATIONS", +5123,Carpenter Shop,Purchase Furniture Catalogue,"ENDGAME_LOCATIONS", +5124,JojaMart,Purchase Joja Furniture Catalogue,"ENDGAME_LOCATIONS", +5125,Traveling Cart,Purchase Junimo Catalogue,"ENDGAME_LOCATIONS", +5126,Traveling Cart,Purchase Retro Catalogue,"ENDGAME_LOCATIONS", +5127,Garbage Cans,Find Trash Catalogue,"ENDGAME_LOCATIONS", +5128,Sewer,Purchase Wizard Catalogue,"ENDGAME_LOCATIONS", +5129,Traveling Cart,Purchase Spouse Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY", +5130,Traveling Cart,Purchase Abigail Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5131,Traveling Cart,Purchase Alex Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5132,Traveling Cart,Purchase Elliott Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5133,Traveling Cart,Purchase Emily Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5134,Traveling Cart,Purchase Haley Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5135,Traveling Cart,Purchase Harvey Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5136,Traveling Cart,Purchase Krobus Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5137,Traveling Cart,Purchase Leah Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5138,Traveling Cart,Purchase Maru Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5139,Traveling Cart,Purchase Penny Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5140,Traveling Cart,Purchase Sam Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5141,Traveling Cart,Purchase Sebastian Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5142,Traveling Cart,Purchase Shane Portrait,"ENDGAME_LOCATIONS,REQUIRES_FRIENDSANITY_MARRIAGE", +5143,Traveling Cart,Purchase Tea Set,"ENDGAME_LOCATIONS", +55001,Stardew Valley,Level 1 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55002,Stardew Valley,Level 2 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55003,Stardew Valley,Level 3 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55004,Stardew Valley,Level 4 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55005,Stardew Valley,Level 5 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55006,Stardew Valley,Level 6 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55007,Stardew Valley,Level 7 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55008,Stardew Valley,Level 8 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55009,Stardew Valley,Level 9 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55010,Stardew Valley,Level 10 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +55011,Stardew Valley,Level 1 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55012,Stardew Valley,Level 2 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55013,Stardew Valley,Level 3 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55014,Stardew Valley,Level 4 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55015,Stardew Valley,Level 5 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55016,Stardew Valley,Level 6 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55017,Stardew Valley,Level 7 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55018,Stardew Valley,Level 8 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55019,Stardew Valley,Level 9 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55020,Stardew Valley,Level 10 Socializing,"SKILL_LEVEL,SOCIALIZING_LEVEL",Socializing Skill +55021,Magic Altar,Level 1 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55022,Magic Altar,Level 2 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55023,Magic Altar,Level 3 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55024,Magic Altar,Level 4 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55025,Magic Altar,Level 5 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55026,Magic Altar,Level 6 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55027,Magic Altar,Level 7 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55028,Magic Altar,Level 8 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55029,Magic Altar,Level 9 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55030,Magic Altar,Level 10 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +55031,Town,Level 1 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55032,Town,Level 2 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55033,Town,Level 3 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55034,Town,Level 4 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55035,Town,Level 5 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55036,Town,Level 6 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55037,Town,Level 7 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55038,Town,Level 8 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55039,Town,Level 9 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55040,Town,Level 10 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +55041,Stardew Valley,Level 1 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55042,Stardew Valley,Level 2 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55043,Stardew Valley,Level 3 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55044,Stardew Valley,Level 4 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55045,Stardew Valley,Level 5 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55046,Stardew Valley,Level 6 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55047,Stardew Valley,Level 7 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55048,Stardew Valley,Level 8 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55049,Stardew Valley,Level 9 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55050,Stardew Valley,Level 10 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +55051,Stardew Valley,Level 1 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55052,Stardew Valley,Level 2 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55053,Stardew Valley,Level 3 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55054,Stardew Valley,Level 4 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55055,Stardew Valley,Level 5 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55056,Stardew Valley,Level 6 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55057,Stardew Valley,Level 7 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55058,Stardew Valley,Level 8 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55059,Stardew Valley,Level 9 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55060,Stardew Valley,Level 10 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +55501,Magic Altar,Analyze: Clear Debris,MANDATORY,Magic +55502,Magic Altar,Analyze: Till,MANDATORY,Magic +55503,Magic Altar,Analyze: Water,MANDATORY,Magic +55504,Magic Altar,Analyze All Toil School Locations,MANDATORY,Magic +55505,Magic Altar,Analyze: Evac,MANDATORY,Magic +55506,Magic Altar,Analyze: Haste,MANDATORY,Magic +55507,Magic Altar,Analyze: Heal,MANDATORY,Magic +55508,Magic Altar,Analyze All Life School Locations,MANDATORY,Magic +55509,Magic Altar,Analyze: Descend,MANDATORY,Magic +55510,Magic Altar,Analyze: Fireball,MANDATORY,Magic +55511,Magic Altar,Analyze: Frostbolt,MANDATORY,Magic +55512,Magic Altar,Analyze All Elemental School Locations,MANDATORY,Magic +55513,Magic Altar,Analyze: Lantern,MANDATORY,Magic +55514,Magic Altar,Analyze: Tendrils,MANDATORY,Magic +55515,Magic Altar,Analyze: Shockwave,MANDATORY,Magic +55516,Magic Altar,Analyze All Nature School Locations,MANDATORY,Magic +55517,Magic Altar,Analyze: Meteor,MANDATORY,Magic +55518,Magic Altar,Analyze: Lucksteal,MANDATORY,Magic +55519,Magic Altar,Analyze: Bloodmana,MANDATORY,Magic +55520,Magic Altar,Analyze All Eldritch School Locations,MANDATORY,Magic +55521,Magic Altar,Analyze Every Magic School Location,MANDATORY,Magic +56001,Museum,Friendsanity: Jasper 1 <3,FRIENDSANITY,Professor Jasper Thomas +56002,Museum,Friendsanity: Jasper 2 <3,FRIENDSANITY,Professor Jasper Thomas +56003,Museum,Friendsanity: Jasper 3 <3,FRIENDSANITY,Professor Jasper Thomas +56004,Museum,Friendsanity: Jasper 4 <3,FRIENDSANITY,Professor Jasper Thomas +56005,Museum,Friendsanity: Jasper 5 <3,FRIENDSANITY,Professor Jasper Thomas +56006,Museum,Friendsanity: Jasper 6 <3,FRIENDSANITY,Professor Jasper Thomas +56007,Museum,Friendsanity: Jasper 7 <3,FRIENDSANITY,Professor Jasper Thomas +56008,Museum,Friendsanity: Jasper 8 <3,FRIENDSANITY,Professor Jasper Thomas +56009,Museum,Friendsanity: Jasper 9 <3,FRIENDSANITY,Professor Jasper Thomas +56010,Museum,Friendsanity: Jasper 10 <3,FRIENDSANITY,Professor Jasper Thomas +56011,Museum,Friendsanity: Jasper 11 <3,FRIENDSANITY,Professor Jasper Thomas +56012,Museum,Friendsanity: Jasper 12 <3,FRIENDSANITY,Professor Jasper Thomas +56013,Museum,Friendsanity: Jasper 13 <3,FRIENDSANITY,Professor Jasper Thomas +56014,Museum,Friendsanity: Jasper 14 <3,FRIENDSANITY,Professor Jasper Thomas +56015,Yoba's Clearing,Friendsanity: Yoba 1 <3,FRIENDSANITY,Custom NPC - Yoba +56016,Yoba's Clearing,Friendsanity: Yoba 2 <3,FRIENDSANITY,Custom NPC - Yoba +56017,Yoba's Clearing,Friendsanity: Yoba 3 <3,FRIENDSANITY,Custom NPC - Yoba +56018,Yoba's Clearing,Friendsanity: Yoba 4 <3,FRIENDSANITY,Custom NPC - Yoba +56019,Yoba's Clearing,Friendsanity: Yoba 5 <3,FRIENDSANITY,Custom NPC - Yoba +56020,Yoba's Clearing,Friendsanity: Yoba 6 <3,FRIENDSANITY,Custom NPC - Yoba +56021,Yoba's Clearing,Friendsanity: Yoba 7 <3,FRIENDSANITY,Custom NPC - Yoba +56022,Yoba's Clearing,Friendsanity: Yoba 8 <3,FRIENDSANITY,Custom NPC - Yoba +56023,Yoba's Clearing,Friendsanity: Yoba 9 <3,FRIENDSANITY,Custom NPC - Yoba +56024,Yoba's Clearing,Friendsanity: Yoba 10 <3,FRIENDSANITY,Custom NPC - Yoba +56025,Marnie's Ranch,Friendsanity: Mr. Ginger 1 <3,FRIENDSANITY,Mister Ginger (cat npc) +56026,Marnie's Ranch,Friendsanity: Mr. Ginger 2 <3,FRIENDSANITY,Mister Ginger (cat npc) +56027,Marnie's Ranch,Friendsanity: Mr. Ginger 3 <3,FRIENDSANITY,Mister Ginger (cat npc) +56028,Marnie's Ranch,Friendsanity: Mr. Ginger 4 <3,FRIENDSANITY,Mister Ginger (cat npc) +56029,Marnie's Ranch,Friendsanity: Mr. Ginger 5 <3,FRIENDSANITY,Mister Ginger (cat npc) +56030,Marnie's Ranch,Friendsanity: Mr. Ginger 6 <3,FRIENDSANITY,Mister Ginger (cat npc) +56031,Marnie's Ranch,Friendsanity: Mr. Ginger 7 <3,FRIENDSANITY,Mister Ginger (cat npc) +56032,Marnie's Ranch,Friendsanity: Mr. Ginger 8 <3,FRIENDSANITY,Mister Ginger (cat npc) +56033,Marnie's Ranch,Friendsanity: Mr. Ginger 9 <3,FRIENDSANITY,Mister Ginger (cat npc) +56034,Marnie's Ranch,Friendsanity: Mr. Ginger 10 <3,FRIENDSANITY,Mister Ginger (cat npc) +56035,Town,Friendsanity: Ayeisha 1 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56036,Town,Friendsanity: Ayeisha 2 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56037,Town,Friendsanity: Ayeisha 3 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56038,Town,Friendsanity: Ayeisha 4 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56039,Town,Friendsanity: Ayeisha 5 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56040,Town,Friendsanity: Ayeisha 6 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56041,Town,Friendsanity: Ayeisha 7 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56042,Town,Friendsanity: Ayeisha 8 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56043,Town,Friendsanity: Ayeisha 9 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56044,Town,Friendsanity: Ayeisha 10 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +56045,Saloon,Friendsanity: Shiko 1 <3,FRIENDSANITY,Shiko - New Custom NPC +56046,Saloon,Friendsanity: Shiko 2 <3,FRIENDSANITY,Shiko - New Custom NPC +56047,Saloon,Friendsanity: Shiko 3 <3,FRIENDSANITY,Shiko - New Custom NPC +56048,Saloon,Friendsanity: Shiko 4 <3,FRIENDSANITY,Shiko - New Custom NPC +56049,Saloon,Friendsanity: Shiko 5 <3,FRIENDSANITY,Shiko - New Custom NPC +56050,Saloon,Friendsanity: Shiko 6 <3,FRIENDSANITY,Shiko - New Custom NPC +56051,Saloon,Friendsanity: Shiko 7 <3,FRIENDSANITY,Shiko - New Custom NPC +56052,Saloon,Friendsanity: Shiko 8 <3,FRIENDSANITY,Shiko - New Custom NPC +56053,Saloon,Friendsanity: Shiko 9 <3,FRIENDSANITY,Shiko - New Custom NPC +56054,Saloon,Friendsanity: Shiko 10 <3,FRIENDSANITY,Shiko - New Custom NPC +56055,Saloon,Friendsanity: Shiko 11 <3,FRIENDSANITY,Shiko - New Custom NPC +56056,Saloon,Friendsanity: Shiko 12 <3,FRIENDSANITY,Shiko - New Custom NPC +56057,Saloon,Friendsanity: Shiko 13 <3,FRIENDSANITY,Shiko - New Custom NPC +56058,Saloon,Friendsanity: Shiko 14 <3,FRIENDSANITY,Shiko - New Custom NPC +56059,Wizard Tower,Friendsanity: Wellwick 1 <3,FRIENDSANITY,'Prophet' Wellwick +56060,Wizard Tower,Friendsanity: Wellwick 2 <3,FRIENDSANITY,'Prophet' Wellwick +56061,Wizard Tower,Friendsanity: Wellwick 3 <3,FRIENDSANITY,'Prophet' Wellwick +56062,Wizard Tower,Friendsanity: Wellwick 4 <3,FRIENDSANITY,'Prophet' Wellwick +56063,Wizard Tower,Friendsanity: Wellwick 5 <3,FRIENDSANITY,'Prophet' Wellwick +56064,Wizard Tower,Friendsanity: Wellwick 6 <3,FRIENDSANITY,'Prophet' Wellwick +56065,Wizard Tower,Friendsanity: Wellwick 7 <3,FRIENDSANITY,'Prophet' Wellwick +56066,Wizard Tower,Friendsanity: Wellwick 8 <3,FRIENDSANITY,'Prophet' Wellwick +56067,Wizard Tower,Friendsanity: Wellwick 9 <3,FRIENDSANITY,'Prophet' Wellwick +56068,Wizard Tower,Friendsanity: Wellwick 10 <3,FRIENDSANITY,'Prophet' Wellwick +56069,Wizard Tower,Friendsanity: Wellwick 11 <3,FRIENDSANITY,'Prophet' Wellwick +56070,Wizard Tower,Friendsanity: Wellwick 12 <3,FRIENDSANITY,'Prophet' Wellwick +56071,Wizard Tower,Friendsanity: Wellwick 13 <3,FRIENDSANITY,'Prophet' Wellwick +56072,Wizard Tower,Friendsanity: Wellwick 14 <3,FRIENDSANITY,'Prophet' Wellwick +56073,Forest,Friendsanity: Delores 1 <3,FRIENDSANITY,Delores - Custom NPC +56074,Forest,Friendsanity: Delores 2 <3,FRIENDSANITY,Delores - Custom NPC +56075,Forest,Friendsanity: Delores 3 <3,FRIENDSANITY,Delores - Custom NPC +56076,Forest,Friendsanity: Delores 4 <3,FRIENDSANITY,Delores - Custom NPC +56077,Forest,Friendsanity: Delores 5 <3,FRIENDSANITY,Delores - Custom NPC +56078,Forest,Friendsanity: Delores 6 <3,FRIENDSANITY,Delores - Custom NPC +56079,Forest,Friendsanity: Delores 7 <3,FRIENDSANITY,Delores - Custom NPC +56080,Forest,Friendsanity: Delores 8 <3,FRIENDSANITY,Delores - Custom NPC +56081,Forest,Friendsanity: Delores 9 <3,FRIENDSANITY,Delores - Custom NPC +56082,Forest,Friendsanity: Delores 10 <3,FRIENDSANITY,Delores - Custom NPC +56083,Forest,Friendsanity: Delores 11 <3,FRIENDSANITY,Delores - Custom NPC +56084,Forest,Friendsanity: Delores 12 <3,FRIENDSANITY,Delores - Custom NPC +56085,Forest,Friendsanity: Delores 13 <3,FRIENDSANITY,Delores - Custom NPC +56086,Forest,Friendsanity: Delores 14 <3,FRIENDSANITY,Delores - Custom NPC +56087,Alec's Pet Shop,Friendsanity: Alec 1 <3,FRIENDSANITY,Alec Revisited +56088,Alec's Pet Shop,Friendsanity: Alec 2 <3,FRIENDSANITY,Alec Revisited +56089,Alec's Pet Shop,Friendsanity: Alec 3 <3,FRIENDSANITY,Alec Revisited +56090,Alec's Pet Shop,Friendsanity: Alec 4 <3,FRIENDSANITY,Alec Revisited +56091,Alec's Pet Shop,Friendsanity: Alec 5 <3,FRIENDSANITY,Alec Revisited +56092,Alec's Pet Shop,Friendsanity: Alec 6 <3,FRIENDSANITY,Alec Revisited +56093,Alec's Pet Shop,Friendsanity: Alec 7 <3,FRIENDSANITY,Alec Revisited +56094,Alec's Pet Shop,Friendsanity: Alec 8 <3,FRIENDSANITY,Alec Revisited +56095,Alec's Pet Shop,Friendsanity: Alec 9 <3,FRIENDSANITY,Alec Revisited +56096,Alec's Pet Shop,Friendsanity: Alec 10 <3,FRIENDSANITY,Alec Revisited +56097,Alec's Pet Shop,Friendsanity: Alec 11 <3,FRIENDSANITY,Alec Revisited +56098,Alec's Pet Shop,Friendsanity: Alec 12 <3,FRIENDSANITY,Alec Revisited +56099,Alec's Pet Shop,Friendsanity: Alec 13 <3,FRIENDSANITY,Alec Revisited +56100,Alec's Pet Shop,Friendsanity: Alec 14 <3,FRIENDSANITY,Alec Revisited +56101,Eugene's Garden,Friendsanity: Eugene 1 <3,FRIENDSANITY,Custom NPC Eugene +56102,Eugene's Garden,Friendsanity: Eugene 2 <3,FRIENDSANITY,Custom NPC Eugene +56103,Eugene's Garden,Friendsanity: Eugene 3 <3,FRIENDSANITY,Custom NPC Eugene +56104,Eugene's Garden,Friendsanity: Eugene 4 <3,FRIENDSANITY,Custom NPC Eugene +56105,Eugene's Garden,Friendsanity: Eugene 5 <3,FRIENDSANITY,Custom NPC Eugene +56106,Eugene's Garden,Friendsanity: Eugene 6 <3,FRIENDSANITY,Custom NPC Eugene +56107,Eugene's Garden,Friendsanity: Eugene 7 <3,FRIENDSANITY,Custom NPC Eugene +56108,Eugene's Garden,Friendsanity: Eugene 8 <3,FRIENDSANITY,Custom NPC Eugene +56109,Eugene's Garden,Friendsanity: Eugene 9 <3,FRIENDSANITY,Custom NPC Eugene +56110,Eugene's Garden,Friendsanity: Eugene 10 <3,FRIENDSANITY,Custom NPC Eugene +56111,Eugene's Garden,Friendsanity: Eugene 11 <3,FRIENDSANITY,Custom NPC Eugene +56112,Eugene's Garden,Friendsanity: Eugene 12 <3,FRIENDSANITY,Custom NPC Eugene +56113,Eugene's Garden,Friendsanity: Eugene 13 <3,FRIENDSANITY,Custom NPC Eugene +56114,Eugene's Garden,Friendsanity: Eugene 14 <3,FRIENDSANITY,Custom NPC Eugene +56115,Forest,Friendsanity: Juna 1 <3,FRIENDSANITY,Juna - Roommate NPC +56116,Forest,Friendsanity: Juna 2 <3,FRIENDSANITY,Juna - Roommate NPC +56117,Forest,Friendsanity: Juna 3 <3,FRIENDSANITY,Juna - Roommate NPC +56118,Forest,Friendsanity: Juna 4 <3,FRIENDSANITY,Juna - Roommate NPC +56119,Forest,Friendsanity: Juna 5 <3,FRIENDSANITY,Juna - Roommate NPC +56120,Forest,Friendsanity: Juna 6 <3,FRIENDSANITY,Juna - Roommate NPC +56121,Forest,Friendsanity: Juna 7 <3,FRIENDSANITY,Juna - Roommate NPC +56122,Forest,Friendsanity: Juna 8 <3,FRIENDSANITY,Juna - Roommate NPC +56123,Forest,Friendsanity: Juna 9 <3,FRIENDSANITY,Juna - Roommate NPC +56124,Forest,Friendsanity: Juna 10 <3,FRIENDSANITY,Juna - Roommate NPC +56125,Riley's House,Friendsanity: Riley 1 <3,FRIENDSANITY,Custom NPC - Riley +56126,Riley's House,Friendsanity: Riley 2 <3,FRIENDSANITY,Custom NPC - Riley +56127,Riley's House,Friendsanity: Riley 3 <3,FRIENDSANITY,Custom NPC - Riley +56128,Riley's House,Friendsanity: Riley 4 <3,FRIENDSANITY,Custom NPC - Riley +56129,Riley's House,Friendsanity: Riley 5 <3,FRIENDSANITY,Custom NPC - Riley +56130,Riley's House,Friendsanity: Riley 6 <3,FRIENDSANITY,Custom NPC - Riley +56131,Riley's House,Friendsanity: Riley 7 <3,FRIENDSANITY,Custom NPC - Riley +56132,Riley's House,Friendsanity: Riley 8 <3,FRIENDSANITY,Custom NPC - Riley +56133,Riley's House,Friendsanity: Riley 9 <3,FRIENDSANITY,Custom NPC - Riley +56134,Riley's House,Friendsanity: Riley 10 <3,FRIENDSANITY,Custom NPC - Riley +56135,Riley's House,Friendsanity: Riley 11 <3,FRIENDSANITY,Custom NPC - Riley +56136,Riley's House,Friendsanity: Riley 12 <3,FRIENDSANITY,Custom NPC - Riley +56137,Riley's House,Friendsanity: Riley 13 <3,FRIENDSANITY,Custom NPC - Riley +56138,Riley's House,Friendsanity: Riley 14 <3,FRIENDSANITY,Custom NPC - Riley +56139,JojaMart,Friendsanity: Claire 1 <3,FRIENDSANITY,Stardew Valley Expanded +56140,JojaMart,Friendsanity: Claire 2 <3,FRIENDSANITY,Stardew Valley Expanded +56141,JojaMart,Friendsanity: Claire 3 <3,FRIENDSANITY,Stardew Valley Expanded +56142,JojaMart,Friendsanity: Claire 4 <3,FRIENDSANITY,Stardew Valley Expanded +56143,JojaMart,Friendsanity: Claire 5 <3,FRIENDSANITY,Stardew Valley Expanded +56144,JojaMart,Friendsanity: Claire 6 <3,FRIENDSANITY,Stardew Valley Expanded +56145,JojaMart,Friendsanity: Claire 7 <3,FRIENDSANITY,Stardew Valley Expanded +56146,JojaMart,Friendsanity: Claire 8 <3,FRIENDSANITY,Stardew Valley Expanded +56147,JojaMart,Friendsanity: Claire 9 <3,FRIENDSANITY,Stardew Valley Expanded +56148,JojaMart,Friendsanity: Claire 10 <3,FRIENDSANITY,Stardew Valley Expanded +56149,JojaMart,Friendsanity: Claire 11 <3,FRIENDSANITY,Stardew Valley Expanded +56150,JojaMart,Friendsanity: Claire 12 <3,FRIENDSANITY,Stardew Valley Expanded +56151,JojaMart,Friendsanity: Claire 13 <3,FRIENDSANITY,Stardew Valley Expanded +56152,JojaMart,Friendsanity: Claire 14 <3,FRIENDSANITY,Stardew Valley Expanded +56153,Galmoran Outpost,Friendsanity: Lance 1 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56154,Galmoran Outpost,Friendsanity: Lance 2 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56155,Galmoran Outpost,Friendsanity: Lance 3 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56156,Galmoran Outpost,Friendsanity: Lance 4 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56157,Galmoran Outpost,Friendsanity: Lance 5 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56158,Galmoran Outpost,Friendsanity: Lance 6 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56159,Galmoran Outpost,Friendsanity: Lance 7 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56160,Galmoran Outpost,Friendsanity: Lance 8 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56161,Galmoran Outpost,Friendsanity: Lance 9 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56162,Galmoran Outpost,Friendsanity: Lance 10 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56163,Galmoran Outpost,Friendsanity: Lance 11 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56164,Galmoran Outpost,Friendsanity: Lance 12 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56165,Galmoran Outpost,Friendsanity: Lance 13 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56166,Galmoran Outpost,Friendsanity: Lance 14 <3,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded +56167,Jenkins' Residence,Friendsanity: Olivia 1 <3,FRIENDSANITY,Stardew Valley Expanded +56168,Jenkins' Residence,Friendsanity: Olivia 2 <3,FRIENDSANITY,Stardew Valley Expanded +56169,Jenkins' Residence,Friendsanity: Olivia 3 <3,FRIENDSANITY,Stardew Valley Expanded +56170,Jenkins' Residence,Friendsanity: Olivia 4 <3,FRIENDSANITY,Stardew Valley Expanded +56171,Jenkins' Residence,Friendsanity: Olivia 5 <3,FRIENDSANITY,Stardew Valley Expanded +56172,Jenkins' Residence,Friendsanity: Olivia 6 <3,FRIENDSANITY,Stardew Valley Expanded +56173,Jenkins' Residence,Friendsanity: Olivia 7 <3,FRIENDSANITY,Stardew Valley Expanded +56174,Jenkins' Residence,Friendsanity: Olivia 8 <3,FRIENDSANITY,Stardew Valley Expanded +56175,Jenkins' Residence,Friendsanity: Olivia 9 <3,FRIENDSANITY,Stardew Valley Expanded +56176,Jenkins' Residence,Friendsanity: Olivia 10 <3,FRIENDSANITY,Stardew Valley Expanded +56177,Jenkins' Residence,Friendsanity: Olivia 11 <3,FRIENDSANITY,Stardew Valley Expanded +56178,Jenkins' Residence,Friendsanity: Olivia 12 <3,FRIENDSANITY,Stardew Valley Expanded +56179,Jenkins' Residence,Friendsanity: Olivia 13 <3,FRIENDSANITY,Stardew Valley Expanded +56180,Jenkins' Residence,Friendsanity: Olivia 14 <3,FRIENDSANITY,Stardew Valley Expanded +56181,Wizard Tower,Friendsanity: Wizard 11 <3,FRIENDSANITY,Stardew Valley Expanded +56182,Wizard Tower,Friendsanity: Wizard 12 <3,FRIENDSANITY,Stardew Valley Expanded +56183,Wizard Tower,Friendsanity: Wizard 13 <3,FRIENDSANITY,Stardew Valley Expanded +56184,Wizard Tower,Friendsanity: Wizard 14 <3,FRIENDSANITY,Stardew Valley Expanded +56185,Blue Moon Vineyard,Friendsanity: Sophia 1 <3,FRIENDSANITY,Stardew Valley Expanded +56186,Blue Moon Vineyard,Friendsanity: Sophia 2 <3,FRIENDSANITY,Stardew Valley Expanded +56187,Blue Moon Vineyard,Friendsanity: Sophia 3 <3,FRIENDSANITY,Stardew Valley Expanded +56188,Blue Moon Vineyard,Friendsanity: Sophia 4 <3,FRIENDSANITY,Stardew Valley Expanded +56189,Blue Moon Vineyard,Friendsanity: Sophia 5 <3,FRIENDSANITY,Stardew Valley Expanded +56190,Blue Moon Vineyard,Friendsanity: Sophia 6 <3,FRIENDSANITY,Stardew Valley Expanded +56191,Blue Moon Vineyard,Friendsanity: Sophia 7 <3,FRIENDSANITY,Stardew Valley Expanded +56192,Blue Moon Vineyard,Friendsanity: Sophia 8 <3,FRIENDSANITY,Stardew Valley Expanded +56193,Blue Moon Vineyard,Friendsanity: Sophia 9 <3,FRIENDSANITY,Stardew Valley Expanded +56194,Blue Moon Vineyard,Friendsanity: Sophia 10 <3,FRIENDSANITY,Stardew Valley Expanded +56195,Blue Moon Vineyard,Friendsanity: Sophia 11 <3,FRIENDSANITY,Stardew Valley Expanded +56196,Blue Moon Vineyard,Friendsanity: Sophia 12 <3,FRIENDSANITY,Stardew Valley Expanded +56197,Blue Moon Vineyard,Friendsanity: Sophia 13 <3,FRIENDSANITY,Stardew Valley Expanded +56198,Blue Moon Vineyard,Friendsanity: Sophia 14 <3,FRIENDSANITY,Stardew Valley Expanded +56199,Jenkins' Residence,Friendsanity: Victor 1 <3,FRIENDSANITY,Stardew Valley Expanded +56200,Jenkins' Residence,Friendsanity: Victor 2 <3,FRIENDSANITY,Stardew Valley Expanded +56201,Jenkins' Residence,Friendsanity: Victor 3 <3,FRIENDSANITY,Stardew Valley Expanded +56202,Jenkins' Residence,Friendsanity: Victor 4 <3,FRIENDSANITY,Stardew Valley Expanded +56203,Jenkins' Residence,Friendsanity: Victor 5 <3,FRIENDSANITY,Stardew Valley Expanded +56204,Jenkins' Residence,Friendsanity: Victor 6 <3,FRIENDSANITY,Stardew Valley Expanded +56205,Jenkins' Residence,Friendsanity: Victor 7 <3,FRIENDSANITY,Stardew Valley Expanded +56206,Jenkins' Residence,Friendsanity: Victor 8 <3,FRIENDSANITY,Stardew Valley Expanded +56207,Jenkins' Residence,Friendsanity: Victor 9 <3,FRIENDSANITY,Stardew Valley Expanded +56208,Jenkins' Residence,Friendsanity: Victor 10 <3,FRIENDSANITY,Stardew Valley Expanded +56209,Jenkins' Residence,Friendsanity: Victor 11 <3,FRIENDSANITY,Stardew Valley Expanded +56210,Jenkins' Residence,Friendsanity: Victor 12 <3,FRIENDSANITY,Stardew Valley Expanded +56211,Jenkins' Residence,Friendsanity: Victor 13 <3,FRIENDSANITY,Stardew Valley Expanded +56212,Jenkins' Residence,Friendsanity: Victor 14 <3,FRIENDSANITY,Stardew Valley Expanded +56213,Fairhaven Farm,Friendsanity: Andy 1 <3,FRIENDSANITY,Stardew Valley Expanded +56214,Fairhaven Farm,Friendsanity: Andy 2 <3,FRIENDSANITY,Stardew Valley Expanded +56215,Fairhaven Farm,Friendsanity: Andy 3 <3,FRIENDSANITY,Stardew Valley Expanded +56216,Fairhaven Farm,Friendsanity: Andy 4 <3,FRIENDSANITY,Stardew Valley Expanded +56217,Fairhaven Farm,Friendsanity: Andy 5 <3,FRIENDSANITY,Stardew Valley Expanded +56218,Fairhaven Farm,Friendsanity: Andy 6 <3,FRIENDSANITY,Stardew Valley Expanded +56219,Fairhaven Farm,Friendsanity: Andy 7 <3,FRIENDSANITY,Stardew Valley Expanded +56220,Fairhaven Farm,Friendsanity: Andy 8 <3,FRIENDSANITY,Stardew Valley Expanded +56221,Fairhaven Farm,Friendsanity: Andy 9 <3,FRIENDSANITY,Stardew Valley Expanded +56222,Fairhaven Farm,Friendsanity: Andy 10 <3,FRIENDSANITY,Stardew Valley Expanded +56223,Aurora Vineyard,Friendsanity: Apples 1 <3,FRIENDSANITY,Stardew Valley Expanded +56224,Aurora Vineyard,Friendsanity: Apples 2 <3,FRIENDSANITY,Stardew Valley Expanded +56225,Aurora Vineyard,Friendsanity: Apples 3 <3,FRIENDSANITY,Stardew Valley Expanded +56226,Aurora Vineyard,Friendsanity: Apples 4 <3,FRIENDSANITY,Stardew Valley Expanded +56227,Aurora Vineyard,Friendsanity: Apples 5 <3,FRIENDSANITY,Stardew Valley Expanded +56228,Aurora Vineyard,Friendsanity: Apples 6 <3,FRIENDSANITY,Stardew Valley Expanded +56229,Aurora Vineyard,Friendsanity: Apples 7 <3,FRIENDSANITY,Stardew Valley Expanded +56230,Aurora Vineyard,Friendsanity: Apples 8 <3,FRIENDSANITY,Stardew Valley Expanded +56231,Aurora Vineyard,Friendsanity: Apples 9 <3,FRIENDSANITY,Stardew Valley Expanded +56232,Aurora Vineyard,Friendsanity: Apples 10 <3,FRIENDSANITY,Stardew Valley Expanded +56233,Museum,Friendsanity: Gunther 1 <3,FRIENDSANITY,Stardew Valley Expanded +56234,Museum,Friendsanity: Gunther 2 <3,FRIENDSANITY,Stardew Valley Expanded +56235,Museum,Friendsanity: Gunther 3 <3,FRIENDSANITY,Stardew Valley Expanded +56236,Museum,Friendsanity: Gunther 4 <3,FRIENDSANITY,Stardew Valley Expanded +56237,Museum,Friendsanity: Gunther 5 <3,FRIENDSANITY,Stardew Valley Expanded +56238,Museum,Friendsanity: Gunther 6 <3,FRIENDSANITY,Stardew Valley Expanded +56239,Museum,Friendsanity: Gunther 7 <3,FRIENDSANITY,Stardew Valley Expanded +56240,Museum,Friendsanity: Gunther 8 <3,FRIENDSANITY,Stardew Valley Expanded +56241,Museum,Friendsanity: Gunther 9 <3,FRIENDSANITY,Stardew Valley Expanded +56242,Museum,Friendsanity: Gunther 10 <3,FRIENDSANITY,Stardew Valley Expanded +56243,JojaMart,Friendsanity: Martin 1 <3,FRIENDSANITY,Stardew Valley Expanded +56244,JojaMart,Friendsanity: Martin 2 <3,FRIENDSANITY,Stardew Valley Expanded +56245,JojaMart,Friendsanity: Martin 3 <3,FRIENDSANITY,Stardew Valley Expanded +56246,JojaMart,Friendsanity: Martin 4 <3,FRIENDSANITY,Stardew Valley Expanded +56247,JojaMart,Friendsanity: Martin 5 <3,FRIENDSANITY,Stardew Valley Expanded +56248,JojaMart,Friendsanity: Martin 6 <3,FRIENDSANITY,Stardew Valley Expanded +56249,JojaMart,Friendsanity: Martin 7 <3,FRIENDSANITY,Stardew Valley Expanded +56250,JojaMart,Friendsanity: Martin 8 <3,FRIENDSANITY,Stardew Valley Expanded +56251,JojaMart,Friendsanity: Martin 9 <3,FRIENDSANITY,Stardew Valley Expanded +56252,JojaMart,Friendsanity: Martin 10 <3,FRIENDSANITY,Stardew Valley Expanded +56253,Adventurer's Guild,Friendsanity: Marlon 1 <3,FRIENDSANITY,Stardew Valley Expanded +56254,Adventurer's Guild,Friendsanity: Marlon 2 <3,FRIENDSANITY,Stardew Valley Expanded +56255,Adventurer's Guild,Friendsanity: Marlon 3 <3,FRIENDSANITY,Stardew Valley Expanded +56256,Adventurer's Guild,Friendsanity: Marlon 4 <3,FRIENDSANITY,Stardew Valley Expanded +56257,Adventurer's Guild,Friendsanity: Marlon 5 <3,FRIENDSANITY,Stardew Valley Expanded +56258,Adventurer's Guild,Friendsanity: Marlon 6 <3,FRIENDSANITY,Stardew Valley Expanded +56259,Adventurer's Guild,Friendsanity: Marlon 7 <3,FRIENDSANITY,Stardew Valley Expanded +56260,Adventurer's Guild,Friendsanity: Marlon 8 <3,FRIENDSANITY,Stardew Valley Expanded +56261,Adventurer's Guild,Friendsanity: Marlon 9 <3,FRIENDSANITY,Stardew Valley Expanded +56262,Adventurer's Guild,Friendsanity: Marlon 10 <3,FRIENDSANITY,Stardew Valley Expanded +56263,Wizard Tower,Friendsanity: Morgan 1 <3,FRIENDSANITY,Stardew Valley Expanded +56264,Wizard Tower,Friendsanity: Morgan 2 <3,FRIENDSANITY,Stardew Valley Expanded +56265,Wizard Tower,Friendsanity: Morgan 3 <3,FRIENDSANITY,Stardew Valley Expanded +56266,Wizard Tower,Friendsanity: Morgan 4 <3,FRIENDSANITY,Stardew Valley Expanded +56267,Wizard Tower,Friendsanity: Morgan 5 <3,FRIENDSANITY,Stardew Valley Expanded +56268,Wizard Tower,Friendsanity: Morgan 6 <3,FRIENDSANITY,Stardew Valley Expanded +56269,Wizard Tower,Friendsanity: Morgan 7 <3,FRIENDSANITY,Stardew Valley Expanded +56270,Wizard Tower,Friendsanity: Morgan 8 <3,FRIENDSANITY,Stardew Valley Expanded +56271,Wizard Tower,Friendsanity: Morgan 9 <3,FRIENDSANITY,Stardew Valley Expanded +56272,Wizard Tower,Friendsanity: Morgan 10 <3,FRIENDSANITY,Stardew Valley Expanded +56273,Scarlett's House,Friendsanity: Scarlett 1 <3,FRIENDSANITY,Stardew Valley Expanded +56274,Scarlett's House,Friendsanity: Scarlett 2 <3,FRIENDSANITY,Stardew Valley Expanded +56275,Scarlett's House,Friendsanity: Scarlett 3 <3,FRIENDSANITY,Stardew Valley Expanded +56276,Scarlett's House,Friendsanity: Scarlett 4 <3,FRIENDSANITY,Stardew Valley Expanded +56277,Scarlett's House,Friendsanity: Scarlett 5 <3,FRIENDSANITY,Stardew Valley Expanded +56278,Scarlett's House,Friendsanity: Scarlett 6 <3,FRIENDSANITY,Stardew Valley Expanded +56279,Scarlett's House,Friendsanity: Scarlett 7 <3,FRIENDSANITY,Stardew Valley Expanded +56280,Scarlett's House,Friendsanity: Scarlett 8 <3,FRIENDSANITY,Stardew Valley Expanded +56281,Scarlett's House,Friendsanity: Scarlett 9 <3,FRIENDSANITY,Stardew Valley Expanded +56282,Scarlett's House,Friendsanity: Scarlett 10 <3,FRIENDSANITY,Stardew Valley Expanded +56283,Susan's House,Friendsanity: Susan 1 <3,FRIENDSANITY,Stardew Valley Expanded +56284,Susan's House,Friendsanity: Susan 2 <3,FRIENDSANITY,Stardew Valley Expanded +56285,Susan's House,Friendsanity: Susan 3 <3,FRIENDSANITY,Stardew Valley Expanded +56286,Susan's House,Friendsanity: Susan 4 <3,FRIENDSANITY,Stardew Valley Expanded +56287,Susan's House,Friendsanity: Susan 5 <3,FRIENDSANITY,Stardew Valley Expanded +56288,Susan's House,Friendsanity: Susan 6 <3,FRIENDSANITY,Stardew Valley Expanded +56289,Susan's House,Friendsanity: Susan 7 <3,FRIENDSANITY,Stardew Valley Expanded +56290,Susan's House,Friendsanity: Susan 8 <3,FRIENDSANITY,Stardew Valley Expanded +56291,Susan's House,Friendsanity: Susan 9 <3,FRIENDSANITY,Stardew Valley Expanded +56292,Susan's House,Friendsanity: Susan 10 <3,FRIENDSANITY,Stardew Valley Expanded +56293,JojaMart,Friendsanity: Morris 1 <3,FRIENDSANITY,Stardew Valley Expanded +56294,JojaMart,Friendsanity: Morris 2 <3,FRIENDSANITY,Stardew Valley Expanded +56295,JojaMart,Friendsanity: Morris 3 <3,FRIENDSANITY,Stardew Valley Expanded +56296,JojaMart,Friendsanity: Morris 4 <3,FRIENDSANITY,Stardew Valley Expanded +56297,JojaMart,Friendsanity: Morris 5 <3,FRIENDSANITY,Stardew Valley Expanded +56298,JojaMart,Friendsanity: Morris 6 <3,FRIENDSANITY,Stardew Valley Expanded +56299,JojaMart,Friendsanity: Morris 7 <3,FRIENDSANITY,Stardew Valley Expanded +56300,JojaMart,Friendsanity: Morris 8 <3,FRIENDSANITY,Stardew Valley Expanded +56301,JojaMart,Friendsanity: Morris 9 <3,FRIENDSANITY,Stardew Valley Expanded +56302,JojaMart,Friendsanity: Morris 10 <3,FRIENDSANITY,Stardew Valley Expanded +56303,Witch's Swamp,Friendsanity: Zic 1 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56304,Witch's Swamp,Friendsanity: Zic 2 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56305,Witch's Swamp,Friendsanity: Zic 3 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56306,Witch's Swamp,Friendsanity: Zic 4 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56307,Witch's Swamp,Friendsanity: Zic 5 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56308,Witch's Swamp,Friendsanity: Zic 6 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56309,Witch's Swamp,Friendsanity: Zic 7 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56310,Witch's Swamp,Friendsanity: Zic 8 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56311,Witch's Swamp,Friendsanity: Zic 9 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56312,Witch's Swamp,Friendsanity: Zic 10 <3,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul +56313,Witch's Attic,Friendsanity: Alecto 1 <3,FRIENDSANITY,Alecto the Witch +56314,Witch's Attic,Friendsanity: Alecto 2 <3,FRIENDSANITY,Alecto the Witch +56315,Witch's Attic,Friendsanity: Alecto 3 <3,FRIENDSANITY,Alecto the Witch +56316,Witch's Attic,Friendsanity: Alecto 4 <3,FRIENDSANITY,Alecto the Witch +56317,Witch's Attic,Friendsanity: Alecto 5 <3,FRIENDSANITY,Alecto the Witch +56318,Witch's Attic,Friendsanity: Alecto 6 <3,FRIENDSANITY,Alecto the Witch +56319,Witch's Attic,Friendsanity: Alecto 7 <3,FRIENDSANITY,Alecto the Witch +56320,Witch's Attic,Friendsanity: Alecto 8 <3,FRIENDSANITY,Alecto the Witch +56321,Witch's Attic,Friendsanity: Alecto 9 <3,FRIENDSANITY,Alecto the Witch +56322,Witch's Attic,Friendsanity: Alecto 10 <3,FRIENDSANITY,Alecto the Witch +56323,Mouse House,Friendsanity: Lacey 1 <3,FRIENDSANITY,Hat Mouse Lacey +56324,Mouse House,Friendsanity: Lacey 2 <3,FRIENDSANITY,Hat Mouse Lacey +56325,Mouse House,Friendsanity: Lacey 3 <3,FRIENDSANITY,Hat Mouse Lacey +56326,Mouse House,Friendsanity: Lacey 4 <3,FRIENDSANITY,Hat Mouse Lacey +56327,Mouse House,Friendsanity: Lacey 5 <3,FRIENDSANITY,Hat Mouse Lacey +56328,Mouse House,Friendsanity: Lacey 6 <3,FRIENDSANITY,Hat Mouse Lacey +56329,Mouse House,Friendsanity: Lacey 7 <3,FRIENDSANITY,Hat Mouse Lacey +56330,Mouse House,Friendsanity: Lacey 8 <3,FRIENDSANITY,Hat Mouse Lacey +56331,Mouse House,Friendsanity: Lacey 9 <3,FRIENDSANITY,Hat Mouse Lacey +56332,Mouse House,Friendsanity: Lacey 10 <3,FRIENDSANITY,Hat Mouse Lacey +56333,Mouse House,Friendsanity: Lacey 11 <3,FRIENDSANITY,Hat Mouse Lacey +56334,Mouse House,Friendsanity: Lacey 12 <3,FRIENDSANITY,Hat Mouse Lacey +56335,Mouse House,Friendsanity: Lacey 13 <3,FRIENDSANITY,Hat Mouse Lacey +56336,Mouse House,Friendsanity: Lacey 14 <3,FRIENDSANITY,Hat Mouse Lacey +56337,Boarding House - First Floor,Friendsanity: Joel 1 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56338,Boarding House - First Floor,Friendsanity: Joel 2 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56339,Boarding House - First Floor,Friendsanity: Joel 3 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56340,Boarding House - First Floor,Friendsanity: Joel 4 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56341,Boarding House - First Floor,Friendsanity: Joel 5 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56342,Boarding House - First Floor,Friendsanity: Joel 6 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56343,Boarding House - First Floor,Friendsanity: Joel 7 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56344,Boarding House - First Floor,Friendsanity: Joel 8 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56345,Boarding House - First Floor,Friendsanity: Joel 9 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56346,Boarding House - First Floor,Friendsanity: Joel 10 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56347,Boarding House - First Floor,Friendsanity: Sheila 1 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56348,Boarding House - First Floor,Friendsanity: Sheila 2 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56349,Boarding House - First Floor,Friendsanity: Sheila 3 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56350,Boarding House - First Floor,Friendsanity: Sheila 4 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56351,Boarding House - First Floor,Friendsanity: Sheila 5 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56352,Boarding House - First Floor,Friendsanity: Sheila 6 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56353,Boarding House - First Floor,Friendsanity: Sheila 7 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56354,Boarding House - First Floor,Friendsanity: Sheila 8 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56355,Boarding House - First Floor,Friendsanity: Sheila 9 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56356,Boarding House - First Floor,Friendsanity: Sheila 10 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56357,Boarding House - First Floor,Friendsanity: Sheila 11 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56358,Boarding House - First Floor,Friendsanity: Sheila 12 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56359,Boarding House - First Floor,Friendsanity: Sheila 13 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56360,Boarding House - First Floor,Friendsanity: Sheila 14 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56361,The Lost Valley,Friendsanity: Gregory 1 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56362,The Lost Valley,Friendsanity: Gregory 2 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56363,The Lost Valley,Friendsanity: Gregory 3 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56364,The Lost Valley,Friendsanity: Gregory 4 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56365,The Lost Valley,Friendsanity: Gregory 5 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56366,The Lost Valley,Friendsanity: Gregory 6 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56367,The Lost Valley,Friendsanity: Gregory 7 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56368,The Lost Valley,Friendsanity: Gregory 8 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56369,The Lost Valley,Friendsanity: Gregory 9 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56370,The Lost Valley,Friendsanity: Gregory 10 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56371,The Lost Valley,Friendsanity: Gregory 11 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56372,The Lost Valley,Friendsanity: Gregory 12 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56373,The Lost Valley,Friendsanity: Gregory 13 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +56374,The Lost Valley,Friendsanity: Gregory 14 <3,FRIENDSANITY,Boarding House and Bus Stop Extension +57001,Pierre's General Store,Premium Pack,"BACKPACK,BACKPACK_TIER",Bigger Backpack +57002,Carpenter Shop,Tractor Garage Blueprint,BUILDING_BLUEPRINT,Tractor Mod +57003,The Deep Woods Depth 100,Pet the Deep Woods Unicorn,MANDATORY,DeepWoods +57004,The Deep Woods Depth 50,Breaking Up Deep Woods Gingerbread House,MANDATORY,DeepWoods +57005,The Deep Woods Depth 50,Drinking From Deep Woods Fountain,MANDATORY,DeepWoods +57006,The Deep Woods Depth 100,Deep Woods Treasure Chest,MANDATORY,DeepWoods +57007,The Deep Woods Depth 100,Deep Woods Trash Bin,MANDATORY,DeepWoods +57008,The Deep Woods Depth 50,Chop Down a Deep Woods Iridium Tree,MANDATORY,DeepWoods +57009,The Deep Woods Depth 10,The Deep Woods: Depth 10,ELEVATOR,DeepWoods +57010,The Deep Woods Depth 20,The Deep Woods: Depth 20,ELEVATOR,DeepWoods +57011,The Deep Woods Depth 30,The Deep Woods: Depth 30,ELEVATOR,DeepWoods +57012,The Deep Woods Depth 40,The Deep Woods: Depth 40,ELEVATOR,DeepWoods +57013,The Deep Woods Depth 50,The Deep Woods: Depth 50,ELEVATOR,DeepWoods +57014,The Deep Woods Depth 60,The Deep Woods: Depth 60,ELEVATOR,DeepWoods +57015,The Deep Woods Depth 70,The Deep Woods: Depth 70,ELEVATOR,DeepWoods +57016,The Deep Woods Depth 80,The Deep Woods: Depth 80,ELEVATOR,DeepWoods +57017,The Deep Woods Depth 90,The Deep Woods: Depth 90,ELEVATOR,DeepWoods +57018,The Deep Woods Depth 100,The Deep Woods: Depth 100,ELEVATOR,DeepWoods +57019,The Deep Woods Depth 50,Purify an Infested Lichtung,MANDATORY,DeepWoods +57020,Skull Cavern Floor 25,Skull Cavern: Floor 25,ELEVATOR,Skull Cavern Elevator +57021,Skull Cavern Floor 50,Skull Cavern: Floor 50,ELEVATOR,Skull Cavern Elevator +57022,Skull Cavern Floor 75,Skull Cavern: Floor 75,ELEVATOR,Skull Cavern Elevator +57023,Skull Cavern Floor 100,Skull Cavern: Floor 100,ELEVATOR,Skull Cavern Elevator +57024,Skull Cavern Floor 125,Skull Cavern: Floor 125,ELEVATOR,Skull Cavern Elevator +57025,Skull Cavern Floor 150,Skull Cavern: Floor 150,ELEVATOR,Skull Cavern Elevator +57026,Skull Cavern Floor 175,Skull Cavern: Floor 175,ELEVATOR,Skull Cavern Elevator +57027,Skull Cavern Floor 200,Skull Cavern: Floor 200,ELEVATOR,Skull Cavern Elevator +57028,The Deep Woods Depth 100,The Sword in the Stone,MANDATORY,DeepWoods +57051,Abandoned Mines - 1A,Abandoned Treasure - Floor 1A,MANDATORY,Boarding House and Bus Stop Extension +57052,Abandoned Mines - 1B,Abandoned Treasure - Floor 1B,MANDATORY,Boarding House and Bus Stop Extension +57053,Abandoned Mines - 2A,Abandoned Treasure - Floor 2A,MANDATORY,Boarding House and Bus Stop Extension +57054,Abandoned Mines - 2B,Abandoned Treasure - Floor 2B,MANDATORY,Boarding House and Bus Stop Extension +57055,Abandoned Mines - 3,Abandoned Treasure - Floor 3,MANDATORY,Boarding House and Bus Stop Extension +57056,Abandoned Mines - 4,Abandoned Treasure - Floor 4,MANDATORY,Boarding House and Bus Stop Extension +57057,Abandoned Mines - 5,Abandoned Treasure - Floor 5,MANDATORY,Boarding House and Bus Stop Extension +57101,Pierre's General Store,Premium Pack 1,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57102,Pierre's General Store,Premium Pack 2,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57103,Pierre's General Store,Premium Pack 3,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57104,Pierre's General Store,Premium Pack 4,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57105,Pierre's General Store,Premium Pack 5,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57106,Pierre's General Store,Premium Pack 6,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57107,Pierre's General Store,Premium Pack 7,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57108,Pierre's General Store,Premium Pack 8,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57109,Pierre's General Store,Premium Pack 9,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57110,Pierre's General Store,Premium Pack 10,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57111,Pierre's General Store,Premium Pack 11,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57112,Pierre's General Store,Premium Pack 12,"BACKPACK,SPLIT_BACKPACK",Bigger Backpack +57401,Farm,Cook Magic Elixir,COOKSANITY,Magic +57402,Farm,Craft Travel Core,"CRAFTSANITY,CRAFTSANITY_CRAFT",Magic +57403,Farm,Craft Haste Elixir,"CRAFTSANITY,CRAFTSANITY_CRAFT",Stardew Valley Expanded +57404,Farm,Craft Hero Elixir,"CRAFTSANITY,CRAFTSANITY_CRAFT",Stardew Valley Expanded +57405,Farm,Craft Armor Elixir,"CRAFTSANITY,CRAFTSANITY_CRAFT",Stardew Valley Expanded +57406,Witch's Swamp,Craft Ginger Tincture,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND",Distant Lands - Witch Swamp Overhaul +57407,Farm,Craft Glass Path,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57408,Farm,Craft Glass Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57409,Farm,Craft Glass Fence,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57410,Farm,Craft Bone Path,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57411,Farm,Craft Water Sifter,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57412,Farm,Craft Wooden Display,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57413,Farm,Craft Hardwood Display,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57414,Farm,Craft Dwarf Gadget: Infinite Volcano Simulation,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND",Archaeology +57415,Farm,Craft Grinder,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57416,Farm,Craft Preservation Chamber,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57417,Farm,Craft Hardwood Preservation Chamber,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57418,Farm,Craft Ancient Battery Production Station,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57419,Farm,Craft Neanderthal Skeleton,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57420,Farm,Craft Pterodactyl Skeleton L,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57421,Farm,Craft Pterodactyl Skeleton M,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57422,Farm,Craft Pterodactyl Skeleton R,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57423,Farm,Craft T-Rex Skeleton L,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57424,Farm,Craft T-Rex Skeleton M,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57425,Farm,Craft T-Rex Skeleton R,"CRAFTSANITY,CRAFTSANITY_CRAFT",Boarding House and Bus Stop Extension +57426,Farm,Craft Restoration Table,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57427,Farm,Craft Rusty Path,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57428,Farm,Craft Rusty Brazier,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57429,Farm,Craft Lucky Ring,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57430,Farm,Craft Bone Fence,"CRAFTSANITY,CRAFTSANITY_CRAFT",Archaeology +57431,Farm,Craft Bouquet,"CRAFTSANITY,CRAFTSANITY_CRAFT",Socializing Skill +57432,Farm,Craft Trash Bin,"CRAFTSANITY,CRAFTSANITY_CRAFT",Binning Skill +57433,Farm,Craft Composter,"CRAFTSANITY,CRAFTSANITY_CRAFT",Binning Skill +57434,Farm,Craft Recycling Bin,"CRAFTSANITY,CRAFTSANITY_CRAFT",Binning Skill +57435,Farm,Craft Advanced Recycling Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT",Binning Skill +57440,Farm,Craft Copper Slot Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT",Luck Skill +57441,Farm,Craft Gold Slot Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT",Luck Skill +57442,Farm,Craft Iridium Slot Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT",Luck Skill +57443,Farm,Craft Radioactive Slot Machine,"CRAFTSANITY,CRAFTSANITY_CRAFT,GINGER_ISLAND,REQUIRES_QI_ORDERS",Luck Skill +57451,Adventurer's Guild,Magic Elixir Recipe,"CHEFSANITY,CRAFTSANITY_RECIPE,CHEFSANITY_PURCHASE",Magic +57452,Adventurer's Guild,Travel Core Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Magic +57453,Alesia Shop,Haste Elixir Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Stardew Valley Expanded +57454,Isaac Shop,Hero Elixir Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Stardew Valley Expanded +57455,Alesia Shop,Armor Elixir Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Stardew Valley Expanded +57501,Mountain,Quest: Missing Envelope,"STORY_QUEST",Ayeisha - The Postal Worker (Custom NPC) +57502,Forest,Quest: Ayeisha's Lost Ring,"STORY_QUEST",Ayeisha - The Postal Worker (Custom NPC) +57503,Forest,Quest: Mr.Ginger's request,"STORY_QUEST",Mister Ginger (cat npc) +57504,Forest,Quest: Juna's Drink Request,"STORY_QUEST",Juna - Roommate NPC +57505,Forest,Quest: Juna's BFF Request,"STORY_QUEST",Juna - Roommate NPC +57506,Forest,Juna's Monster Mash,SPECIAL_ORDER_BOARD,Juna - Roommate NPC +57507,Adventurer's Guild,Quest: Marlon's Boat,"STORY_QUEST,GINGER_ISLAND",Stardew Valley Expanded +57508,Railroad,Quest: The Railroad Boulder,"STORY_QUEST",Stardew Valley Expanded +57509,Grandpa's Shed Interior,Quest: Grandpa's Shed,"STORY_QUEST",Stardew Valley Expanded +57510,Aurora Vineyard,Quest: Aurora Vineyard,"STORY_QUEST",Stardew Valley Expanded +57511,Lance's House Main,Quest: Monster Crops,"STORY_QUEST,GINGER_ISLAND",Stardew Valley Expanded +57512,Sewer,Quest: Void Soul Retrieval,"STORY_QUEST,GINGER_ISLAND",Stardew Valley Expanded +57513,Fairhaven Farm,Andy's Cellar,SPECIAL_ORDER_BOARD,Stardew Valley Expanded +57514,Adventurer's Guild,A Mysterious Venture,SPECIAL_ORDER_BOARD,Stardew Valley Expanded +57515,Jenkins' Residence,An Elegant Reception,SPECIAL_ORDER_BOARD,Stardew Valley Expanded +57516,Sophia's House,Fairy Garden,"SPECIAL_ORDER_BOARD,GINGER_ISLAND",Stardew Valley Expanded +57517,Susan's House,Homemade Fertilizer,SPECIAL_ORDER_BOARD,Stardew Valley Expanded +57519,Witch's Swamp,Quest: Corrupted Crops Task,GINGER_ISLAND,Distant Lands - Witch Swamp Overhaul +57520,Witch's Swamp,Quest: A New Pot,STORY_QUEST,Distant Lands - Witch Swamp Overhaul +57521,Witch's Swamp,Quest: Fancy Blanket Task,STORY_QUEST,Distant Lands - Witch Swamp Overhaul +57522,Witch's Swamp,Quest: Witch's order,GINGER_ISLAND,Distant Lands - Witch Swamp Overhaul +57523,Boarding House - First Floor,Quest: Pumpkin Soup,STORY_QUEST,Boarding House and Bus Stop Extension +57524,Museum,Geode Order,SPECIAL_ORDER_BOARD,Professor Jasper Thomas +57525,Museum,Dwarven Scrolls,SPECIAL_ORDER_BOARD,Professor Jasper Thomas +57526,Mouse House,Quest: Hats for the Hat Mouse,STORY_QUEST,Hat Mouse Lacey +57551,Kitchen,Cook Baked Berry Oatmeal,COOKSANITY,Stardew Valley Expanded +57552,Kitchen,Cook Flower Cookie,COOKSANITY,Stardew Valley Expanded +57553,Kitchen,Cook Big Bark Burger,COOKSANITY,Stardew Valley Expanded +57554,Kitchen,Cook Frog Legs,COOKSANITY,Stardew Valley Expanded +57555,Kitchen,Cook Glazed Butterfish,COOKSANITY,Stardew Valley Expanded +57556,Kitchen,Cook Mixed Berry Pie,COOKSANITY,Stardew Valley Expanded +57557,Kitchen,Cook Mushroom Berry Rice,COOKSANITY,Stardew Valley Expanded +57558,Kitchen,Cook Seaweed Salad,COOKSANITY,Stardew Valley Expanded +57559,Kitchen,Cook Void Delight,COOKSANITY,Stardew Valley Expanded +57560,Kitchen,Cook Void Salmon Sushi,COOKSANITY,Stardew Valley Expanded +57561,Kitchen,Cook Mushroom Kebab,COOKSANITY,Distant Lands - Witch Swamp Overhaul +57562,Kitchen,Cook Crayfish Soup,COOKSANITY,Distant Lands - Witch Swamp Overhaul +57563,Kitchen,Cook Pemmican,COOKSANITY,Distant Lands - Witch Swamp Overhaul +57564,Kitchen,Cook Void Mint Tea,COOKSANITY,Distant Lands - Witch Swamp Overhaul +57565,Kitchen,Cook Special Pumpkin Soup,COOKSANITY,Boarding House and Bus Stop Extension +57566,Kitchen,Cook Digger's Delight,COOKSANITY,Archaeology +57567,Kitchen,Cook Rocky Root Coffee,COOKSANITY,Archaeology +57568,Kitchen,Cook Ancient Jello,COOKSANITY,Archaeology +57569,Kitchen,Cook Grilled Cheese,COOKSANITY,Binning Skill +57570,Kitchen,Cook Fish Casserole,COOKSANITY,Binning Skill +57601,Bear Shop,Baked Berry Oatmeal Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded +57602,Bear Shop,Flower Cookie Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded +57603,Saloon,Big Bark Burger Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded +57604,Adventurer's Guild,Frog Legs Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded +57605,Saloon,Glazed Butterfish Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded +57606,Saloon,Mixed Berry Pie Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded +57607,Adventurer's Guild,Mushroom Berry Rice Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded +57608,Adventurer's Guild,Seaweed Salad Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded +57609,Sewer,Void Delight Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Stardew Valley Expanded +57610,Sewer,Void Salmon Sushi Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded +57611,Witch's Swamp,Mushroom Kebab Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul +57613,Witch's Swamp,Pemmican Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul +57614,Witch's Swamp,Void Mint Tea Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul +57616,Mines Dwarf Shop,Neanderthal Skeleton Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57617,Mines Dwarf Shop,Pterodactyl Skeleton L Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57618,Mines Dwarf Shop,Pterodactyl Skeleton M Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57619,Mines Dwarf Shop,Pterodactyl Skeleton R Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57620,Mines Dwarf Shop,T-Rex Skeleton L Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57621,Mines Dwarf Shop,T-Rex Skeleton M Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57622,Mines Dwarf Shop,T-Rex Skeleton R Recipe,"CRAFTSANITY,CRAFTSANITY_RECIPE",Boarding House and Bus Stop Extension +57623,Farm,Digger's Delight Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology +57624,Farm,Rocky Root Coffee Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology +57625,Farm,Ancient Jello Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology +57627,Farm,Grilled Cheese Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Binning Skill +57628,Farm,Fish Casserole Recipe,"CHEFSANITY,CHEFSANITY_SKILL",Binning Skill +57651,Alesia Shop,Tempered Galaxy Dagger,MANDATORY,Stardew Valley Expanded +57652,Isaac Shop,Tempered Galaxy Sword,MANDATORY,Stardew Valley Expanded +57653,Isaac Shop,Tempered Galaxy Hammer,MANDATORY,Stardew Valley Expanded +57701,Island South,Fishsanity: Baby Lunaloo,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57702,Crimson Badlands,Fishsanity: Bonefish,FISHSANITY,Stardew Valley Expanded +57703,Forest,Fishsanity: Bull Trout,FISHSANITY,Stardew Valley Expanded +57704,Forest West,Fishsanity: Butterfish,FISHSANITY,Stardew Valley Expanded +57705,Island South,Fishsanity: Clownfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57706,Highlands Outside,Fishsanity: Daggerfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57707,Mountain,Fishsanity: Frog,FISHSANITY,Stardew Valley Expanded +57708,Highlands Outside,Fishsanity: Gemfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57709,Sprite Spring,Fishsanity: Goldenfish,FISHSANITY,Stardew Valley Expanded +57710,Secret Woods,Fishsanity: Grass Carp,FISHSANITY,Stardew Valley Expanded +57711,Forest West,Fishsanity: King Salmon,FISHSANITY,Stardew Valley Expanded +57712,Island West,Fishsanity: Lunaloo,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57713,Sprite Spring,Fishsanity: Meteor Carp,FISHSANITY,Stardew Valley Expanded +57714,Town,Fishsanity: Minnow,FISHSANITY,Stardew Valley Expanded +57715,Forest West,Fishsanity: Puppyfish,FISHSANITY,Stardew Valley Expanded +57716,Sewer,Fishsanity: Radioactive Bass,FISHSANITY,Stardew Valley Expanded +57717,Island West,Fishsanity: Seahorse,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57718,Island West,Fishsanity: Sea Sponge,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57719,Island South,Fishsanity: Shiny Lunaloo,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57720,Mutant Bug Lair,Fishsanity: Snatcher Worm,FISHSANITY,Stardew Valley Expanded +57721,Beach,Fishsanity: Starfish,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57722,Fable Reef,Fishsanity: Torpedo Trout,"FISHSANITY,GINGER_ISLAND",Stardew Valley Expanded +57723,Witch's Swamp,Fishsanity: Void Eel,FISHSANITY,Stardew Valley Expanded +57724,Mutant Bug Lair,Fishsanity: Water Grub,FISHSANITY,Stardew Valley Expanded +57725,Crimson Badlands,Fishsanity: Undeadfish,FISHSANITY,Stardew Valley Expanded +57726,Shearwater Bridge,Fishsanity: Kittyfish,FISHSANITY,Stardew Valley Expanded +57728,Witch's Swamp,Fishsanity: Void Minnow,FISHSANITY,Distant Lands - Witch Swamp Overhaul +57729,Witch's Swamp,Fishsanity: Swamp Leech,FISHSANITY,Distant Lands - Witch Swamp Overhaul +57730,Witch's Swamp,Fishsanity: Giant Horsehoe Crab,FISHSANITY,Distant Lands - Witch Swamp Overhaul +57731,Witch's Swamp,Fishsanity: Purple Algae,FISHSANITY,Distant Lands - Witch Swamp Overhaul +57901,Farm,Harvest Monster Fruit,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded +57902,Farm,Harvest Salal Berry,CROPSANITY,Stardew Valley Expanded +57903,Farm,Harvest Slime Berry,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded +57904,Farm,Harvest Ancient Fiber,CROPSANITY,Stardew Valley Expanded +57905,Farm,Harvest Monster Mushroom,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded +57906,Farm,Harvest Void Root,"CROPSANITY,GINGER_ISLAND",Stardew Valley Expanded +57907,Farm,Harvest Void Mint Leaves,CROPSANITY,Distant Lands - Witch Swamp Overhaul +57908,Farm,Harvest Vile Ancient Fruit,CROPSANITY,Distant Lands - Witch Swamp Overhaul +58001,Shipping,Shipsanity: Magic Elixir,SHIPSANITY,Magic +58002,Shipping,Shipsanity: Travel Core,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Magic +58003,Shipping,Shipsanity: Aegis Elixir,SHIPSANITY,Stardew Valley Expanded +58004,Shipping,Shipsanity: Aged Blue Moon Wine,SHIPSANITY,Stardew Valley Expanded +58005,Shipping,Shipsanity: Ancient Fern Seed,SHIPSANITY,Stardew Valley Expanded +58006,Shipping,Shipsanity: Ancient Fiber,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58007,Shipping,Shipsanity: Armor Elixir,SHIPSANITY,Stardew Valley Expanded +58008,Shipping,Shipsanity: Baby Lunaloo,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58009,Shipping,Shipsanity: Baked Berry Oatmeal,SHIPSANITY,Stardew Valley Expanded +58010,Shipping,Shipsanity: Barbarian Elixir,SHIPSANITY,Stardew Valley Expanded +58011,Shipping,Shipsanity: Bearberry,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58012,Shipping,Shipsanity: Big Bark Burger,SHIPSANITY,Stardew Valley Expanded +58013,Shipping,Shipsanity: Conch,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58014,Shipping,Shipsanity: Blue Moon Wine,SHIPSANITY,Stardew Valley Expanded +58015,Shipping,Shipsanity: Bonefish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58016,Shipping,Shipsanity: Bull Trout,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58017,Shipping,Shipsanity: Butterfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58018,Shipping,Shipsanity: Clownfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58019,Shipping,Shipsanity: Daggerfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58020,Shipping,Shipsanity: Dewdrop Berry,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58021,Shipping,Shipsanity: Sand Dollar,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58023,Shipping,Shipsanity: Ferngill Primrose,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58024,Shipping,Shipsanity: Flower Cookie,SHIPSANITY,Stardew Valley Expanded +58025,Shipping,Shipsanity: Frog,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58026,Shipping,Shipsanity: Frog Legs,SHIPSANITY,Stardew Valley Expanded +58027,Shipping,Shipsanity: Fungus Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded +58029,Shipping,Shipsanity: Gemfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58030,Shipping,Shipsanity: Glazed Butterfish,"SHIPSANITY",Stardew Valley Expanded +58031,Shipping,Shipsanity: Golden Ocean Flower,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58032,Shipping,Shipsanity: Goldenfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58033,Shipping,Shipsanity: Goldenrod,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58034,Shipping,Shipsanity: Grampleton Orange Chicken,SHIPSANITY,Stardew Valley Expanded +58035,Shipping,Shipsanity: Grass Carp,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58036,Shipping,Shipsanity: Gravity Elixir,SHIPSANITY,Stardew Valley Expanded +58037,Shipping,Shipsanity: Green Mushroom,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58038,Shipping,Shipsanity: Haste Elixir,SHIPSANITY,Stardew Valley Expanded +58039,Shipping,Shipsanity: Hero Elixir,SHIPSANITY,Stardew Valley Expanded +58040,Shipping,Shipsanity: King Salmon,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58050,Shipping,Shipsanity: Kittyfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58051,Shipping,Shipsanity: Lightning Elixir,SHIPSANITY,Stardew Valley Expanded +58052,Shipping,Shipsanity: Four Leaf Clover,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58053,Shipping,Shipsanity: Lunaloo,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58054,Shipping,Shipsanity: Meteor Carp,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58055,Shipping,Shipsanity: Minnow,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58056,Shipping,Shipsanity: Mixed Berry Pie,SHIPSANITY,Stardew Valley Expanded +58057,Shipping,Shipsanity: Monster Fruit,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58058,Shipping,Shipsanity: Monster Mushroom,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58059,Shipping,Shipsanity: Mushroom Berry Rice,SHIPSANITY,Stardew Valley Expanded +58060,Shipping,Shipsanity: Mushroom Colony,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58061,Shipping,Shipsanity: Ornate Treasure Chest,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded +58062,Shipping,Shipsanity: Poison Mushroom,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58063,Shipping,Shipsanity: Puppyfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58064,Shipping,Shipsanity: Radioactive Bass,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58065,Shipping,Shipsanity: Red Baneberry,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58066,Shipping,Shipsanity: Rusty Blade,SHIPSANITY,Stardew Valley Expanded +58067,Shipping,Shipsanity: Salal Berry,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58068,Shipping,Shipsanity: Sea Sponge,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58069,Shipping,Shipsanity: Seahorse,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58070,Shipping,Shipsanity: Seaweed Salad,SHIPSANITY,Stardew Valley Expanded +58071,Shipping,Shipsanity: Shiny Lunaloo,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58072,Shipping,Shipsanity: Shrub Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded +58073,Shipping,Shipsanity: Slime Berry,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58074,Shipping,Shipsanity: Slime Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded +58075,Shipping,Shipsanity: Rafflesia,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58076,Shipping,Shipsanity: Sports Drink,SHIPSANITY,Stardew Valley Expanded +58077,Shipping,Shipsanity: Stalk Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded +58078,Shipping,Shipsanity: Stamina Capsule,SHIPSANITY,Stardew Valley Expanded +58079,Shipping,Shipsanity: Starfish,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58080,Shipping,Shipsanity: Swirl Stone,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58081,Shipping,Shipsanity: Thistle,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58082,Shipping,Shipsanity: Torpedo Trout,"SHIPSANITY,SHIPSANITY_FISH,GINGER_ISLAND",Stardew Valley Expanded +58083,Shipping,Shipsanity: Undeadfish,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58084,Shipping,Shipsanity: Void Delight,SHIPSANITY,Stardew Valley Expanded +58085,Shipping,Shipsanity: Void Eel,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58086,Shipping,Shipsanity: Void Pebble,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58087,Shipping,Shipsanity: Void Root,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT,GINGER_ISLAND",Stardew Valley Expanded +58088,Shipping,Shipsanity: Void Salmon Sushi,SHIPSANITY,Stardew Valley Expanded +58089,Shipping,Shipsanity: Void Seed,"SHIPSANITY,GINGER_ISLAND",Stardew Valley Expanded +58090,Shipping,Shipsanity: Void Shard,SHIPSANITY,Stardew Valley Expanded +58091,Shipping,Shipsanity: Void Soul,SHIPSANITY,Stardew Valley Expanded +58092,Shipping,Shipsanity: Water Grub,"SHIPSANITY,SHIPSANITY_FISH",Stardew Valley Expanded +58093,Shipping,Shipsanity: Winter Star Rose,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded +58094,Shipping,Shipsanity: Wooden Display: Amphibian Fossil,SHIPSANITY,Archaeology +58095,Shipping,Shipsanity: Hardwood Display: Amphibian Fossil,SHIPSANITY,Archaeology +58096,Shipping,Shipsanity: Wooden Display: Anchor,SHIPSANITY,Archaeology +58097,Shipping,Shipsanity: Hardwood Display: Anchor,SHIPSANITY,Archaeology +58098,Shipping,Shipsanity: Wooden Display: Ancient Doll,SHIPSANITY,Archaeology +58099,Shipping,Shipsanity: Hardwood Display: Ancient Doll,SHIPSANITY,Archaeology +58100,Shipping,Shipsanity: Wooden Display: Ancient Drum,SHIPSANITY,Archaeology +58101,Shipping,Shipsanity: Hardwood Display: Ancient Drum,SHIPSANITY,Archaeology +58102,Shipping,Shipsanity: Wooden Display: Ancient Seed,SHIPSANITY,Archaeology +58103,Shipping,Shipsanity: Hardwood Display: Ancient Seed,SHIPSANITY,Archaeology +58104,Shipping,Shipsanity: Wooden Display: Ancient Sword,SHIPSANITY,Archaeology +58105,Shipping,Shipsanity: Hardwood Display: Ancient Sword,SHIPSANITY,Archaeology +58106,Shipping,Shipsanity: Wooden Display: Arrowhead,SHIPSANITY,Archaeology +58107,Shipping,Shipsanity: Hardwood Display: Arrowhead,SHIPSANITY,Archaeology +58108,Shipping,Shipsanity: Wooden Display: Bone Flute,SHIPSANITY,Archaeology +58109,Shipping,Shipsanity: Hardwood Display: Bone Flute,SHIPSANITY,Archaeology +58110,Shipping,Shipsanity: Wooden Display: Chewing Stick,SHIPSANITY,Archaeology +58111,Shipping,Shipsanity: Hardwood Display: Chewing Stick,SHIPSANITY,Archaeology +58112,Shipping,Shipsanity: Wooden Display: Chicken Statue,SHIPSANITY,Archaeology +58113,Shipping,Shipsanity: Hardwood Display: Chicken Statue,SHIPSANITY,Archaeology +58114,Shipping,Shipsanity: Wooden Display: Chipped Amphora,SHIPSANITY,Archaeology +58115,Shipping,Shipsanity: Hardwood Display: Chipped Amphora,SHIPSANITY,Archaeology +58116,Shipping,Shipsanity: Wooden Display: Dinosaur Egg,SHIPSANITY,Archaeology +58117,Shipping,Shipsanity: Hardwood Display: Dinosaur Egg,SHIPSANITY,Archaeology +58118,Shipping,Shipsanity: Wooden Display: Dried Starfish,SHIPSANITY,Archaeology +58119,Shipping,Shipsanity: Hardwood Display: Dried Starfish,SHIPSANITY,Archaeology +58120,Shipping,Shipsanity: Wooden Display: Dwarf Gadget,SHIPSANITY,Archaeology +58121,Shipping,Shipsanity: Hardwood Display: Dwarf Gadget,SHIPSANITY,Archaeology +58122,Shipping,Shipsanity: Wooden Display: Dwarf Scroll I,SHIPSANITY,Archaeology +58123,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll I,SHIPSANITY,Archaeology +58124,Shipping,Shipsanity: Wooden Display: Dwarf Scroll II,SHIPSANITY,Archaeology +58125,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll II,SHIPSANITY,Archaeology +58126,Shipping,Shipsanity: Wooden Display: Dwarf Scroll III,SHIPSANITY,Archaeology +58127,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll III,SHIPSANITY,Archaeology +58128,Shipping,Shipsanity: Wooden Display: Dwarf Scroll IV,SHIPSANITY,Archaeology +58129,Shipping,Shipsanity: Hardwood Display: Dwarf Scroll IV,SHIPSANITY,Archaeology +58130,Shipping,Shipsanity: Wooden Display: Dwarvish Helm,SHIPSANITY,Archaeology +58131,Shipping,Shipsanity: Hardwood Display: Dwarvish Helm,SHIPSANITY,Archaeology +58132,Shipping,Shipsanity: Wooden Display: Elvish Jewelry,SHIPSANITY,Archaeology +58133,Shipping,Shipsanity: Hardwood Display: Elvish Jewelry,SHIPSANITY,Archaeology +58134,Shipping,Shipsanity: Wooden Display: Fossilized Leg,"SHIPSANITY,GINGER_ISLAND",Archaeology +58135,Shipping,Shipsanity: Hardwood Display: Fossilized Leg,"SHIPSANITY,GINGER_ISLAND",Archaeology +58136,Shipping,Shipsanity: Wooden Display: Fossilized Ribs,"SHIPSANITY,GINGER_ISLAND",Archaeology +58137,Shipping,Shipsanity: Hardwood Display: Fossilized Ribs,"SHIPSANITY,GINGER_ISLAND",Archaeology +58138,Shipping,Shipsanity: Wooden Display: Fossilized Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology +58139,Shipping,Shipsanity: Hardwood Display: Fossilized Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology +58140,Shipping,Shipsanity: Wooden Display: Fossilized Spine,"SHIPSANITY,GINGER_ISLAND",Archaeology +58141,Shipping,Shipsanity: Hardwood Display: Fossilized Spine,"SHIPSANITY,GINGER_ISLAND",Archaeology +58142,Shipping,Shipsanity: Wooden Display: Fossilized Tail,"SHIPSANITY,GINGER_ISLAND",Archaeology +58143,Shipping,Shipsanity: Hardwood Display: Fossilized Tail,"SHIPSANITY,GINGER_ISLAND",Archaeology +58144,Shipping,Shipsanity: Wooden Display: Glass Shards,SHIPSANITY,Archaeology +58145,Shipping,Shipsanity: Hardwood Display: Glass Shards,SHIPSANITY,Archaeology +58146,Shipping,Shipsanity: Wooden Display: Golden Mask,SHIPSANITY,Archaeology +58147,Shipping,Shipsanity: Hardwood Display: Golden Mask,SHIPSANITY,Archaeology +58148,Shipping,Shipsanity: Wooden Display: Golden Relic,SHIPSANITY,Archaeology +58149,Shipping,Shipsanity: Hardwood Display: Golden Relic,SHIPSANITY,Archaeology +58150,Shipping,Shipsanity: Wooden Display: Mummified Bat,"SHIPSANITY,GINGER_ISLAND",Archaeology +58151,Shipping,Shipsanity: Hardwood Display: Mummified Bat,"SHIPSANITY,GINGER_ISLAND",Archaeology +58152,Shipping,Shipsanity: Wooden Display: Mummified Frog,"SHIPSANITY,GINGER_ISLAND",Archaeology +58153,Shipping,Shipsanity: Hardwood Display: Mummified Frog,"SHIPSANITY,GINGER_ISLAND",Archaeology +58154,Shipping,Shipsanity: Wooden Display: Nautilus Fossil,SHIPSANITY,Archaeology +58155,Shipping,Shipsanity: Hardwood Display: Nautilus Fossil,SHIPSANITY,Archaeology +58156,Shipping,Shipsanity: Wooden Display: Ornamental Fan,SHIPSANITY,Archaeology +58157,Shipping,Shipsanity: Hardwood Display: Ornamental Fan,SHIPSANITY,Archaeology +58158,Shipping,Shipsanity: Wooden Display: Palm Fossil,SHIPSANITY,Archaeology +58159,Shipping,Shipsanity: Hardwood Display: Palm Fossil,SHIPSANITY,Archaeology +58160,Shipping,Shipsanity: Wooden Display: Prehistoric Handaxe,SHIPSANITY,Archaeology +58161,Shipping,Shipsanity: Hardwood Display: Prehistoric Handaxe,SHIPSANITY,Archaeology +58162,Shipping,Shipsanity: Wooden Display: Prehistoric Rib,SHIPSANITY,Archaeology +58163,Shipping,Shipsanity: Hardwood Display: Prehistoric Rib,SHIPSANITY,Archaeology +58164,Shipping,Shipsanity: Wooden Display: Prehistoric Scapula,SHIPSANITY,Archaeology +58165,Shipping,Shipsanity: Hardwood Display: Prehistoric Scapula,SHIPSANITY,Archaeology +58166,Shipping,Shipsanity: Wooden Display: Prehistoric Skull,SHIPSANITY,Archaeology +58167,Shipping,Shipsanity: Hardwood Display: Prehistoric Skull,SHIPSANITY,Archaeology +58168,Shipping,Shipsanity: Wooden Display: Prehistoric Tibia,SHIPSANITY,Archaeology +58169,Shipping,Shipsanity: Hardwood Display: Prehistoric Tibia,SHIPSANITY,Archaeology +58170,Shipping,Shipsanity: Wooden Display: Prehistoric Tool,SHIPSANITY,Archaeology +58171,Shipping,Shipsanity: Hardwood Display: Prehistoric Tool,SHIPSANITY,Archaeology +58172,Shipping,Shipsanity: Wooden Display: Prehistoric Vertebra,SHIPSANITY,Archaeology +58173,Shipping,Shipsanity: Hardwood Display: Prehistoric Vertebra,SHIPSANITY,Archaeology +58174,Shipping,Shipsanity: Wooden Display: Rare Disc,SHIPSANITY,Archaeology +58175,Shipping,Shipsanity: Hardwood Display: Rare Disc,SHIPSANITY,Archaeology +58176,Shipping,Shipsanity: Wooden Display: Rusty Cog,SHIPSANITY,Archaeology +58177,Shipping,Shipsanity: Hardwood Display: Rusty Cog,SHIPSANITY,Archaeology +58178,Shipping,Shipsanity: Wooden Display: Rusty Spoon,SHIPSANITY,Archaeology +58179,Shipping,Shipsanity: Hardwood Display: Rusty Spoon,SHIPSANITY,Archaeology +58180,Shipping,Shipsanity: Wooden Display: Rusty Spur,SHIPSANITY,Archaeology +58181,Shipping,Shipsanity: Hardwood Display: Rusty Spur,SHIPSANITY,Archaeology +58182,Shipping,Shipsanity: Wooden Display: Skeletal Hand,SHIPSANITY,Archaeology +58183,Shipping,Shipsanity: Hardwood Display: Skeletal Hand,SHIPSANITY,Archaeology +58184,Shipping,Shipsanity: Wooden Display: Skeletal Tail,SHIPSANITY,Archaeology +58185,Shipping,Shipsanity: Hardwood Display: Skeletal Tail,SHIPSANITY,Archaeology +58186,Shipping,Shipsanity: Wooden Display: Snake Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology +58187,Shipping,Shipsanity: Hardwood Display: Snake Skull,"SHIPSANITY,GINGER_ISLAND",Archaeology +58188,Shipping,Shipsanity: Wooden Display: Snake Vertebrae,"SHIPSANITY,GINGER_ISLAND",Archaeology +58189,Shipping,Shipsanity: Hardwood Display: Snake Vertebrae,"SHIPSANITY,GINGER_ISLAND",Archaeology +58190,Shipping,Shipsanity: Wooden Display: Strange Doll (Green),SHIPSANITY,Archaeology +58191,Shipping,Shipsanity: Hardwood Display: Strange Doll (Green),SHIPSANITY,Archaeology +58192,Shipping,Shipsanity: Wooden Display: Strange Doll,SHIPSANITY,Archaeology +58193,Shipping,Shipsanity: Hardwood Display: Strange Doll,SHIPSANITY,Archaeology +58194,Shipping,Shipsanity: Wooden Display: Trilobite Fossil,SHIPSANITY,Archaeology +58195,Shipping,Shipsanity: Hardwood Display: Trilobite Fossil,SHIPSANITY,Archaeology +58196,Shipping,Shipsanity: Bone Path,SHIPSANITY,Archaeology +58197,Shipping,Shipsanity: Glass Fence,SHIPSANITY,Archaeology +58198,Shipping,Shipsanity: Glass Path,SHIPSANITY,Archaeology +58199,Shipping,Shipsanity: Hardwood Display,SHIPSANITY,Archaeology +58200,Shipping,Shipsanity: Wooden Display,SHIPSANITY,Archaeology +58201,Shipping,Shipsanity: Dwarf Gadget: Infinite Volcano Simulation,"SHIPSANITY,GINGER_ISLAND",Archaeology +58202,Shipping,Shipsanity: Water Sifter,"SHIPSANITY,DEPRECATED",Archaeology +58203,Shipping,Shipsanity: Brown Amanita,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul +58204,Shipping,Shipsanity: Swamp Herb,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul +58205,Shipping,Shipsanity: Void Mint Seeds,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58206,Shipping,Shipsanity: Vile Ancient Fruit Seeds,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58207,Shipping,Shipsanity: Void Mint Leaves,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul +58208,Shipping,Shipsanity: Vile Ancient Fruit,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul +58209,Shipping,Shipsanity: Void Minnow,"SHIPSANITY,SHIPSANITY_FISH",Distant Lands - Witch Swamp Overhaul +58210,Shipping,Shipsanity: Swamp Leech,"SHIPSANITY,SHIPSANITY_FISH",Distant Lands - Witch Swamp Overhaul +58211,Shipping,Shipsanity: Purple Algae,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58212,Shipping,Shipsanity: Giant Horsehoe Crab,"SHIPSANITY,SHIPSANITY_FISH",Distant Lands - Witch Swamp Overhaul +58213,Shipping,Shipsanity: Mushroom Kebab,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58214,Shipping,Shipsanity: Crayfish Soup,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58215,Shipping,Shipsanity: Pemmican,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58216,Shipping,Shipsanity: Void Mint Tea,SHIPSANITY,Distant Lands - Witch Swamp Overhaul +58217,Shipping,Shipsanity: Ginger Tincture,"SHIPSANITY,GINGER_ISLAND",Distant Lands - Witch Swamp Overhaul +58218,Shipping,Shipsanity: Neanderthal Limb Bones,SHIPSANITY,Boarding House and Bus Stop Extension +58219,Shipping,Shipsanity: Dinosaur Claw,SHIPSANITY,Boarding House and Bus Stop Extension +58220,Shipping,Shipsanity: Special Pumpkin Soup,SHIPSANITY,Boarding House and Bus Stop Extension +58221,Shipping,Shipsanity: Pterodactyl L Wing Bone,SHIPSANITY,Boarding House and Bus Stop Extension +58222,Shipping,Shipsanity: Dinosaur Skull,SHIPSANITY,Boarding House and Bus Stop Extension +58223,Shipping,Shipsanity: Dinosaur Tooth,SHIPSANITY,Boarding House and Bus Stop Extension +58224,Shipping,Shipsanity: Pterodactyl Egg,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Boarding House and Bus Stop Extension +58225,Shipping,Shipsanity: Pterodactyl Ribs,SHIPSANITY,Boarding House and Bus Stop Extension +58226,Shipping,Shipsanity: Dinosaur Vertebra,SHIPSANITY,Boarding House and Bus Stop Extension +58227,Shipping,Shipsanity: Neanderthal Ribs,SHIPSANITY,Boarding House and Bus Stop Extension +58228,Shipping,Shipsanity: Dinosaur Pelvis,SHIPSANITY,Boarding House and Bus Stop Extension +58229,Shipping,Shipsanity: Dinosaur Ribs,SHIPSANITY,Boarding House and Bus Stop Extension +58230,Shipping,Shipsanity: Pterodactyl Phalange,SHIPSANITY,Boarding House and Bus Stop Extension +58231,Shipping,Shipsanity: Pterodactyl Vertebra,SHIPSANITY,Boarding House and Bus Stop Extension +58232,Shipping,Shipsanity: Neanderthal Pelvis,SHIPSANITY,Boarding House and Bus Stop Extension +58233,Shipping,Shipsanity: Pterodactyl Skull,SHIPSANITY,Boarding House and Bus Stop Extension +58234,Shipping,Shipsanity: Dinosaur Femur,SHIPSANITY,Boarding House and Bus Stop Extension +58235,Shipping,Shipsanity: Pterodactyl Claw,SHIPSANITY,Boarding House and Bus Stop Extension +58236,Shipping,Shipsanity: Neanderthal Skull,SHIPSANITY,Boarding House and Bus Stop Extension +58237,Shipping,Shipsanity: Pterodactyl R Wing Bone,SHIPSANITY,Boarding House and Bus Stop Extension +58238,Shipping,Shipsanity: Scrap Rust,SHIPSANITY,Archaeology +58239,Shipping,Shipsanity: Rusty Path,SHIPSANITY,Archaeology +58241,Shipping,Shipsanity: Digger's Delight,SHIPSANITY,Archaeology +58242,Shipping,Shipsanity: Rocky Root Coffee,SHIPSANITY,Archaeology +58243,Shipping,Shipsanity: Ancient Jello,SHIPSANITY,Archaeology +58244,Shipping,Shipsanity: Bone Fence,SHIPSANITY,Archaeology +58245,Shipping,Shipsanity: Grilled Cheese,SHIPSANITY,Binning Skill +58246,Shipping,Shipsanity: Fish Casserole,SHIPSANITY,Binning Skill +58247,Shipping,Shipsanity: Snatcher Worm,SHIPSANITY,Stardew Valley Expanded +58248,Shipping,Shipsanity: Digging Like Worms,SHIPSANITY,Archaeology +59000,Farm,Read Digging Like Worms,"BOOKSANITY,BOOKSANITY_SKILL",Archaeology diff --git a/worlds/stardew_valley/data/monster_data.py b/worlds/stardew_valley/data/monster_data.py index b423fce4a3db..f5fb2aac92c3 100644 --- a/worlds/stardew_valley/data/monster_data.py +++ b/worlds/stardew_valley/data/monster_data.py @@ -1,6 +1,7 @@ from dataclasses import dataclass -from typing import List, Tuple, Dict, Set, Callable +from typing import List, Tuple, Dict, Callable +from .game_item import Source from ..mods.mod_data import ModNames from ..mods.mod_monster_locations import modded_monsters_locations from ..strings.monster_names import Monster, MonsterCategory @@ -12,7 +13,7 @@ class StardewMonster: name: str category: str - locations: Tuple[str] + locations: Tuple[str, ...] difficulty: str def __repr__(self): @@ -20,6 +21,12 @@ def __repr__(self): f" Difficulty: {self.difficulty}) |" +@dataclass(frozen=True, kw_only=True) +class MonsterSource(Source): + monsters: Tuple[StardewMonster, ...] + amount_tier: int = 0 + + slime_hutch = (Region.slime_hutch,) mines_floor_20 = (Region.mines_floor_20,) mines_floor_60 = (Region.mines_floor_60,) @@ -97,6 +104,8 @@ def register_monster_modification(mod_name: str, monster: StardewMonster, modifi armored_bug_dangerous = create_monster(Monster.armored_bug_dangerous, MonsterCategory.cave_insects, skull_cavern, Performance.good) # Requires 'Bug Killer' enchantment +metal_head = create_monster(Monster.metal_head, MonsterCategory.metal_heads, mines_floor_100, Performance.good) + duggy = create_monster(Monster.duggy, MonsterCategory.duggies, mines_floor_20, Performance.basic) duggy_dangerous = create_monster(Monster.duggy_dangerous, MonsterCategory.duggies, dangerous_mines_20, Performance.great) magma_duggy = create_monster(Monster.magma_duggy, MonsterCategory.duggies, volcano, Performance.galaxy) @@ -122,6 +131,8 @@ def register_monster_modification(mod_name: str, monster: StardewMonster, modifi magma_sprite = create_monster(Monster.magma_sprite, MonsterCategory.magma_sprites, volcano, Performance.galaxy) magma_sparker = create_monster(Monster.magma_sparker, MonsterCategory.magma_sprites, volcano_high, Performance.galaxy) +haunted_skull = create_monster(Monster.haunted_skull, MonsterCategory.none, quarry_mine, Performance.great) + register_monster_modification(ModNames.sve, shadow_brute_dangerous, update_monster_locations) register_monster_modification(ModNames.sve, shadow_sniper, update_monster_locations) register_monster_modification(ModNames.sve, shadow_shaman_dangerous, update_monster_locations) @@ -145,7 +156,7 @@ def register_monster_modification(mod_name: str, monster: StardewMonster, modifi register_monster_modification(ModNames.boarding_house, bug, update_monster_locations) -def all_monsters_by_name_given_mods(mods: Set[str]) -> Dict[str, StardewMonster]: +def all_monsters_by_name_given_content_packs(mods: set[str]) -> dict[str, StardewMonster]: monsters_by_name = {} for monster in all_monsters: current_monster = monster @@ -158,7 +169,7 @@ def all_monsters_by_name_given_mods(mods: Set[str]) -> Dict[str, StardewMonster] return monsters_by_name -def all_monsters_by_category_given_mods(mods: Set[str]) -> Dict[str, Tuple[StardewMonster, ...]]: +def all_monsters_by_category_given_content_packs(mods: set[str]) -> dict[str, Tuple[StardewMonster, ...]]: monsters_by_category = {} for monster in all_monsters: current_monster = monster diff --git a/worlds/stardew_valley/data/movies.py b/worlds/stardew_valley/data/movies.py new file mode 100644 index 000000000000..ac60ce17c299 --- /dev/null +++ b/worlds/stardew_valley/data/movies.py @@ -0,0 +1,99 @@ +from typing import List + +from ..strings.season_names import Season +from ..strings.villager_names import NPC + +movies_by_name = dict() +snacks_by_name = dict() +npc_snacks = dict() + + +def movie(movie_name: str, season: str, loving_npcs: List[str]): + movie = Movie(movie_name, season, loving_npcs) + movies_by_name[movie_name] = movie + return movie + + +def snack(snack_name: str, category: str, loving_npcs: List[str]): + snack = Snack(snack_name, category, loving_npcs) + snacks_by_name[snack_name] = snack + for npc in loving_npcs: + if npc not in npc_snacks: + npc_snacks[npc] = [] + npc_snacks[npc].append(snack) + return snack + + +class Movie: + name: str + season: str + loving_npcs: List[str] + + def __init__(self, name: str, season: str, loving_npcs: List[str]): + self.name = name + self.season = season + self.loving_npcs = loving_npcs + + +class Snack: + name: str + category: str + loving_npcs: List[str] + + def __init__(self, name: str, category: str, loving_npcs: List[str]): + self.name = name + self.category = category + self.loving_npcs = loving_npcs + + +class MovieName: + brave_sapling = movie("The Brave Little Sapling", Season.spring, [NPC.caroline, NPC.dwarf, NPC.jas, NPC.penny, NPC.sandy, NPC.vincent]) + prairie_king = movie("Journey Of The Prairie King: The Motion Picture", Season.summer, [NPC.caroline, NPC.dwarf, NPC.jas, NPC.robin, NPC.sandy, NPC.vincent]) + mysterium = movie("Mysterium", Season.fall, [NPC.abigail, NPC.dwarf, NPC.elliott, NPC.leah, NPC.sandy, NPC.sebastian, NPC.wizard]) + miracle_coldstar_ranch = movie("The Miracle At Coldstar Ranch", Season.winter, [NPC.dwarf, NPC.emily, NPC.evelyn, NPC.gus, NPC.harvey, NPC.marnie, NPC.sandy]) + natural_wonders = movie("Natural Wonders: Exploring Our Vibrant World", Season.spring, [NPC.demetrius, NPC.dwarf, NPC.jas, NPC.leo, NPC.lewis, NPC.maru, NPC.sandy]) + wumbus = movie("Wumbus", Season.summer, [NPC.alex, NPC.demetrius, NPC.dwarf, NPC.gus, NPC.jas, NPC.maru, NPC.pierre, NPC.sam, NPC.sandy, NPC.shane, NPC.vincent]) + howls_in_rain = movie("It Howls In The Rain", Season.fall, [NPC.abigail, NPC.alex, NPC.dwarf, NPC.sandy, NPC.sebastian, NPC.shane]) + zuzu_city_express = movie("The Zuzu City Express", Season.winter, [NPC.dwarf, NPC.evelyn, NPC.george, NPC.harvey, NPC.jodi, NPC.sandy]) + + +class SnackCategory: + salty = "Movie Salty Snacks" + sweet = "Movie Sweet Snacks" + drinks = "Movie Drinks" + meals = "Movie Meals" + + +class SnackName: + apple_slices = snack("Apple Slices", SnackCategory.sweet, [NPC.harvey]) + black_licorice = snack("Black Licorice", SnackCategory.sweet, [NPC.george, NPC.krobus, NPC.wizard]) + cappuccino_mousse_cake = snack("Cappuccino Mousse Cake", SnackCategory.sweet, [NPC.elliott, NPC.evelyn, NPC.gus, NPC.haley]) + chocolate_popcorn = snack("Chocolate Popcorn", SnackCategory.sweet, [NPC.jodi]) + cotton_candy = snack("Cotton Candy", SnackCategory.sweet, [NPC.penny, NPC.sandy]) + fries = snack("Fries", SnackCategory.salty, [NPC.clint]) + hummus_snack_pack = snack("Hummus Snack Pack", SnackCategory.salty, [NPC.shane]) + ice_cream_sandwich = snack("Ice Cream Sandwich", SnackCategory.sweet, [NPC.marnie]) + jasmine_tea = snack("Jasmine Tea", SnackCategory.drinks, [NPC.caroline, NPC.harvey, NPC.lewis, NPC.sebastian]) + jawbreaker = snack("Jawbreaker", SnackCategory.sweet, [NPC.vincent]) + joja_cola = snack("Joja Cola", SnackCategory.drinks, [NPC.shane]) + jojacorn = snack("JojaCorn", SnackCategory.salty, [NPC.shane]) + kale_smoothie = snack("Kale Smoothie", SnackCategory.drinks, [NPC.emily]) + nachos = snack("Nachos", SnackCategory.meals, [NPC.pam, NPC.shane]) + panzanella_salad = snack("Panzanella Salad", SnackCategory.meals, [NPC.gus, NPC.leah]) + personal_pizza = snack("Personal Pizza", SnackCategory.meals, [NPC.pierre, NPC.sam, NPC.shane]) + popcorn = snack("Popcorn", SnackCategory.salty, [NPC.demetrius, NPC.kent]) + rock_candy = snack("Rock Candy", SnackCategory.sweet, [NPC.abigail, NPC.dwarf]) + salmon_burger = snack("Salmon Burger", SnackCategory.meals, [NPC.alex, NPC.linus, NPC.willy]) + salted_peanuts = snack("Salted Peanuts", SnackCategory.salty, [NPC.robin]) + sour_slimes = snack("Sour Slimes", SnackCategory.sweet, [NPC.jas]) + star_cookie = snack("Star Cookie", SnackCategory.sweet, [NPC.evelyn, NPC.maru, NPC.wizard]) + stardrop_sorbet = snack("Stardrop Sorbet", SnackCategory.sweet, [NPC.alex, NPC.harvey, NPC.sam, NPC.sebastian, NPC.shane, NPC.abigail, NPC.emily, NPC.haley, + NPC.leah, NPC.maru, NPC.penny, NPC.caroline, NPC.clint, NPC.demetrius, NPC.dwarf, NPC.evelyn, + NPC.george, NPC.gus, NPC.jas, NPC.jodi, NPC.kent, NPC.lewis, NPC.linus, NPC.marnie, NPC.pam, + NPC.pierre, NPC.robin, NPC.sandy, NPC.vincent, NPC.willy, NPC.wizard]) + truffle_popcorn = snack("Truffle Popcorn", SnackCategory.salty, [NPC.caroline, NPC.elliott, NPC.gus]) + + +# For some unknown reason, Leo doesn't love ANY snack +npc_snacks[NPC.leo] = [] + diff --git a/worlds/stardew_valley/data/museum_data.py b/worlds/stardew_valley/data/museum_data.py index 0607261ec605..7548d208768e 100644 --- a/worlds/stardew_valley/data/museum_data.py +++ b/worlds/stardew_valley/data/museum_data.py @@ -15,7 +15,7 @@ @dataclass(frozen=True) class MuseumItem: item_name: str - locations: Tuple[str, ...] + artifact_spot_locations: Tuple[str, ...] geodes: Tuple[str, ...] monsters: Tuple[str, ...] difficulty: float @@ -23,11 +23,11 @@ class MuseumItem: @staticmethod def of(item_name: str, difficulty: float, - locations: Union[str, Tuple[str, ...]], + artifact_spot_locations: Union[str, Tuple[str, ...]], geodes: Union[str, Tuple[str, ...]], monsters: Union[str, Tuple[str, ...]]) -> MuseumItem: - if isinstance(locations, str): - locations = (locations,) + if isinstance(artifact_spot_locations, str): + artifact_spot_locations = (artifact_spot_locations,) if isinstance(geodes, str): geodes = (geodes,) @@ -35,10 +35,10 @@ def of(item_name: str, if isinstance(monsters, str): monsters = (monsters,) - return MuseumItem(item_name, locations, geodes, monsters, difficulty) + return MuseumItem(item_name, artifact_spot_locations, geodes, monsters, difficulty) def __repr__(self): - return f"{self.item_name} (Locations: {self.locations} |" \ + return f"{self.item_name} (Artifact Spot Locations: {self.artifact_spot_locations} |" \ f" Geodes: {self.geodes} |" \ f" Monsters: {self.monsters}) " @@ -53,17 +53,17 @@ def __repr__(self): def create_artifact(name: str, difficulty: float, - locations: Union[str, Tuple[str, ...]] = (), + artifact_spot_locations: Union[str, Tuple[str, ...]] = (), geodes: Union[str, Tuple[str, ...]] = (), monsters: Union[str, Tuple[str, ...]] = ()) -> MuseumItem: - artifact_item = MuseumItem.of(name, difficulty, locations, geodes, monsters) + artifact_item = MuseumItem.of(name, difficulty, artifact_spot_locations, geodes, monsters) all_museum_artifacts.append(artifact_item) all_museum_items.append(artifact_item) return artifact_item def create_mineral(name: str, - locations: Union[str, Tuple[str, ...]] = (), + artifact_spot_locations: Union[str, Tuple[str, ...]] = (), geodes: Union[str, Tuple[str, ...]] = (), monsters: Union[str, Tuple[str, ...]] = (), difficulty: Optional[float] = None) -> MuseumItem: @@ -80,7 +80,7 @@ def create_mineral(name: str, if "Fishing Chest" in geodes: difficulty += 4.3 - mineral_item = MuseumItem.of(name, difficulty, locations, geodes, monsters) + mineral_item = MuseumItem.of(name, difficulty, artifact_spot_locations, geodes, monsters) all_museum_minerals.append(mineral_item) all_museum_items.append(mineral_item) return mineral_item @@ -168,7 +168,7 @@ class Artifact: geodes=WaterChest.fishing_chest) palm_fossil = create_artifact("Palm Fossil", 10.2, (Region.dig_site, Region.desert, Region.forest, Region.beach)) - trilobite = create_artifact("Trilobite", 7.4, (Region.dig_site, Region.desert, Region.forest, Region.beach)) + trilobite = create_artifact(Fossil.trilobite, 7.4, (Region.dig_site, Region.desert, Region.forest, Region.beach)) class Mineral: @@ -267,15 +267,15 @@ class Mineral: geodes=(Geode.geode, Geode.omni)) basalt = create_mineral("Basalt", geodes=(Geode.magma, Geode.omni)) - limestone = create_mineral("Limestone", + limestone = create_mineral(Mineral.limestone, geodes=(Geode.geode, Geode.omni)) soapstone = create_mineral("Soapstone", geodes=(Geode.frozen, Geode.omni)) hematite = create_mineral("Hematite", geodes=(Geode.frozen, Geode.omni)) - mudstone = create_mineral("Mudstone", + mudstone = create_mineral(Mineral.mudstone, geodes=(Geode.geode, Geode.omni)) - obsidian = create_mineral("Obsidian", + obsidian = create_mineral(Mineral.obsidian, geodes=(Geode.magma, Geode.omni)) slate = create_mineral("Slate", geodes=(Geode.geode, Geode.omni)) fairy_stone = create_mineral("Fairy Stone", geodes=(Geode.frozen, Geode.omni)) diff --git a/worlds/stardew_valley/data/recipe_data.py b/worlds/stardew_valley/data/recipe_data.py index 667227cb9e2b..66afb90dda9b 100644 --- a/worlds/stardew_valley/data/recipe_data.py +++ b/worlds/stardew_valley/data/recipe_data.py @@ -1,6 +1,7 @@ -from typing import Dict, List, Optional +from typing import List from .recipe_source import RecipeSource, FriendshipSource, SkillSource, QueenOfSauceSource, ShopSource, StarterSource, ShopTradeSource, ShopFriendshipSource +from ..content.content_packs import ginger_island_content_pack from ..mods.mod_data import ModNames from ..strings.animal_product_names import AnimalProduct from ..strings.artisan_good_names import ArtisanGood @@ -23,15 +24,15 @@ class CookingRecipe: meal: str - ingredients: Dict[str, int] + ingredients: dict[str, int] source: RecipeSource - mod_name: Optional[str] = None + content_pack: str | None - def __init__(self, meal: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None): + def __init__(self, meal: str, ingredients: dict[str, int], source: RecipeSource, content_pack: str | None): self.meal = meal self.ingredients = ingredients self.source = source - self.mod_name = mod_name + self.content_pack = content_pack def __repr__(self): return f"{self.meal} (Source: {self.source} |" \ @@ -41,44 +42,44 @@ def __repr__(self): all_cooking_recipes: List[CookingRecipe] = [] -def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe: +def friendship_recipe(name: str, friend: str, hearts: int, ingredients: dict[str, int], /, *, content_pack: str | None = None) -> CookingRecipe: source = FriendshipSource(friend, hearts) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def friendship_and_shop_recipe(name: str, friend: str, hearts: int, region: str, price: int, ingredients: Dict[str, int], - mod_name: Optional[str] = None) -> CookingRecipe: +def friendship_and_shop_recipe(name: str, friend: str, hearts: int, region: str, price: int, ingredients: dict[str, int], + /, *, content_pack: str | None = None) -> CookingRecipe: source = ShopFriendshipSource(friend, hearts, region, price) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe: +def skill_recipe(name: str, skill: str, level: int, ingredients: dict[str, int], /, *, content_pack: str | None = None) -> CookingRecipe: source = SkillSource(skill, level) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe: +def shop_recipe(name: str, region: str, price: int, ingredients: dict[str, int], /, *, content_pack: str | None = None) -> CookingRecipe: source = ShopSource(region, price) - return create_recipe(name, ingredients, source, mod_name) + return create_recipe(name, ingredients, source, content_pack) -def shop_trade_recipe(name: str, region: str, currency: str, price: int, ingredients: Dict[str, int]) -> CookingRecipe: +def shop_trade_recipe(name: str, region: str, currency: str, price: int, ingredients: dict[str, int], /, *, content_pack: str | None = None) -> CookingRecipe: source = ShopTradeSource(region, currency, price) - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) -def queen_of_sauce_recipe(name: str, year: int, season: str, day: int, ingredients: Dict[str, int]) -> CookingRecipe: +def queen_of_sauce_recipe(name: str, year: int, season: str, day: int, ingredients: dict[str, int], /, *, content_pack: str | None = None) -> CookingRecipe: source = QueenOfSauceSource(year, season, day) - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) -def starter_recipe(name: str, ingredients: Dict[str, int]) -> CookingRecipe: +def starter_recipe(name: str, ingredients: dict[str, int], /, *, content_pack: str | None = None) -> CookingRecipe: source = StarterSource() - return create_recipe(name, ingredients, source) + return create_recipe(name, ingredients, source, content_pack) -def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None) -> CookingRecipe: - recipe = CookingRecipe(name, ingredients, source, mod_name) +def create_recipe(name: str, ingredients: dict[str, int], source: RecipeSource, content_pack: str | None = None) -> CookingRecipe: + recipe = CookingRecipe(name, ingredients, source, content_pack) all_cooking_recipes.append(recipe) return recipe @@ -88,7 +89,8 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, autumn_bounty = friendship_recipe(Meal.autumn_bounty, NPC.demetrius, 7, {Vegetable.yam: 1, Vegetable.pumpkin: 1}) baked_fish = queen_of_sauce_recipe(Meal.baked_fish, 1, Season.summer, 7, {Fish.sunfish: 1, Fish.bream: 1, Ingredient.wheat_flour: 1}) banana_pudding = shop_trade_recipe(Meal.banana_pudding, Region.island_trader, Fossil.bone_fragment, 30, - {Fruit.banana: 1, AnimalProduct.cow_milk: 1, Ingredient.sugar: 1}) + {Fruit.banana: 1, AnimalProduct.cow_milk: 1, Ingredient.sugar: 1}, + content_pack=ginger_island_content_pack.name) bean_hotpot = friendship_recipe(Meal.bean_hotpot, NPC.clint, 7, {Vegetable.green_bean: 2}) blackberry_cobbler_ingredients = {Forageable.blackberry: 2, Ingredient.sugar: 1, Ingredient.wheat_flour: 1} blackberry_cobbler_qos = queen_of_sauce_recipe(Meal.blackberry_cobbler, 2, Season.fall, 14, blackberry_cobbler_ingredients) @@ -122,7 +124,8 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, fried_egg = starter_recipe(Meal.fried_egg, {AnimalProduct.chicken_egg: 1}) fried_mushroom = friendship_recipe(Meal.fried_mushroom, NPC.demetrius, 3, {Mushroom.common: 1, Mushroom.morel: 1, Ingredient.oil: 1}) fruit_salad = queen_of_sauce_recipe(Meal.fruit_salad, 2, Season.fall, 7, {Fruit.blueberry: 1, Fruit.melon: 1, Fruit.apricot: 1}) -ginger_ale = shop_recipe(Beverage.ginger_ale, Region.volcano_dwarf_shop, 1000, {Forageable.ginger: 3, Ingredient.sugar: 1}) +ginger_ale = shop_recipe(Beverage.ginger_ale, Region.volcano_dwarf_shop, 1000, {Forageable.ginger: 3, Ingredient.sugar: 1}, + content_pack=ginger_island_content_pack.name) glazed_yams = queen_of_sauce_recipe(Meal.glazed_yams, 1, Season.fall, 21, {Vegetable.yam: 1, Ingredient.sugar: 1}) hashbrowns = queen_of_sauce_recipe(Meal.hashbrowns, 2, Season.spring, 14, {Vegetable.potato: 1, Ingredient.oil: 1}) ice_cream = friendship_recipe(Meal.ice_cream, NPC.jodi, 7, {AnimalProduct.cow_milk: 1, Ingredient.sugar: 1}) @@ -131,7 +134,8 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, lobster_bisque_qos = queen_of_sauce_recipe(Meal.lobster_bisque, 2, Season.winter, 14, lobster_bisque_ingredients) lucky_lunch = queen_of_sauce_recipe(Meal.lucky_lunch, 2, Season.spring, 28, {Fish.sea_cucumber: 1, Meal.tortilla: 1, Flower.blue_jazz: 1}) maki_roll = queen_of_sauce_recipe(Meal.maki_roll, 1, Season.summer, 21, {Fish.any: 1, WaterItem.seaweed: 1, Ingredient.rice: 1}) -mango_sticky_rice = friendship_recipe(Meal.mango_sticky_rice, NPC.leo, 7, {Fruit.mango: 1, Forageable.coconut: 1, Ingredient.rice: 1}) +mango_sticky_rice = friendship_recipe(Meal.mango_sticky_rice, NPC.leo, 7, {Fruit.mango: 1, Forageable.coconut: 1, Ingredient.rice: 1}, + content_pack=ginger_island_content_pack.name) maple_bar = queen_of_sauce_recipe(Meal.maple_bar, 2, Season.summer, 14, {ArtisanGood.maple_syrup: 1, Ingredient.sugar: 1, Ingredient.wheat_flour: 1}) miners_treat = skill_recipe(Meal.miners_treat, Skill.mining, 3, {Forageable.cave_carrot: 2, Ingredient.sugar: 1, AnimalProduct.cow_milk: 1}) moss_soup = skill_recipe(Meal.moss_soup, Skill.foraging, 3, {Material.moss: 20}) @@ -146,7 +150,7 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, pizza_qos = queen_of_sauce_recipe(Meal.pizza, 2, Season.spring, 7, pizza_ingredients) pizza_saloon = shop_recipe(Meal.pizza, Region.saloon, 150, pizza_ingredients) plum_pudding = queen_of_sauce_recipe(Meal.plum_pudding, 1, Season.winter, 7, {Forageable.wild_plum: 2, Ingredient.wheat_flour: 1, Ingredient.sugar: 1}) -poi = friendship_recipe(Meal.poi, NPC.leo, 3, {Vegetable.taro_root: 4}) +poi = friendship_recipe(Meal.poi, NPC.leo, 3, {Vegetable.taro_root: 4}, content_pack=ginger_island_content_pack.name) poppyseed_muffin = queen_of_sauce_recipe(Meal.poppyseed_muffin, 2, Season.winter, 7, {Flower.poppy: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1}) pumpkin_pie_ingredients = {Vegetable.pumpkin: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.cow_milk: 1} pumpkin_pie_qos = queen_of_sauce_recipe(Meal.pumpkin_pie, 1, Season.winter, 21, pumpkin_pie_ingredients) @@ -177,55 +181,59 @@ def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, tortilla_qos = queen_of_sauce_recipe(Meal.tortilla, 1, Season.fall, 7, tortilla_ingredients) tortilla_saloon = shop_recipe(Meal.tortilla, Region.saloon, 100, tortilla_ingredients) triple_shot_espresso = shop_recipe(Beverage.triple_shot_espresso, Region.saloon, 5000, {Beverage.coffee: 3}) -tropical_curry = shop_recipe(Meal.tropical_curry, Region.island_resort, 2000, {Forageable.coconut: 1, Fruit.pineapple: 1, Fruit.hot_pepper: 1}) +tropical_curry = shop_recipe(Meal.tropical_curry, Region.island_resort, 2000, {Forageable.coconut: 1, Fruit.pineapple: 1, Fruit.hot_pepper: 1}, + content_pack=ginger_island_content_pack.name) trout_soup = queen_of_sauce_recipe(Meal.trout_soup, 1, Season.fall, 14, {Fish.rainbow_trout: 1, WaterItem.green_algae: 1}) vegetable_medley = friendship_recipe(Meal.vegetable_medley, NPC.caroline, 7, {Vegetable.tomato: 1, Vegetable.beet: 1}) -magic_elixir = shop_recipe(ModEdible.magic_elixir, Region.adventurer_guild, 3000, {Edible.life_elixir: 1, Mushroom.purple: 1}, ModNames.magic) +magic_elixir = shop_recipe(ModEdible.magic_elixir, Region.adventurer_guild, 3000, {Edible.life_elixir: 1, Mushroom.purple: 1}, content_pack=ModNames.magic) baked_berry_oatmeal = shop_recipe(SVEMeal.baked_berry_oatmeal, SVERegion.bear_shop, 0, {Forageable.salmonberry: 15, Forageable.blackberry: 15, - Ingredient.sugar: 1, Ingredient.wheat_flour: 2}, ModNames.sve) + Ingredient.sugar: 1, Ingredient.wheat_flour: 2}, + content_pack=ModNames.sve) big_bark_burger = friendship_and_shop_recipe(SVEMeal.big_bark_burger, NPC.gus, 5, Region.saloon, 5500, - {SVEFish.puppyfish: 1, Meal.bread: 1, Ingredient.oil: 1}, ModNames.sve) + {SVEFish.puppyfish: 1, Meal.bread: 1, Ingredient.oil: 1}, content_pack=ModNames.sve) flower_cookie = shop_recipe(SVEMeal.flower_cookie, SVERegion.bear_shop, 0, {SVEForage.ferngill_primrose: 1, SVEForage.goldenrod: 1, SVEForage.winter_star_rose: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, - AnimalProduct.large_egg: 1}, ModNames.sve) -frog_legs = shop_recipe(SVEMeal.frog_legs, Region.adventurer_guild, 2000, {SVEFish.frog: 1, Ingredient.oil: 1, Ingredient.wheat_flour: 1}, ModNames.sve) + AnimalProduct.large_egg: 1}, content_pack=ModNames.sve) +frog_legs = shop_recipe(SVEMeal.frog_legs, Region.adventurer_guild, 2000, {SVEFish.frog: 1, Ingredient.oil: 1, Ingredient.wheat_flour: 1}, + content_pack=ModNames.sve) glazed_butterfish = friendship_and_shop_recipe(SVEMeal.glazed_butterfish, NPC.gus, 10, Region.saloon, 4000, - {SVEFish.butterfish: 1, Ingredient.wheat_flour: 1, Ingredient.oil: 1}, ModNames.sve) + {SVEFish.butterfish: 1, Ingredient.wheat_flour: 1, Ingredient.oil: 1}, content_pack=ModNames.sve) mixed_berry_pie = shop_recipe(SVEMeal.mixed_berry_pie, Region.saloon, 3500, {Fruit.strawberry: 6, SVEFruit.salal_berry: 6, Forageable.blackberry: 6, SVEForage.bearberry: 6, Ingredient.sugar: 1, Ingredient.wheat_flour: 1}, - ModNames.sve) + content_pack=ModNames.sve) mushroom_berry_rice = friendship_and_shop_recipe(SVEMeal.mushroom_berry_rice, ModNPC.marlon, 6, Region.adventurer_guild, 1500, - {SVEForage.poison_mushroom: 3, SVEForage.red_baneberry: 10, Ingredient.rice: 1, Ingredient.sugar: 2}, - ModNames.sve) + {SVEForage.poison_mushroom: 3, SVEForage.red_baneberry: 10, + Ingredient.rice: 1, Ingredient.sugar: 2}, content_pack=ModNames.sve) seaweed_salad = shop_recipe(SVEMeal.seaweed_salad, Region.fish_shop, 1250, {SVEWaterItem.dulse_seaweed: 2, WaterItem.seaweed: 2, Ingredient.oil: 1}, - ModNames.sve) + content_pack=ModNames.sve) void_delight = friendship_and_shop_recipe(SVEMeal.void_delight, NPC.krobus, 10, Region.sewer, 5000, - {SVEFish.void_eel: 1, Loot.void_essence: 50, Loot.solar_essence: 20}, ModNames.sve) + {SVEFish.void_eel: 1, Loot.void_essence: 50, Loot.solar_essence: 20}, content_pack=ModNames.sve) void_salmon_sushi = friendship_and_shop_recipe(SVEMeal.void_salmon_sushi, NPC.krobus, 10, Region.sewer, 5000, - {Fish.void_salmon: 1, ArtisanGood.void_mayonnaise: 1, WaterItem.seaweed: 3}, ModNames.sve) + {Fish.void_salmon: 1, ArtisanGood.void_mayonnaise: 1, WaterItem.seaweed: 3}, content_pack=ModNames.sve) mushroom_kebab = friendship_recipe(DistantLandsMeal.mushroom_kebab, ModNPC.goblin, 2, {Mushroom.chanterelle: 1, Mushroom.common: 1, - Mushroom.red: 1, Material.wood: 1}, ModNames.distant_lands) -void_mint_tea = friendship_recipe(DistantLandsMeal.void_mint_tea, ModNPC.goblin, 4, {DistantLandsCrop.void_mint: 1}, ModNames.distant_lands) + Mushroom.red: 1, Material.wood: 1}, content_pack=ModNames.distant_lands) +void_mint_tea = friendship_recipe(DistantLandsMeal.void_mint_tea, ModNPC.goblin, 4, {DistantLandsCrop.void_mint: 1}, content_pack=ModNames.distant_lands) crayfish_soup = friendship_recipe(DistantLandsMeal.crayfish_soup, ModNPC.goblin, 6, {Forageable.cave_carrot: 1, Fish.crayfish: 1, DistantLandsFish.purple_algae: 1, WaterItem.white_algae: 1}, - ModNames.distant_lands) + content_pack=ModNames.distant_lands) pemmican = friendship_recipe(DistantLandsMeal.pemmican, ModNPC.goblin, 8, {Loot.bug_meat: 1, Fish.any: 1, Forageable.salmonberry: 3, - Material.stone: 2}, ModNames.distant_lands) + Material.stone: 2}, content_pack=ModNames.distant_lands) special_pumpkin_soup = friendship_recipe(BoardingHouseMeal.special_pumpkin_soup, ModNPC.joel, 6, {Vegetable.pumpkin: 2, AnimalProduct.large_goat_milk: 1, - Vegetable.garlic: 1}, ModNames.boarding_house) + Vegetable.garlic: 1}, content_pack=ModNames.boarding_house) diggers_delight = skill_recipe(ArchaeologyMeal.diggers_delight, ModSkill.archaeology, 3, - {Forageable.cave_carrot: 2, Ingredient.sugar: 1, AnimalProduct.milk: 1}, ModNames.archaeology) + {Forageable.cave_carrot: 2, Ingredient.sugar: 1, AnimalProduct.milk: 1}, content_pack=ModNames.archaeology) rocky_root = skill_recipe(ArchaeologyMeal.rocky_root, ModSkill.archaeology, 7, {Forageable.cave_carrot: 3, Seed.coffee: 1, Material.stone: 1}, - ModNames.archaeology) + content_pack=ModNames.archaeology) ancient_jello = skill_recipe(ArchaeologyMeal.ancient_jello, ModSkill.archaeology, 9, {WaterItem.cave_jelly: 6, Ingredient.sugar: 5, AnimalProduct.egg: 1, AnimalProduct.milk: 1, Artifact.chipped_amphora: 1}, - ModNames.archaeology) + content_pack=ModNames.archaeology) -grilled_cheese = skill_recipe(TrashyMeal.grilled_cheese, ModSkill.binning, 1, {Meal.bread: 1, ArtisanGood.cheese: 1}, ModNames.binning_skill) -fish_casserole = skill_recipe(TrashyMeal.fish_casserole, ModSkill.binning, 8, {Fish.any: 1, AnimalProduct.milk: 1, Vegetable.carrot: 1}, ModNames.binning_skill) +grilled_cheese = skill_recipe(TrashyMeal.grilled_cheese, ModSkill.binning, 1, {Meal.bread: 1, ArtisanGood.cheese: 1}, content_pack=ModNames.binning_skill) +fish_casserole = skill_recipe(TrashyMeal.fish_casserole, ModSkill.binning, 8, {Fish.any: 1, AnimalProduct.milk: 1, Vegetable.carrot: 1}, + content_pack=ModNames.binning_skill) all_cooking_recipes_by_name = {recipe.meal: recipe for recipe in all_cooking_recipes} diff --git a/worlds/stardew_valley/data/recipe_source.py b/worlds/stardew_valley/data/recipe_source.py index bc8c09ee9241..617f42c9b68a 100644 --- a/worlds/stardew_valley/data/recipe_source.py +++ b/worlds/stardew_valley/data/recipe_source.py @@ -121,6 +121,17 @@ def __repr__(self): return f"ShopSource at {self.region} costing {self.price}g" +class ShopWithKnownRecipeSource(ShopSource): + recipe_required: str + + def __init__(self, region: str, price: int, recipe_required: str): + super().__init__(region, price) + self.recipe_required = recipe_required + + def __repr__(self): + return f"ShopSource at {self.region} costing {self.price}g" + + class ShopFriendshipSource(RecipeSource): friend: str hearts: int diff --git a/worlds/stardew_valley/data/requirement.py b/worlds/stardew_valley/data/requirement.py index d335527a39ef..326c2f3cce7a 100644 --- a/worlds/stardew_valley/data/requirement.py +++ b/worlds/stardew_valley/data/requirement.py @@ -1,9 +1,15 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field +from typing import Tuple from .game_item import Requirement from ..strings.tool_names import ToolMaterial +@dataclass(frozen=True) +class HasItemRequirement(Requirement): + item: str + + @dataclass(frozen=True) class BookRequirement(Requirement): book: str @@ -47,8 +53,19 @@ class QuestRequirement(Requirement): @dataclass(frozen=True) -class RelationshipRequirement(Requirement): +class MeetRequirement(Requirement): npc: str + + +@dataclass(frozen=True) +class SpecificFriendRequirement(Requirement): + npc: str + hearts: int + + +@dataclass(frozen=True) +class NumberOfFriendsRequirement(Requirement): + friends: int hearts: int @@ -60,3 +77,125 @@ class FishingRequirement(Requirement): @dataclass(frozen=True) class WalnutRequirement(Requirement): amount: int + + +@dataclass(frozen=True) +class TotalEarningsRequirement(Requirement): + amount: int + + +@dataclass(frozen=True) +class GrangeDisplayRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class EggHuntRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class FishingCompetitionRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class LuauDelightRequirementRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class MovieRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class ForgeInfinityWeaponRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class CaughtFishRequirement(Requirement): + number_fish: int + unique: bool = field(kw_only=True) + + +@dataclass(frozen=True) +class MuseumCompletionRequirement(Requirement): + number_donated: int = 95 + + +@dataclass(frozen=True) +class FullShipmentRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class BuildingRequirement(Requirement): + building: str + + +@dataclass(frozen=True) +class CookedRecipesRequirement(Requirement): + number_of_recipes: int + + +@dataclass(frozen=True) +class CraftedItemsRequirement(Requirement): + number_of_recipes: int + + +@dataclass(frozen=True) +class HelpWantedRequirement(Requirement): + number_of_quests: int + + +@dataclass(frozen=True) +class ShipOneCropRequirement(Requirement): + number: int + + +@dataclass(frozen=True) +class ReceivedRaccoonsRequirement(Requirement): + number_of_raccoons: int + + +@dataclass(frozen=True) +class PrizeMachineRequirement(Requirement): + number_of_tickets: int + + +@dataclass(frozen=True) +class AllAchievementsRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class PerfectionPercentRequirement(Requirement): + percent: int + + +@dataclass(frozen=True) +class ReadAllBooksRequirement(Requirement): + pass + + +@dataclass(frozen=True) +class MinesRequirement(Requirement): + floor: int + + +@dataclass(frozen=True) +class DangerousMinesRequirement(Requirement): + floor: int + + +@dataclass(frozen=True) +class MonsterKillRequirement(Requirement): + monsters: Tuple[str, ...] + amount: int = 1 + + +@dataclass(frozen=True) +class CatalogueRequirement(Requirement): + catalogue: str diff --git a/worlds/stardew_valley/data/secret_note_data.py b/worlds/stardew_valley/data/secret_note_data.py new file mode 100644 index 000000000000..410dd2579d28 --- /dev/null +++ b/worlds/stardew_valley/data/secret_note_data.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass +from typing import Tuple, Dict, List + +from ..strings.animal_product_names import AnimalProduct +from ..strings.artisan_good_names import ArtisanGood +from ..strings.crop_names import Vegetable, Fruit +from ..strings.flower_names import Flower +from ..strings.food_names import Meal, Beverage +from ..strings.forageable_names import Forageable +from ..strings.metal_names import Mineral, MetalBar +from ..strings.villager_names import NPC + + +class SecretNote: + note_1 = "Secret Note #1: A Page From Abigail's Diary" + note_2 = "Secret Note #2: Sam's Holiday Shopping List" + note_3 = "Secret Note #3: Leah's Perfect Dinner" + note_4 = "Secret Note #4: Maru's Greatest Invention Yet" + note_5 = "Secret Note #5: Penny gets everyone something they love" + note_6 = "Secret Note #6: Stardrop Saloon Special Orders" + note_7 = "Secret Note #7: Older Bachelors In Town" + note_8 = "Secret Note #8: To Haley And Emily" + note_9 = "Secret Note #9: Alex's Strength Training Diet" + note_10 = "Secret Note #10: Cryptic Note" + note_11 = "Secret Note #11: Marnie's Memory" + note_12 = "Secret Note #12: Good Things In Garbage Cans" + note_13 = "Secret Note #13: Junimo Plush" + note_14 = "Secret Note #14: Stone Junimo" + note_15 = "Secret Note #15: Mermaid Show" + note_16 = "Secret Note #16: Treasure Chest" + note_17 = "Secret Note #17: Green Strange Doll" + note_18 = "Secret Note #18: Yellow Strange Doll" + note_19_part_1 = "Secret Note #19: Solid Gold Lewis" + note_19_part_2 = "Secret Note #19: In Town For All To See" + note_20 = "Secret Note #20: Special Charm" + note_21 = "Secret Note #21: A Date In Nature" + note_22 = "Secret Note #22: The Mysterious Qi" + note_23 = "Secret Note #23: Strange Note" + note_24 = "Secret Note #24: M. Jasper's Book On Junimos" + note_25 = "Secret Note #25: Ornate Necklace" + note_26 = "Secret Note #26: Ancient Farming Secrets" + note_27 = "Secret Note #27: A Compendium Of My Greatest Discoveries" + + +@dataclass(frozen=True) +class RequiredGifts: + npc: str + gifts: Tuple[str, ...] + + +gift_requirements: Dict[str, List[RequiredGifts]] = { + SecretNote.note_1: [RequiredGifts(NPC.abigail, (Vegetable.pumpkin, Mineral.amethyst, Meal.chocolate_cake, Meal.spicy_eel, Meal.blackberry_cobbler,)), ], + SecretNote.note_2: [RequiredGifts(NPC.sebastian, (Mineral.frozen_tear, Meal.sashimi,)), + RequiredGifts(NPC.penny, (Mineral.emerald, Flower.poppy,)), + RequiredGifts(NPC.vincent, (Fruit.grape, Meal.cranberry_candy,)), + RequiredGifts(NPC.jodi, (Meal.crispy_bass, Meal.pancakes,)), + RequiredGifts(NPC.kent, (Meal.fiddlehead_risotto, Meal.roasted_hazelnuts,)), + RequiredGifts(NPC.sam, (Forageable.cactus_fruit, Meal.maple_bar, Meal.pizza,)), ], + SecretNote.note_3: [RequiredGifts(NPC.leah, (Meal.salad, ArtisanGood.goat_cheese, AnimalProduct.truffle, ArtisanGood.wine,)), ], + SecretNote.note_4: [RequiredGifts(NPC.maru, (MetalBar.gold, MetalBar.iridium, ArtisanGood.battery_pack, Mineral.diamond, Fruit.strawberry,)), ], + SecretNote.note_5: [RequiredGifts(NPC.pam, (Vegetable.parsnip, Meal.glazed_yams,)), + RequiredGifts(NPC.jas, (Flower.fairy_rose, Meal.plum_pudding,)), + RequiredGifts(NPC.vincent, (Meal.pink_cake, Fruit.grape,)), + RequiredGifts(NPC.george, (Forageable.leek, Meal.fried_mushroom,)), + RequiredGifts(NPC.evelyn, (Vegetable.beet, Flower.tulip,)), ], + SecretNote.note_6: [RequiredGifts(NPC.lewis, (Meal.autumn_bounty,)), + RequiredGifts(NPC.marnie, (Meal.pumpkin_pie,)), + RequiredGifts(NPC.demetrius, (Meal.bean_hotpot,)), + RequiredGifts(NPC.caroline, (Meal.fish_taco,)), ], + SecretNote.note_7: [RequiredGifts(NPC.harvey, (Beverage.coffee, ArtisanGood.pickles,)), + RequiredGifts(NPC.elliott, (Meal.crab_cakes, Fruit.pomegranate,)), + RequiredGifts(NPC.shane, (Beverage.beer, Meal.pizza, Meal.pepper_poppers,)), ], + SecretNote.note_8: [RequiredGifts(NPC.haley, (Meal.pink_cake, Flower.sunflower,)), + RequiredGifts(NPC.emily, (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.jade, Mineral.ruby, Mineral.topaz, AnimalProduct.wool,)), ], + SecretNote.note_9: [RequiredGifts(NPC.alex, (Meal.complete_breakfast, Meal.salmon_dinner,)), ], +} diff --git a/worlds/stardew_valley/data/shirt_data.py b/worlds/stardew_valley/data/shirt_data.py new file mode 100644 index 000000000000..178ea79456a9 --- /dev/null +++ b/worlds/stardew_valley/data/shirt_data.py @@ -0,0 +1,33 @@ +from typing import List + +from worlds.stardew_valley.strings.animal_product_names import AnimalProduct +from worlds.stardew_valley.strings.forageable_names import Forageable + +all_shirts = [] +all_considered_shirts = [] + + +class Shirt: + name: str + required_items: List[str] + + def __init__(self, name: str, items: List[str]): + self.name = name + self.required_items = items + + +# consider_in_logic exists as a temporary measure because I don't feel like writing out the logic for every single shirt at this stage, +# and I only need some of them for the meme bundle +def shirt(name: str, items: str | List[str], consider_in_logic: bool = True) -> Shirt: + if isinstance(items, str): + items = [items] + new_shirt = Shirt(name, items) + all_shirts.append(new_shirt) + if consider_in_logic: + all_considered_shirts.append(new_shirt) + return new_shirt + + +class Shirts: + vacation = shirt("Vacation Shirt", Forageable.coconut) + green_jacket = shirt("Green Jacket Shirt", AnimalProduct.duck_egg) diff --git a/worlds/stardew_valley/data/shop.py b/worlds/stardew_valley/data/shop.py index 3700ee8978df..8ac5fd9e91ec 100644 --- a/worlds/stardew_valley/data/shop.py +++ b/worlds/stardew_valley/data/shop.py @@ -1,21 +1,23 @@ +from collections.abc import Iterable from dataclasses import dataclass -from typing import Tuple, Optional -from .game_item import Source +from .game_item import Source, Requirement +from ..strings.currency_names import Currency from ..strings.season_names import Season -ItemPrice = Tuple[int, str] +ItemPrice = tuple[int, str] @dataclass(frozen=True, kw_only=True) class ShopSource(Source): shop_region: str - money_price: Optional[int] = None - items_price: Optional[Tuple[ItemPrice, ...]] = None - seasons: Tuple[str, ...] = Season.all + price: int | None = None + items_price: tuple[ItemPrice, ...] | None = None + seasons: tuple[str, ...] = Season.all + currency: str = Currency.money def __post_init__(self): - assert self.money_price is not None or self.items_price is not None, "At least money price or items price need to be defined." + assert self.price is not None or self.items_price is not None, "At least money price or items price need to be defined." assert self.items_price is None or all(isinstance(p, tuple) for p in self.items_price), "Items price should be a tuple." @@ -37,3 +39,13 @@ class PrizeMachineSource(Source): @dataclass(frozen=True, kw_only=True) class FishingTreasureChestSource(Source): amount: int + + +@dataclass(frozen=True, kw_only=True) +class HatMouseSource(Source): + price: int | None = None + unlock_requirements: tuple[Requirement, ...] | None = None + + @property + def all_requirements(self) -> Iterable[Requirement]: + return self.other_requirements + (self.unlock_requirements or ()) diff --git a/worlds/stardew_valley/data/villagers_data.py b/worlds/stardew_valley/data/villagers_data.py index 70fb110ffbae..c1468c86ab84 100644 --- a/worlds/stardew_valley/data/villagers_data.py +++ b/worlds/stardew_valley/data/villagers_data.py @@ -4,7 +4,8 @@ from ..mods.mod_data import ModNames from ..strings.food_names import Beverage from ..strings.generic_names import Generic -from ..strings.region_names import Region, SVERegion, AlectoRegion, BoardingHouseRegion, LaceyRegion, LogicRegion +from ..strings.metal_names import Mineral, Fossil +from ..strings.region_names import Region, SVERegion, AlectoRegion, BoardingHouseRegion, LaceyRegion, LogicRegion, RileyRegion from ..strings.season_names import Season from ..strings.villager_names import NPC, ModNPC @@ -43,6 +44,16 @@ def __repr__(self): island = (Region.island_east,) secret_woods = (Region.secret_woods,) wizard_tower = (Region.wizard_tower,) +sam_house = (Region.sam_house,) +pierre_shop = (Region.pierre_store,) +haley_house = (Region.haley_house,) +saloon = (Region.saloon,) +blacksmith = (Region.blacksmith,) +trailer = (Region.trailer,) +leah_house = (Region.leah_house,) +linus_tent = (Region.tent,) +lewis_house = (Region.mayor_house,) +fish_shop = (Region.fish_shop,) # Stardew Valley Expanded Locations adventurer = (Region.adventurer_guild,) @@ -53,6 +64,8 @@ def __repr__(self): jojamart = (Region.jojamart,) railroad = (Region.railroad,) junimo = (SVERegion.junimo_woods,) +olivia_house = (SVERegion.jenkins_residence,) +andy_house = (SVERegion.fairhaven_farm,) # Stray Locations witch_swamp = (Region.witch_swamp,) @@ -60,6 +73,7 @@ def __repr__(self): hat_house = (LaceyRegion.hat_house,) the_lost_valley = (BoardingHouseRegion.the_lost_valley,) boarding_house = (BoardingHouseRegion.boarding_house_first,) +riley_house = (RileyRegion.riley_house,) golden_pumpkin = ("Golden Pumpkin",) # magic_rock_candy = ("Magic Rock Candy",) @@ -90,7 +104,7 @@ def __repr__(self): tigerseye = ("Tigerseye",) sam_loves = cactus_fruit + maple_bar + pizza + tigerseye frozen_tear = ("Frozen Tear",) -obsidian = ("Obsidian",) +obsidian = (Mineral.obsidian,) # pumpkin_soup = ("Pumpkin Soup",) # sashimi = ("Sashimi",) void_egg = ("Void Egg",) @@ -252,7 +266,7 @@ def __repr__(self): kale = ("Kale",) muscle_remedy = ("Muscle Remedy",) vegetable_medley = ("Vegetable Medley",) -trilobite = ("Trilobite",) +trilobite = (Fossil.trilobite,) golden_mask = ("Golden Mask",) rainbow_shell = ("Rainbow Shell",) blue_jazz = ("Blue Jazz",) @@ -361,39 +375,39 @@ def villager(name: str, bachelor: bool, locations: Tuple[str, ...], birthday: st return Villager(name, bachelor, locations, birthday, gifts, available, mod_name) -josh = villager(NPC.alex, True, town + alex_house, Season.summer, universal_loves + complete_breakfast + salmon_dinner, True) -elliott = villager(NPC.elliott, True, town + beach + elliott_house, Season.fall, universal_loves + elliott_loves, True) -harvey = villager(NPC.harvey, True, town + hospital, Season.winter, universal_loves + harvey_loves, True) -sam = villager(NPC.sam, True, town, Season.summer, universal_loves + sam_loves, True) +josh = villager(NPC.alex, True, alex_house, Season.summer, universal_loves + complete_breakfast + salmon_dinner, True) +elliott = villager(NPC.elliott, True, beach + elliott_house, Season.fall, universal_loves + elliott_loves, True) +harvey = villager(NPC.harvey, True, hospital, Season.winter, universal_loves + harvey_loves, True) +sam = villager(NPC.sam, True, sam_house, Season.summer, universal_loves + sam_loves, True) sebastian = villager(NPC.sebastian, True, carpenter, Season.winter, universal_loves + sebastian_loves, True) shane = villager(NPC.shane, True, ranch, Season.spring, universal_loves + shane_loves, True) -abigail = villager(NPC.abigail, True, town, Season.fall, universal_loves + abigail_loves, True) -emily = villager(NPC.emily, True, town, Season.spring, universal_loves + emily_loves, True) -haley = villager(NPC.haley, True, town, Season.spring, universal_loves_no_prismatic_shard + haley_loves, True) -leah = villager(NPC.leah, True, forest, Season.winter, universal_loves + leah_loves, True) -maru = villager(NPC.maru, True, carpenter + hospital + town, Season.summer, universal_loves + maru_loves, True) -penny = villager(NPC.penny, True, town, Season.fall, universal_loves_no_rabbit_foot + penny_loves, True) -caroline = villager(NPC.caroline, False, town, Season.winter, universal_loves + caroline_loves, True) -clint = villager(NPC.clint, False, town, Season.winter, universal_loves + clint_loves, True) +abigail = villager(NPC.abigail, True, pierre_shop, Season.fall, universal_loves + abigail_loves, True) +emily = villager(NPC.emily, True, haley_house, Season.spring, universal_loves + emily_loves, True) +haley = villager(NPC.haley, True, haley_house, Season.spring, universal_loves_no_prismatic_shard + haley_loves, True) +leah = villager(NPC.leah, True, forest + leah_house, Season.winter, universal_loves + leah_loves, True) +maru = villager(NPC.maru, True, carpenter + hospital, Season.summer, universal_loves + maru_loves, True) +penny = villager(NPC.penny, True, trailer, Season.fall, universal_loves_no_rabbit_foot + penny_loves, True) +caroline = villager(NPC.caroline, False, pierre_shop, Season.winter, universal_loves + caroline_loves, True) +clint = villager(NPC.clint, False, blacksmith, Season.winter, universal_loves + clint_loves, True) demetrius = villager(NPC.demetrius, False, carpenter, Season.summer, universal_loves + demetrius_loves, True) dwarf = villager(NPC.dwarf, False, mines_dwarf_shop, Season.summer, universal_loves + dwarf_loves, False) -evelyn = villager(NPC.evelyn, False, town, Season.winter, universal_loves + evelyn_loves, True) -george = villager(NPC.george, False, town, Season.fall, universal_loves + george_loves, True) -gus = villager(NPC.gus, False, town, Season.summer, universal_loves + gus_loves, True) +evelyn = villager(NPC.evelyn, False, alex_house, Season.winter, universal_loves + evelyn_loves, True) +george = villager(NPC.george, False, alex_house, Season.fall, universal_loves + george_loves, True) +gus = villager(NPC.gus, False, saloon, Season.summer, universal_loves + gus_loves, True) jas = villager(NPC.jas, False, ranch, Season.summer, universal_loves + jas_loves, True) -jodi = villager(NPC.jodi, False, town, Season.fall, universal_loves + jodi_loves, True) -kent = villager(NPC.kent, False, town, Season.spring, universal_loves + kent_loves, False) +jodi = villager(NPC.jodi, False, sam_house, Season.fall, universal_loves + jodi_loves, True) +kent = villager(NPC.kent, False, sam_house, Season.spring, universal_loves + kent_loves, False) krobus = villager(NPC.krobus, False, sewers, Season.winter, universal_loves + krobus_loves, False) leo = villager(NPC.leo, False, island, Season.summer, universal_loves + leo_loves, False) -lewis = villager(NPC.lewis, False, town, Season.spring, universal_loves + lewis_loves, True) -linus = villager(NPC.linus, False, mountain, Season.winter, universal_loves + linus_loves, True) +lewis = villager(NPC.lewis, False, lewis_house, Season.spring, universal_loves + lewis_loves, True) +linus = villager(NPC.linus, False, mountain + linus_tent, Season.winter, universal_loves + linus_loves, True) marnie = villager(NPC.marnie, False, ranch, Season.fall, universal_loves + marnie_loves, True) -pam = villager(NPC.pam, False, town, Season.spring, universal_loves + pam_loves, True) -pierre = villager(NPC.pierre, False, town, Season.spring, universal_loves + pierre_loves, True) +pam = villager(NPC.pam, False, trailer, Season.spring, universal_loves + pam_loves, True) +pierre = villager(NPC.pierre, False, pierre_shop, Season.spring, universal_loves + pierre_loves, True) robin = villager(NPC.robin, False, carpenter, Season.fall, universal_loves + robin_loves, True) sandy = villager(NPC.sandy, False, oasis, Season.fall, universal_loves + sandy_loves, False) -vincent = villager(NPC.vincent, False, town, Season.spring, universal_loves + vincent_loves, True) -willy = villager(NPC.willy, False, beach, Season.summer, universal_loves + willy_loves, True) +vincent = villager(NPC.vincent, False, sam_house, Season.spring, universal_loves + vincent_loves, True) +willy = villager(NPC.willy, False, beach + fish_shop, Season.summer, universal_loves + willy_loves, True) wizard = villager(NPC.wizard, False, wizard_tower, Season.winter, universal_loves + wizard_loves, True) # Custom NPCs @@ -401,13 +415,13 @@ def villager(name: str, bachelor: bool, locations: Tuple[str, ...], birthday: st ayeisha = villager(ModNPC.ayeisha, False, town, Season.summer, universal_loves + ayeisha_loves, True, ModNames.ayeisha) delores = villager(ModNPC.delores, True, forest, Season.winter, universal_loves + delores_loves, True, ModNames.delores) eugene = villager(ModNPC.eugene, True, forest, Season.spring, universal_loves + eugene_loves, True, ModNames.eugene) -jasper = villager(ModNPC.jasper, True, town, Season.fall, universal_loves + jasper_loves, True, ModNames.jasper) +jasper = villager(ModNPC.jasper, True, museum, Season.fall, universal_loves + jasper_loves, True, ModNames.jasper) juna = villager(ModNPC.juna, False, forest, Season.summer, universal_loves + juna_loves, True, ModNames.juna) kitty = villager(ModNPC.mr_ginger, False, forest, Season.summer, universal_loves + mister_ginger_loves, True, ModNames.ginger) -shiko = villager(ModNPC.shiko, True, town, Season.winter, universal_loves + shiko_loves, True, ModNames.shiko) -wellwick = villager(ModNPC.wellwick, True, forest, Season.winter, universal_loves + wellwick_loves, True, ModNames.wellwick) +shiko = villager(ModNPC.shiko, True, saloon, Season.winter, universal_loves + shiko_loves, True, ModNames.shiko) +wellwick = villager(ModNPC.wellwick, True, wizard_tower, Season.winter, universal_loves + wellwick_loves, True, ModNames.wellwick) yoba = villager(ModNPC.yoba, False, secret_woods, Season.spring, universal_loves + yoba_loves, False, ModNames.yoba) -riley = villager(ModNPC.riley, True, town, Season.spring, universal_loves, True, ModNames.riley) +riley = villager(ModNPC.riley, True, riley_house, Season.spring, universal_loves, True, ModNames.riley) zic = villager(ModNPC.goblin, False, witch_swamp, Season.fall, void_mayonnaise, False, ModNames.distant_lands) alecto = villager(ModNPC.alecto, False, witch_attic, Generic.any, universal_loves, False, ModNames.alecto) lacey = villager(ModNPC.lacey, True, forest, Season.spring, universal_loves, True, ModNames.lacey) @@ -420,15 +434,15 @@ def villager(name: str, bachelor: bool, locations: Tuple[str, ...], birthday: st # SVE Villagers claire = villager(ModNPC.claire, True, town + jojamart, Season.fall, universal_loves + claire_loves, True, ModNames.sve) lance = villager(ModNPC.lance, True, adventurer + highlands + island, Season.spring, lance_loves, False, ModNames.sve) -mommy = villager(ModNPC.olivia, True, town, Season.spring, universal_loves_no_rabbit_foot + olivia_loves, True, ModNames.sve) +mommy = villager(ModNPC.olivia, True, olivia_house, Season.spring, universal_loves_no_rabbit_foot + olivia_loves, True, ModNames.sve) sophia = villager(ModNPC.sophia, True, bluemoon, Season.winter, universal_loves_no_rabbit_foot + sophia_loves, True, ModNames.sve) -victor = villager(ModNPC.victor, True, town, Season.summer, universal_loves + victor_loves, True, ModNames.sve) -andy = villager(ModNPC.andy, False, forest, Season.spring, universal_loves + andy_loves, True, ModNames.sve) +victor = villager(ModNPC.victor, True, olivia_house, Season.summer, universal_loves + victor_loves, True, ModNames.sve) +andy = villager(ModNPC.andy, False, andy_house, Season.spring, universal_loves + andy_loves, True, ModNames.sve) apples = villager(ModNPC.apples, False, aurora + junimo, Generic.any, starfruit, False, ModNames.sve) gunther = villager(ModNPC.gunther, False, museum, Season.winter, universal_loves + gunther_loves, True, ModNames.sve) martin = villager(ModNPC.martin, False, town + jojamart, Season.summer, universal_loves + martin_loves, True, ModNames.sve) marlon = villager(ModNPC.marlon, False, adventurer, Season.winter, universal_loves + marlon_loves, False, ModNames.sve) -morgan = villager(ModNPC.morgan, False, forest, Season.fall, universal_loves_no_rabbit_foot + morgan_loves, False, ModNames.sve) +morgan = villager(ModNPC.morgan, False, wizard_tower, Season.fall, universal_loves_no_rabbit_foot + morgan_loves, False, ModNames.sve) scarlett = villager(ModNPC.scarlett, False, bluemoon, Season.summer, universal_loves + scarlett_loves, False, ModNames.sve) susan = villager(ModNPC.susan, False, railroad, Season.fall, universal_loves + susan_loves, False, ModNames.sve) morris = villager(ModNPC.morris, False, jojamart, Season.spring, universal_loves + morris_loves, True, ModNames.sve) diff --git a/worlds/stardew_valley/docs/en_Stardew Valley.md b/worlds/stardew_valley/docs/en_Stardew Valley.md index 62755dad798d..8c6abe00ebff 100644 --- a/worlds/stardew_valley/docs/en_Stardew Valley.md +++ b/worlds/stardew_valley/docs/en_Stardew Valley.md @@ -36,10 +36,12 @@ The player can choose from a number of goals, using their YAML options. - Become a [Craft Master](https://stardewvalleywiki.com/Crafting) by crafting every item - Earn the title of [Legend](https://stardewvalleywiki.com/Gold) by earning 10 000 000g - Solve the [Mystery of the Stardrops](https://stardewvalleywiki.com/Stardrop) by finding every stardrop +- Find and wear every [Hat](https://stardewvalleywiki.com/Hats) in the game. +- Find and eat every item in the game. - Finish 100% of your randomizer slot with Allsanity: Complete every check in your slot - Achieve [Perfection](https://stardewvalleywiki.com/Perfection) in your save file -The following goals [Community Center, Master Angler, Protector of the Valley, Full Shipment and Gourmet Chef] will adapt +The following goals [Community Center, Master Angler, Protector of the Valley, Full Shipment, Gourmet Chef and Mad Hatter] will adapt to other options in your slots, and are therefore customizable in duration and difficulty. For example, if you set "Fishsanity" to "Exclude Legendaries", and pick the Master Angler goal, you will not need to catch the legendaries to complete the goal. @@ -78,6 +80,12 @@ There also are a number of location checks that are optional, and individual pla - [Shipsanity](https://stardewvalleywiki.com/Shipping): Shipping individual items - [Booksanity](https://stardewvalleywiki.com/Books): Reading individual books - [Walnutsanity](https://stardewvalleywiki.com/Golden_Walnut): Collecting Walnuts on Ginger Island +- [Hatsanity](https://stardewvalleywiki.com/Hats): Wear individual Hats +- [Secretsanity](https://stardewvalleywiki.com/Secrets): Find secrets and easter eggs +- [Moviesanity](https://stardewvalleywiki.com/Movie_Theater): Watch movies with villagers and optionally share snacks with them +- Eatsanity: Eat every item in the game + +And more! ## Which items can be in another player's world? @@ -138,12 +146,11 @@ This means that, for these specific mods, if you decide to include them in your with the assumption that you will install and play with these mods. The multiworld will contain related items and locations for these mods, the specifics will vary from mod to mod -[Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md) +[Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/7.x.x/Documentation/Supported%20Mods.md) List of supported mods: - General - - [Stardew Valley Expanded](https://www.nexusmods.com/stardewvalley/mods/3753) - [Skull Cavern Elevator](https://www.nexusmods.com/stardewvalley/mods/963) - [Bigger Backpack](https://www.nexusmods.com/stardewvalley/mods/1845) - [Tractor Mod](https://www.nexusmods.com/stardewvalley/mods/1401) @@ -169,5 +176,5 @@ Some of these mods might need a patch mod to tie the randomizer with the mod. Th You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-term plans to support that feature. -You can, however, send Stardew Valley objects as gifts from one Stardew Player to another Stardew , or a player in another game that supports gifting, using +You can, however, send Stardew Valley objects as gifts from one Stardew Player to another Stardew, or a player in another game that supports gifting, using in-game Joja Prime delivery, for a fee. This exclusive feature can be turned off if you don't want to send and receive gifts. diff --git a/worlds/stardew_valley/docs/setup_en.md b/worlds/stardew_valley/docs/setup_en.md index 801bf345e916..da838db8166b 100644 --- a/worlds/stardew_valley/docs/setup_en.md +++ b/worlds/stardew_valley/docs/setup_en.md @@ -3,35 +3,49 @@ ## Required Software - Stardew Valley 1.6 on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/)) -- SMAPI ([Mod loader for Stardew Valley](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files)) -- [StardewArchipelago Mod Release 6.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) - - It is important to use a mod release of version 6.x.x to play seeds that have been generated here. Later releases +- SMAPI ([Mod loader for Stardew Valley](https://smapi.io/) ([Nexus](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files) | [Github](https://github.com/Pathoschild/SMAPI/releases)) +- [StardewArchipelago Mod Release 7.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) + - It is important to use a mod release of version 7.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet. ## Optional Software - Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) - * (Only for the TextClient) + * This provides access to various built-in tools, text client, and local generation and hosting +- [Universal Tracker](https://github.com/FarisTheAncient/Archipelago/releases?q=Tracker) + * If you installed Archipelago, you can use a custom Stardew Tracker that depends on Universal Tracker. - Other Stardew Valley Mods [Nexus Mods](https://www.nexusmods.com/stardewvalley) - * There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md) + * There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/7.x.x/Documentation/Supported%20Mods.md) that you can add to your yaml to include them with the Archipelago randomization * It is **not** recommended to further mod Stardew Valley with unsupported mods, although it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs. * The more unsupported mods you have, and the bigger they are, the more likely things are to break. -## Configuring your YAML file +## Configuring your Multiworld -### What is a YAML file and why do I need one? +### Customizing my options + +You can customize your options by visiting the [Stardew Valley Player Options Page](/games/Stardew%20Valley/player-options) + +From there, you can customize all your options, or use a preset. Then, you can either Generate a single player game, or export a yaml file. + +### What is a YAML file and do I need one? + +A yaml file serves to configure your game in the multiworld. If you play a solo game, you can skip it entirely. + +If you intend to play in a multiworld, you will need to provide your yaml file to the person who is hosting the multiworld. See the guide on setting up a basic YAML at the Archipelago setup guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) -### Where do I get a YAML file? +### Creating a room -You can customize your options by visiting the [Stardew Valley Player Options Page](/games/Stardew%20Valley/player-options) +If you hosted your own game, you can now click "Create Room" and your multiworld will be ready for play! ## Joining a MultiWorld Game +Note that even if you play a solo game, you still connect to a server to play. Hosting on archipelago.gg is free and the easiest option, but you can also host it locally. + ### Installing the mod - Install [SMAPI](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files) by following the instructions on the mod page @@ -41,7 +55,7 @@ your Stardew Valley "Mods" folder - Otherwise just launch "StardewModdingAPI.exe" in your installation folder directly - Stardew Valley should launch itself alongside a console which allows you to read mod information and interact with some of them. -### Connect to the MultiServer +### Connect to the Multiworld Launch Stardew Valley with SMAPI. Once you have reached the Stardew Valley title screen, create a new farm. @@ -51,7 +65,7 @@ On the new character creation page, you will see 3 new fields, used to link your You can customize your farm and character as much as desired. -The Server text box needs to have both the address and the port, and your slotname is the name specified in your yaml +The Server text box needs to have both the address and the port, and your slotname is the name specified in your options. You can also see the name on the room page. `archipelago.gg:38281` @@ -62,13 +76,7 @@ The password is optional. Your game will connect automatically to Archipelago, and reconnect automatically when loading the save, later. You will never need to enter this information again for this character, unless your room changes its ip or port. -If the room's ip or port **does** change, you can follow these instructions to modify the connection information of your save file -- Launch modded Stardew Valley -- While **on the main menu** of the game, enter the follow command **in the SMAPI console**: -- `connect_override ip:port slot password` -- Example: `connect_override archipelago.gg:38281 StardewPlayer` -- Load your save game. The new connection information will be used, instead of the saved one -- Play a full day, sleep, and save the game. This connection information will overwrite the previous one and become permanent. +If the room's ip or port **does** change, you will be prompted, after the connection fails, to enter new values and try again. ### Interacting with the MultiWorld from in-game @@ -82,14 +90,14 @@ Lastly, you can also run Archipelago commands `!help` from the in game chat box, items, or check missing locations. It is important to note that the Stardew Valley chat is fairly limited in its capabilities. For example, it doesn't allow -scrolling up to see history that has been pushed off screen. The SMAPI console running alonside your game will have the +scrolling up to see history that has been pushed off screen. The SMAPI console running alongside your game will have the full history as well and may be better suited to read older messages. -For a better chat experience, you can also use the official Archipelago Text Client, altough it will not allow you to run +For a better chat experience, you can also use the official Archipelago Text Client, although it will not allow you to run Stardew-exclusive commands. ### Playing with supported mods -See the [Supported mods documentation](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md) +See the [Supported mods documentation](https://github.com/agilbert1412/StardewArchipelago/blob/7.x.x/Documentation/Supported%20Mods.md) ### Multiplayer diff --git a/worlds/stardew_valley/docs/setup_fr.md b/worlds/stardew_valley/docs/setup_fr.md index d7866c0b162c..bdaa96719cc6 100644 --- a/worlds/stardew_valley/docs/setup_fr.md +++ b/worlds/stardew_valley/docs/setup_fr.md @@ -4,14 +4,14 @@ - Stardew Valley 1.6 sur PC (Recommandé: [Steam](https://store.steampowered.com/app/413150/Stardew_Valley/)) - SMAPI ([Mod loader pour Stardew Valley](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files)) -- [StardewArchipelago Version 6.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) - - Il est important d'utiliser une release en 6.x.x pour jouer sur des seeds générées ici. Les versions ultérieures peuvent uniquement être utilisées pour des release ultérieures du générateur de mondes, qui ne sont pas encore hébergées sur archipelago.gg +- [StardewArchipelago Version 7.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) + - Il est important d'utiliser une release en 7.x.x pour jouer sur des seeds générées ici. Les versions ultérieures peuvent uniquement être utilisées pour des release ultérieures du générateur de mondes, qui ne sont pas encore hébergées sur archipelago.gg ## Logiciels optionnels - Launcher Archipelago à partir de la [page des versions d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) - (Uniquement pour le client textuel) -- Autres [mods supportés](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md) que vous pouvez ajouter au yaml pour les inclure dans la randomization d'Archipelago +- Autres [mods supportés](https://github.com/agilbert1412/StardewArchipelago/blob/7.x.x/Documentation/Supported%20Mods.md) que vous pouvez ajouter au yaml pour les inclure dans la randomization d'Archipelago - Il n'est **pas** recommandé de modder Stardew Valley avec des mods non supportés, même s'il est possible de le faire. Les interactions entre mods peuvent être imprévisibles, et aucune aide ne sera fournie pour les bugs qui y sont liés. @@ -80,7 +80,7 @@ Pour une meilleure expérience avec le chat, vous pouvez aussi utiliser le clien ### Jouer avec des mods supportés -Voir la [documentation des mods supportés](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md) (en Anglais). +Voir la [documentation des mods supportés](https://github.com/agilbert1412/StardewArchipelago/blob/7.x.x/Documentation/Supported%20Mods.md) (en Anglais). ### Multijoueur diff --git a/worlds/stardew_valley/items/__init__.py b/worlds/stardew_valley/items/__init__.py index ddf5e69f68be..bee83ad8a46b 100644 --- a/worlds/stardew_valley/items/__init__.py +++ b/worlds/stardew_valley/items/__init__.py @@ -1 +1,4 @@ +from .early_items import setup_early_items +from .fillers import generate_filler_choice_pool +from .item_creation import create_items from .item_data import item_table, ItemData, Group, items_by_group, load_item_csv diff --git a/worlds/stardew_valley/early_items.py b/worlds/stardew_valley/items/early_items.py similarity index 71% rename from worlds/stardew_valley/early_items.py rename to worlds/stardew_valley/items/early_items.py index 550a92b4451b..0a0050cf2bdc 100644 --- a/worlds/stardew_valley/early_items.py +++ b/worlds/stardew_valley/items/early_items.py @@ -1,15 +1,17 @@ from random import Random -from . import options as stardew_options -from .content import StardewContent -from .strings.ap_names.ap_weapon_names import APWeapon -from .strings.ap_names.transport_names import Transportation -from .strings.building_names import Building -from .strings.region_names import Region -from .strings.season_names import Season -from .strings.skill_names import Skill -from .strings.tv_channel_names import Channel -from .strings.wallet_item_names import Wallet +from .. import options as stardew_options +from ..content import StardewContent +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..strings.ap_names.ap_option_names import ChefsanityOptionName, StartWithoutOptionName +from ..strings.ap_names.ap_weapon_names import APWeapon +from ..strings.ap_names.transport_names import Transportation +from ..strings.building_names import Building +from ..strings.region_names import Region +from ..strings.season_names import Season +from ..strings.skill_names import Skill +from ..strings.tv_channel_names import Channel +from ..strings.wallet_item_names import Wallet early_candidate_rate = 4 always_early_candidates = [Region.greenhouse, Transportation.desert_obelisk, Wallet.rusty_key] @@ -47,7 +49,7 @@ def setup_early_items(multiworld, options: stardew_options.StardewValleyOptions, if options.special_order_locations & stardew_options.SpecialOrderLocations.option_board: early_candidates.append("Special Order Board") - if options.cooksanity != stardew_options.Cooksanity.option_none or options.chefsanity & stardew_options.Chefsanity.option_queen_of_sauce: + if options.cooksanity != stardew_options.Cooksanity.option_none or ChefsanityOptionName.queen_of_sauce in options.chefsanity: early_candidates.append(Channel.queen_of_sauce) if options.craftsanity != stardew_options.Craftsanity.option_none: @@ -58,8 +60,9 @@ def setup_early_items(multiworld, options: stardew_options.StardewValleyOptions, else: early_candidates.append(APWeapon.sword) - if options.exclude_ginger_island == stardew_options.ExcludeGingerIsland.option_false: + if content.is_enabled(ginger_island_content_pack): early_candidates.append(Transportation.island_obelisk) + early_candidates.append(Transportation.boat_repair) if options.walnutsanity.value: early_candidates.append("Island North Turtle") @@ -68,6 +71,14 @@ def setup_early_items(multiworld, options: stardew_options.StardewValleyOptions, if options.museumsanity != stardew_options.Museumsanity.option_none or options.shipsanity >= stardew_options.Shipsanity.option_full_shipment: early_candidates.append(Wallet.metal_detector) + if StartWithoutOptionName.landslide in options.start_without: + early_candidates.append("Landslide Removed") + + if StartWithoutOptionName.community_center in options.start_without: + early_candidates.append("Forest Magic") + early_candidates.append("Community Center Key") + early_candidates.append("Wizard Invitation") + early_forced.extend(random.sample(early_candidates, len(early_candidates) // early_candidate_rate)) for item_name in early_forced: diff --git a/worlds/stardew_valley/items/fillers.py b/worlds/stardew_valley/items/fillers.py new file mode 100644 index 000000000000..76c10e2ad47c --- /dev/null +++ b/worlds/stardew_valley/items/fillers.py @@ -0,0 +1,181 @@ +from random import Random +from typing import List + +from BaseClasses import Item, ItemClassification +from .filters import remove_excluded, remove_limited_amount_resource_packs, remove_already_included +from .item_data import items_by_group, Group, ItemData, StardewItemFactory, item_table +from ..content.game_content import StardewContent +from ..options import StardewValleyOptions, FestivalLocations +from ..strings.ap_names.ap_option_names import BuffOptionName, AllowedFillerOptionName +from ..strings.ap_names.buff_names import Buff + +AllowedFillerTypesMap = { + AllowedFillerOptionName.farming: Group.FILLER_FARMING, + AllowedFillerOptionName.fishing: Group.FILLER_FISHING, + AllowedFillerOptionName.fruit_trees: Group.FILLER_FRUIT_TREES, + AllowedFillerOptionName.food: Group.FILLER_FOOD, + AllowedFillerOptionName.buff_food: Group.FILLER_BUFF_FOOD, + AllowedFillerOptionName.consumables: Group.FILLER_CONSUMABLE, + AllowedFillerOptionName.machines: Group.FILLER_MACHINE, + AllowedFillerOptionName.storage: Group.FILLER_STORAGE, + AllowedFillerOptionName.quality_of_life: Group.FILLER_QUALITY_OF_LIFE, + AllowedFillerOptionName.materials: Group.FILLER_MATERIALS, + AllowedFillerOptionName.currencies: Group.FILLER_CURRENCY, + AllowedFillerOptionName.money: Group.FILLER_MONEY, + AllowedFillerOptionName.hats: Group.FILLER_HAT, + AllowedFillerOptionName.decorations: Group.FILLER_DECORATION, + AllowedFillerOptionName.rings: Group.FILLER_RING, +} + + +def generate_filler_choice_pool(options: StardewValleyOptions, content: StardewContent) -> list[str]: + available_filler = get_all_filler_items(options) + available_filler = remove_excluded(available_filler, content, options) + available_filler = remove_limited_amount_resource_packs(available_filler) + + return [item.name for item in available_filler] + + +def get_all_filler_items(options: StardewValleyOptions) -> list[ItemData]: + all_filler_items = [] + allowed_filler_types = sorted(list(options.allowed_filler_items.value)) + for allowed_filler_type in allowed_filler_types: + allowed_filler_group = AllowedFillerTypesMap[allowed_filler_type] + all_filler_items.extend([pack for pack in items_by_group[allowed_filler_group]]) + all_filler_items.extend(items_by_group[Group.TRASH]) + all_filler_items.extend(get_player_buffs(options)) + all_filler_items.extend(get_traps(options)) + + return all_filler_items + + +def get_filler_weights(options: StardewValleyOptions, all_filler_packs: list[ItemData]) -> list[int]: + weights = [] + for filler in all_filler_packs: + if filler.name in options.trap_distribution: + num = options.trap_distribution[filler.name] + else: + num = options.trap_distribution.default_weight + weights.append(num) + return weights + + +def generate_unique_filler_items(item_factory: StardewItemFactory, content: StardewContent, options: StardewValleyOptions, random: Random, + available_item_slots: int) -> list[Item]: + items = create_filler_festival_rewards(item_factory, content, options) + + if len(items) > available_item_slots: + items = random.sample(items, available_item_slots) + return items + + +def create_filler_festival_rewards(item_factory: StardewItemFactory, content: StardewContent, options: StardewValleyOptions) -> list[Item]: + if options.festival_locations == FestivalLocations.option_disabled: + return [] + filler_rewards = [item for item in items_by_group[Group.FESTIVAL] if item.classification == ItemClassification.filler] + filler_rewards = remove_excluded(filler_rewards, content, options) + return [item_factory(item) for item in filler_rewards] + + +def generate_resource_packs_and_traps(item_factory: StardewItemFactory, + options: StardewValleyOptions, + content: StardewContent, + random: Random, + already_added_items: list[Item], + available_item_slots: int) -> list[Item]: + def filler_factory(item: ItemData): + # Yes some fillers are progression. We add multiple fruit tree saplings for instance. + if ItemClassification.progression in item.classification: + return item_factory(item, + classification_pre_fill=ItemClassification.filler, + classification_post_fill=ItemClassification.progression_skip_balancing) + return item_factory(item) + + already_added_items_names = {item.name for item in already_added_items} + + priority_fillers = get_priority_fillers(options) + priority_fillers = remove_excluded(priority_fillers, content, options) + priority_fillers = remove_already_included(priority_fillers, already_added_items_names) + + if available_item_slots < len(priority_fillers): + return [filler_factory(priority_filler) + for priority_filler in random.sample(priority_fillers, available_item_slots)] + + chosen_fillers = [] + chosen_fillers.extend([filler_factory(priority_filler) for priority_filler in priority_fillers]) + available_item_slots -= len(priority_fillers) + already_added_items_names |= {priority_item.name for priority_item in priority_fillers} + + all_fillers = get_all_filler_items(options) + all_fillers = remove_excluded(all_fillers, content, options) + all_fillers = remove_already_included(all_fillers, already_added_items_names) + + filler_weights = get_filler_weights(options, all_fillers) + + while available_item_slots > 0: + resource_pack = random.choices(all_fillers, weights=filler_weights, k=1)[0] + + exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups + while exactly_2 and available_item_slots == 1: + # We roll another filler since there is no place for the second one + resource_pack = random.choices(all_fillers, weights=filler_weights, k=1)[0] + exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups + + chosen_fillers.append(filler_factory(resource_pack)) + available_item_slots -= 1 + if exactly_2: + chosen_fillers.append(filler_factory(resource_pack)) + available_item_slots -= 1 + + if resource_pack.has_limited_amount(): + index = all_fillers.index(resource_pack) + all_fillers.pop(index) + filler_weights.pop(index) + + return chosen_fillers + + +def get_priority_fillers(options: StardewValleyOptions) -> list[ItemData]: + buffs = get_player_buffs(options) + traps = get_traps(options) + + return buffs + traps + + +def get_player_buffs(options: StardewValleyOptions) -> List[ItemData]: + buff_option = options.enabled_filler_buffs + allowed_buffs = [] + if BuffOptionName.luck in buff_option: + allowed_buffs.append(item_table[Buff.luck]) + if BuffOptionName.damage in buff_option: + allowed_buffs.append(item_table[Buff.damage]) + if BuffOptionName.defense in buff_option: + allowed_buffs.append(item_table[Buff.defense]) + if BuffOptionName.immunity in buff_option: + allowed_buffs.append(item_table[Buff.immunity]) + if BuffOptionName.health in buff_option: + allowed_buffs.append(item_table[Buff.health]) + if BuffOptionName.energy in buff_option: + allowed_buffs.append(item_table[Buff.energy]) + if BuffOptionName.bite in buff_option: + allowed_buffs.append(item_table[Buff.bite_rate]) + if BuffOptionName.fish_trap in buff_option: + allowed_buffs.append(item_table[Buff.fish_trap]) + if BuffOptionName.fishing_bar in buff_option: + allowed_buffs.append(item_table[Buff.fishing_bar]) + if BuffOptionName.quality in buff_option: + allowed_buffs.append(item_table[Buff.quality]) + if BuffOptionName.glow in buff_option: + allowed_buffs.append(item_table[Buff.glow]) + return allowed_buffs + + +def get_traps(options: StardewValleyOptions) -> list[ItemData]: + if not options.trap_difficulty.include_traps(): + return [] + + return [ + trap + for trap in items_by_group[Group.TRAP] + if trap.name not in options.trap_distribution or options.trap_distribution[trap.name] > 0 + ] diff --git a/worlds/stardew_valley/items/filters.py b/worlds/stardew_valley/items/filters.py new file mode 100644 index 000000000000..cab7f6ff8ddd --- /dev/null +++ b/worlds/stardew_valley/items/filters.py @@ -0,0 +1,31 @@ +from typing import Iterable + +from .item_data import ItemData, Group, FILLER_GROUPS +from ..content import StardewContent +from ..options import StardewValleyOptions, Hatsanity + + +def remove_excluded(items: Iterable[ItemData], content: StardewContent, options: StardewValleyOptions) -> list[ItemData]: + filtered_items = [ + item for item in items + if Group.DEPRECATED not in item.groups and content.are_all_enabled(item.content_packs) + ] + if options.hatsanity == Hatsanity.preset_none: + return filtered_items + return [item for item in filtered_items if Group.FILLER_HAT not in item.groups] + + +def remove_limited_amount_resource_packs(packs: Iterable[ItemData]) -> list[ItemData]: + return [ + resource_pack + for resource_pack in packs + if not resource_pack.has_limited_amount() + ] + + +def remove_already_included(items: Iterable[ItemData], already_added_items: set[str]) -> list[ItemData]: + return [ + item + for item in items + if item.name not in already_added_items or (item.has_any_group(*FILLER_GROUPS, Group.TRAP) and Group.MAXIMUM_ONE not in item.groups) + ] diff --git a/worlds/stardew_valley/items/item_creation.py b/worlds/stardew_valley/items/item_creation.py index 6928ca8b66cb..d7541a608f36 100644 --- a/worlds/stardew_valley/items/item_creation.py +++ b/worlds/stardew_valley/items/item_creation.py @@ -1,30 +1,34 @@ import logging from random import Random -from typing import List, Set +from typing import List from BaseClasses import Item, ItemClassification +from .fillers import generate_resource_packs_and_traps, generate_unique_filler_items +from .filters import remove_excluded from .item_data import StardewItemFactory, items_by_group, Group, item_table, ItemData from ..content.feature import friendsanity from ..content.game_content import StardewContent +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..content.vanilla.qi_board import qi_board_content_pack from ..data.game_item import ItemTag from ..mods.mod_data import ModNames -from ..options import StardewValleyOptions, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Museumsanity, \ +from ..options import StardewValleyOptions, FestivalLocations, SpecialOrderLocations, SeasonRandomization, Museumsanity, \ ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \ - Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, EnabledFillerBuffs, TrapDifficulty -from ..strings.ap_names.ap_option_names import BuffOptionName, WalnutsanityOptionName + Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, Moviesanity +from ..options.options import IncludeEndgameLocations, Friendsanity +from ..strings.ap_names.ap_option_names import WalnutsanityOptionName, SecretsanityOptionName, EatsanityOptionName, ChefsanityOptionName, StartWithoutOptionName from ..strings.ap_names.ap_weapon_names import APWeapon from ..strings.ap_names.buff_names import Buff -from ..strings.ap_names.community_upgrade_names import CommunityUpgrade +from ..strings.ap_names.community_upgrade_names import CommunityUpgrade, Bookseller from ..strings.ap_names.mods.mod_items import SVEQuestItem +from ..strings.backpack_tiers import Backpack +from ..strings.building_names import Building from ..strings.currency_names import Currency from ..strings.tool_names import Tool from ..strings.wallet_item_names import Wallet logger = logging.getLogger(__name__) -def get_too_many_items_error_message(locations_count: int, items_count: int) -> str: - return f"There should be at least as many locations [{locations_count}] as there are mandatory items [{items_count}]" - def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item], options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]: @@ -38,21 +42,25 @@ def create_items(item_factory: StardewItemFactory, locations_count: int, items_t items += unique_items logger.debug(f"Created {len(unique_items)} unique items") - unique_filler_items = create_unique_filler_items(item_factory, options, random, locations_count - len(items)) + unique_filler_items = generate_unique_filler_items(item_factory, content, options, random, locations_count - len(items)) items += unique_filler_items logger.debug(f"Created {len(unique_filler_items)} unique filler items") - resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items + items_to_exclude, locations_count - len(items)) + resource_pack_items = generate_resource_packs_and_traps(item_factory, options, content, random, items + items_to_exclude, locations_count - len(items)) items += resource_pack_items logger.debug(f"Created {len(resource_pack_items)} resource packs") return items -def remove_items(items_to_remove, items): - for item in items_to_remove: - if item in items: - items.remove(item) +def remove_items(items_to_remove: List[Item], items: List[Item]): + for item_to_remove in items_to_remove: + for i, item_candidate in enumerate(items): + if item_to_remove != item_candidate: + continue + if item_to_remove.classification == item_candidate.classification and item_to_remove.advancement == item_candidate.advancement: + items.pop(i) + break def remove_items_if_no_room_for_them(unique_items: List[Item], locations_count: int, random: Random): @@ -66,7 +74,9 @@ def remove_items_if_no_room_for_them(unique_items: List[Item], locations_count: removable_items = [item for item in unique_items if not item.classification & ItemClassification.progression] else: logger.debug(f"Player has more items than locations, trying to remove {number_of_items_to_remove} random filler items") - assert len(removable_items) >= number_of_items_to_remove, get_too_many_items_error_message(locations_count, len(unique_items)) + count = len(unique_items) + assert len(removable_items) >= number_of_items_to_remove, \ + f"There should be at least as many locations [{locations_count}] as there are mandatory items [{count - len(removable_items)}]" items_to_remove = random.sample(removable_items, number_of_items_to_remove) remove_items(items_to_remove, unique_items) @@ -79,20 +89,20 @@ def create_unique_items(item_factory: StardewItemFactory, options: StardewValley create_raccoons(item_factory, options, items) items.append(item_factory(Wallet.metal_detector)) # Always offer at least one metal detector - create_backpack_items(item_factory, options, items) - create_weapons(item_factory, options, items) + create_backpack_items(item_factory, options, content, items) + create_weapons(item_factory, options, content, items) items.append(item_factory("Skull Key")) - create_elevators(item_factory, options, items) + create_elevators(item_factory, options, content, items) create_tools(item_factory, content, items) create_skills(item_factory, content, items) - create_wizard_buildings(item_factory, options, items) - create_carpenter_buildings(item_factory, content, items) + create_wizard_buildings(item_factory, options, content, items) + create_carpenter_buildings(item_factory, options, content, items) items.append(item_factory("Railroad Boulder Removed")) items.append(item_factory(CommunityUpgrade.fruit_bats)) items.append(item_factory(CommunityUpgrade.mushroom_boxes)) items.append(item_factory("Beach Bridge")) create_tv_channels(item_factory, options, items) - create_quest_rewards(item_factory, options, items) + create_quest_rewards(item_factory, options, content, items) create_stardrops(item_factory, options, content, items) create_museum_items(item_factory, options, items) create_arcade_machine_items(item_factory, options, items) @@ -104,84 +114,105 @@ def create_unique_items(item_factory: StardewItemFactory, options: StardewValley create_friendsanity_items(item_factory, options, content, items, random) create_festival_rewards(item_factory, options, items) create_special_order_board_rewards(item_factory, options, items) - create_special_order_qi_rewards(item_factory, options, items) - create_walnuts(item_factory, options, items) - create_walnut_purchase_rewards(item_factory, options, items) - create_crafting_recipes(item_factory, options, items) - create_cooking_recipes(item_factory, options, items) + create_special_order_qi_rewards(item_factory, options, content, items) + create_walnuts(item_factory, options, content, items) + create_walnut_purchase_rewards(item_factory, content, items) + create_crafting_recipes(item_factory, options, content, items) + create_cooking_recipes(item_factory, options, content, items) create_shipsanity_items(item_factory, options, items) - create_booksanity_items(item_factory, content, items) + create_booksanity_items(item_factory, options, content, items) + create_movie_items(item_factory, options, items) + create_secrets_items(item_factory, content, options, items) + create_eatsanity_enzyme_items(item_factory, options, items) + create_endgame_locations_items(item_factory, options, items) + create_goal_items(item_factory, options, items) items.append(item_factory("Golden Egg")) items.append(item_factory(CommunityUpgrade.mr_qi_plane_ride)) - create_sve_special_items(item_factory, options, items) - create_magic_mod_spells(item_factory, options, items) - create_deepwoods_pendants(item_factory, options, items) - create_archaeology_items(item_factory, options, items) + items.append(item_factory(Wallet.mens_locker_key)) + items.append(item_factory(Wallet.womens_locker_key)) + + create_sve_special_items(item_factory, content, items) + create_magic_mod_spells(item_factory, content, items) + create_deepwoods_pendants(item_factory, content, items) + create_archaeology_items(item_factory, content, items) return items def create_raccoons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): number_progressive_raccoons = 9 + if options.bundle_per_room.value < 0: + number_progressive_raccoons -= options.bundle_per_room.value if options.quest_locations.has_no_story_quests(): number_progressive_raccoons = number_progressive_raccoons - 1 items.extend(item_factory(item) for item in [CommunityUpgrade.raccoon] * number_progressive_raccoons) -def create_backpack_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if (options.backpack_progression == BackpackProgression.option_progressive or - options.backpack_progression == BackpackProgression.option_early_progressive): - items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2) - if ModNames.big_backpack in options.mods: - items.append(item_factory("Progressive Backpack")) +def create_backpack_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + if options.backpack_progression == BackpackProgression.option_vanilla: + return + num_per_tier = options.backpack_size.count_per_tier() + backpack_tier_names = Backpack.get_purchasable_tiers(ModNames.big_backpack in content.registered_packs, StartWithoutOptionName.backpack in options.start_without) + num_backpacks = len(backpack_tier_names) * num_per_tier + + items.extend(item_factory(item) for item in ["Progressive Backpack"] * num_backpacks) -def create_weapons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - weapons = weapons_count(options) +def create_footwear(item_factory: StardewItemFactory, number: int) -> List[Item]: + return [item_factory(APWeapon.footwear) for _ in range(number)] + + +def create_weapons(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + weapons = weapons_count(content) items.extend(item_factory(item) for item in [APWeapon.slingshot] * 2) monstersanity = options.monstersanity + + ring_classification = ItemClassification.progression if options.bundle_randomization == BundleRandomization.option_meme else ItemClassification.useful + rings_items = [item for item in items_by_group[Group.FILLER_RING] if item.classification is not ItemClassification.filler] + if monstersanity == Monstersanity.option_none: # Without monstersanity, might not be enough checks to split the weapons items.extend(item_factory(item) for item in [APWeapon.weapon] * weapons) - items.extend(item_factory(item) for item in [APWeapon.footwear] * 3) # 1-2 | 3-4 | 6-7-8 + items.extend(create_footwear(item_factory, 3)) # 1-2 | 3-4 | 6-7-8 + rings_items = [item for item in rings_items if item.classification is ItemClassification.progression] + items.extend(item_factory(item, classification_pre_fill=ring_classification) for item in rings_items) return items.extend(item_factory(item) for item in [APWeapon.sword] * weapons) items.extend(item_factory(item) for item in [APWeapon.club] * weapons) items.extend(item_factory(item) for item in [APWeapon.dagger] * weapons) - items.extend(item_factory(item) for item in [APWeapon.footwear] * 4) # 1-2 | 3-4 | 6-7-8 | 11-13 + items.extend(create_footwear(item_factory, 4)) # 1-2 | 3-4 | 6-7-8 | 11-13 + + items.extend(item_factory(item, classification_pre_fill=ring_classification) for item in rings_items) if monstersanity == Monstersanity.option_goals or monstersanity == Monstersanity.option_one_per_category or \ monstersanity == Monstersanity.option_short_goals or monstersanity == Monstersanity.option_very_short_goals: return - if options.exclude_ginger_island == ExcludeGingerIsland.option_true: - rings_items = [item for item in items_by_group[Group.RING] if item.classification is not ItemClassification.filler] - else: - rings_items = [item for item in items_by_group[Group.RING]] - items.extend(item_factory(item) for item in rings_items) -def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): +def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): if options.elevator_progression == ElevatorProgression.option_vanilla: return items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24]) - if ModNames.deepwoods in options.mods: + if ModNames.deepwoods in content.registered_packs: items.extend([item_factory(item) for item in ["Progressive Woods Obelisk Sigils"] * 10]) - if ModNames.skull_cavern_elevator in options.mods: + if ModNames.skull_cavern_elevator in content.registered_packs: items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8]) def create_tools(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): tool_progression = content.features.tool_progression for tool, count in tool_progression.tool_distribution.items(): - item = item_table[tool_progression.to_progressive_item(tool)] + item = item_table[tool_progression.to_progressive_item_name(tool)] # Trash can is only used in tool upgrade logic, so the last trash can is not progression because it basically does not unlock anything. if tool == Tool.trash_can: count -= 1 - items.append(item_factory(item, ItemClassification.useful)) + items.append(item_factory(item, + classification_pre_fill=ItemClassification.useful, + classification_post_fill=ItemClassification.progression_skip_balancing)) items.extend([item_factory(item) for _ in range(count)]) @@ -201,49 +232,53 @@ def create_skills(item_factory: StardewItemFactory, content: StardewContent, ite items.append(item_factory(Wallet.mastery_of_the_five_ways)) -def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - useless_buildings_classification = ItemClassification.progression_skip_balancing if world_is_perfection(options) else ItemClassification.useful - items.append(item_factory("Earth Obelisk", useless_buildings_classification)) - items.append(item_factory("Water Obelisk", useless_buildings_classification)) +def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + useful_buildings_classification = ItemClassification.progression_skip_balancing if goal_is_perfection(options) else ItemClassification.useful + items.append(item_factory("Earth Obelisk", classification_pre_fill=useful_buildings_classification)) + items.append(item_factory("Water Obelisk", classification_pre_fill=useful_buildings_classification)) items.append(item_factory("Desert Obelisk")) items.append(item_factory("Junimo Hut")) - items.append(item_factory("Gold Clock", useless_buildings_classification)) - if options.exclude_ginger_island == ExcludeGingerIsland.option_false: + items.append(item_factory("Gold Clock", classification_pre_fill=useful_buildings_classification)) + if content.is_enabled(ginger_island_content_pack): items.append(item_factory("Island Obelisk")) - if ModNames.deepwoods in options.mods: + if content.is_enabled(ModNames.deepwoods): items.append(item_factory("Woods Obelisk")) -def create_carpenter_buildings(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): +def create_carpenter_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): building_progression = content.features.building_progression if not building_progression.is_progressive: return for building in content.farm_buildings.values(): item_name, _ = building_progression.to_progressive_item(building.name) - items.append(item_factory(item_name)) + if item_name in [Building.stable, Building.well] and options.bundle_randomization != BundleRandomization.option_meme: + items.append(item_factory(item_name, classification_pre_fill=ItemClassification.useful)) + else: + items.append(item_factory(item_name)) -def create_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - create_special_quest_rewards(item_factory, options, items) +def create_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + create_special_quest_rewards(item_factory, options, content, items) create_help_wanted_quest_rewards(item_factory, options, items) - create_quest_rewards_sve(item_factory, options, items) + create_quest_rewards_sve(item_factory, options, content, items) -def create_special_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): +def create_special_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): if options.quest_locations.has_no_story_quests(): return # items.append(item_factory("Adventurer's Guild")) # Now unlocked always! items.append(item_factory(Wallet.club_card)) items.append(item_factory(Wallet.magnifying_glass)) - if ModNames.sve in options.mods: + items.append(item_factory(Wallet.magic_ink)) + items.append(item_factory(Wallet.iridium_snake_milk)) + if ModNames.sve in content.registered_packs: items.append(item_factory(Wallet.bears_knowledge)) else: - items.append(item_factory(Wallet.bears_knowledge, ItemClassification.useful)) # Not necessary outside of SVE - items.append(item_factory(Wallet.iridium_snake_milk)) + items.append(item_factory(Wallet.bears_knowledge, classification_pre_fill=ItemClassification.useful)) # Not necessary outside of SVE items.append(item_factory("Dark Talisman")) - if options.exclude_ginger_island == ExcludeGingerIsland.option_false: + if content.is_enabled(ginger_island_content_pack): items.append(item_factory("Fairy Dust Recipe")) @@ -259,22 +294,24 @@ def create_help_wanted_quest_rewards(item_factory: StardewItemFactory, options: def create_stardrops(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): stardrops_classification = get_stardrop_classification(options) - items.append(item_factory("Stardrop", stardrops_classification)) # The Mines level 100 - items.append(item_factory("Stardrop", stardrops_classification)) # Old Master Cannoli - items.append(item_factory("Stardrop", stardrops_classification)) # Krobus Stardrop + items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # The Mines level 100 + items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Krobus Stardrop if content.features.fishsanity.is_enabled: - items.append(item_factory("Stardrop", stardrops_classification)) # Master Angler Stardrop - if ModNames.deepwoods in options.mods: - items.append(item_factory("Stardrop", stardrops_classification)) # Petting the Unicorn + items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Master Angler Stardrop + if ModNames.deepwoods in content.registered_packs: + items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Petting the Unicorn if content.features.friendsanity.is_enabled: - items.append(item_factory("Stardrop", stardrops_classification)) # Spouse Stardrop + items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Spouse Stardrop + if SecretsanityOptionName.easy in options.secretsanity: + # Always Progression as a different secret requires a stardrop + items.append(item_factory("Stardrop", classification_pre_fill=ItemClassification.progression)) # Old Master Cannoli. def create_museum_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): items.append(item_factory(Wallet.rusty_key)) items.append(item_factory(Wallet.dwarvish_translation_guide)) items.append(item_factory("Ancient Seeds Recipe")) - items.append(item_factory("Stardrop", get_stardrop_classification(options))) + items.append(item_factory("Stardrop", classification_pre_fill=get_stardrop_classification(options))) if options.museumsanity == Museumsanity.option_none: return items.extend(item_factory(item) for item in ["Magic Rock Candy"] * 10) @@ -292,13 +329,10 @@ def create_friendsanity_items(item_factory: StardewItemFactory, options: Stardew item_name = friendsanity.to_item_name(villager.name) for _ in content.features.friendsanity.get_randomized_hearts(villager): - items.append(item_factory(item_name, ItemClassification.progression)) - - need_pet = options.goal == Goal.option_grandpa_evaluation - pet_item_classification = ItemClassification.progression_skip_balancing if need_pet else ItemClassification.useful + items.append(item_factory(item_name)) for _ in content.features.friendsanity.get_pet_randomized_hearts(): - items.append(item_factory(friendsanity.pet_heart_item_name, pet_item_classification)) + items.append(item_factory(friendsanity.pet_heart_item_name, classification_pre_fill=ItemClassification.progression_skip_balancing)) def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random): @@ -359,12 +393,12 @@ def create_festival_rewards(item_factory: StardewItemFactory, options: StardewVa return festival_rewards = [item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler] - items.extend([*festival_rewards, item_factory("Stardrop", get_stardrop_classification(options))]) + items.extend([*festival_rewards, item_factory("Stardrop", classification_pre_fill=get_stardrop_classification(options))]) -def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): +def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): walnutsanity = options.walnutsanity - if options.exclude_ginger_island == ExcludeGingerIsland.option_true or walnutsanity == Walnutsanity.preset_none: + if not content.is_enabled(ginger_island_content_pack) or walnutsanity == Walnutsanity.preset_none: return # Give baseline walnuts just to be nice @@ -391,8 +425,8 @@ def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptio items.extend([item_factory(item) for item in ["5 Golden Walnuts"] * num_penta_walnuts]) -def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if options.exclude_ginger_island == ExcludeGingerIsland.option_true: +def create_walnut_purchase_rewards(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): + if not content.is_enabled(ginger_island_content_pack): return items.extend([item_factory("Boat Repair"), @@ -420,15 +454,15 @@ def special_order_board_item_classification(item: ItemData, need_all_recipes: bo return ItemClassification.useful -def create_special_order_qi_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if options.exclude_ginger_island == ExcludeGingerIsland.option_true: +def create_special_order_qi_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + if not content.is_enabled(ginger_island_content_pack): return qi_gem_rewards = [] if options.bundle_randomization >= BundleRandomization.option_remixed: qi_gem_rewards.append("15 Qi Gems") qi_gem_rewards.append("15 Qi Gems") - if options.special_order_locations & SpecialOrderLocations.value_qi: + if content.is_enabled(qi_board_content_pack): qi_gem_rewards.extend(["100 Qi Gems", "10 Qi Gems", "40 Qi Gems", "25 Qi Gems", "25 Qi Gems", "40 Qi Gems", "20 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems"]) @@ -443,33 +477,33 @@ def create_tv_channels(item_factory: StardewItemFactory, options: StardewValleyO items.extend([item_factory(item) for item in channels]) -def create_crafting_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): +def create_crafting_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): has_craftsanity = options.craftsanity == Craftsanity.option_all crafting_recipes = [] crafting_recipes.extend([recipe for recipe in items_by_group[Group.QI_CRAFTING_RECIPE]]) if has_craftsanity: crafting_recipes.extend([recipe for recipe in items_by_group[Group.CRAFTSANITY]]) - crafting_recipes = remove_excluded_items(crafting_recipes, options) + crafting_recipes = remove_excluded(crafting_recipes, content, options) items.extend([item_factory(item) for item in crafting_recipes]) -def create_cooking_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): +def create_cooking_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): chefsanity = options.chefsanity - if chefsanity == Chefsanity.option_none: + if chefsanity == Chefsanity.preset_none: return chefsanity_recipes_by_name = {recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_STARTER]} # Dictionary to not make duplicates - if chefsanity & Chefsanity.option_queen_of_sauce: + if ChefsanityOptionName.queen_of_sauce in chefsanity: chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_QOS]}) - if chefsanity & Chefsanity.option_purchases: + if ChefsanityOptionName.purchases in chefsanity: chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_PURCHASE]}) - if chefsanity & Chefsanity.option_friendship: + if ChefsanityOptionName.friendship in chefsanity: chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_FRIENDSHIP]}) - if chefsanity & Chefsanity.option_skills: + if ChefsanityOptionName.skills in chefsanity: chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_SKILL]}) - filtered_chefsanity_recipes = remove_excluded_items(list(chefsanity_recipes_by_name.values()), options) + filtered_chefsanity_recipes = remove_excluded(list(chefsanity_recipes_by_name.values()), content, options) items.extend([item_factory(item) for item in filtered_chefsanity_recipes]) @@ -481,14 +515,95 @@ def create_shipsanity_items(item_factory: StardewItemFactory, options: StardewVa items.append(item_factory(Wallet.metal_detector)) -def create_booksanity_items(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): +def create_booksanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + create_bookseller_items(item_factory, options, content, items) booksanity = content.features.booksanity if not booksanity.is_enabled: return items.extend(item_factory(item_table[booksanity.to_item_name(book.name)]) for book in content.find_tagged_items(ItemTag.BOOK_POWER)) progressive_lost_book = item_table[booksanity.progressive_lost_book] - items.extend(item_factory(progressive_lost_book) for _ in content.features.booksanity.get_randomized_lost_books()) + # We do -1 here because the first lost book spawns freely in the museum + num_lost_books = len([book for book in content.features.booksanity.get_randomized_lost_books()]) - 1 + items.extend(item_factory(progressive_lost_book) for _ in range(num_lost_books)) + + +def create_bookseller_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + needs_books = options.shipsanity == Shipsanity.option_everything or content.features.booksanity.is_enabled or content.features.hatsanity.is_enabled + book_items = [] + book_items.extend(item_factory(item_table[Bookseller.days]) for _ in range(4 if needs_books else 1)) + if not needs_books: + book_items.extend(item_factory(item_table[Bookseller.days], classification_pre_fill=ItemClassification.filler) for _ in range(3)) + book_items.extend(item_factory(item_table[Bookseller.stock_rare_books]) for _ in range(2 if needs_books else 1)) + book_items.append(item_factory(item_table[Bookseller.stock_permanent_books])) + book_items.append(item_factory(item_table[Bookseller.stock_experience_books])) + if needs_books: + book_items.extend(item_factory(item_table[Bookseller.stock_experience_books], classification_pre_fill=ItemClassification.filler) for _ in range(2)) + + items.extend(book_items) + + +def create_movie_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): + if options.moviesanity.value < Moviesanity.option_all_movies: + return + + items.extend(item_factory(item) for item in items_by_group[Group.MOVIESANITY]) + + +def create_secrets_items(item_factory: StardewItemFactory, content: StardewContent, options: StardewValleyOptions, items: List[Item]): + if not options.secretsanity: + return + secret_items = [] + if SecretsanityOptionName.easy in options.secretsanity: + secret_items.extend(items_by_group[Group.EASY_SECRET]) + # if SecretsanityOptionName.fishing in options.secretsanity: + # secret_items.extend(items_by_group[Group.FISHING_SECRET]) # There are no longer any of these items, they are now part of FILLER_DECORATION + # if SecretsanityOptionName.difficult in options.secretsanity: + # items.extend(item_factory(item) for item in items_by_group[Group.DIFFICULT_SECRET]) + if SecretsanityOptionName.secret_notes in options.secretsanity: + secret_items.extend(items_by_group[Group.SECRET_NOTES_SECRET]) + if options.quest_locations.has_no_story_quests(): + secret_items.append(item_table[Wallet.club_card]) + secret_items.append(item_table[Wallet.iridium_snake_milk]) + filtered_secret_items = remove_excluded(list(secret_items), content, options) + items.extend([item_factory(item) for item in filtered_secret_items]) + + +def create_eatsanity_enzyme_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): + if EatsanityOptionName.lock_effects not in options.eatsanity: + return + + # These items unlock progressively stronger ability to digest food items that give the associated buff + # Upon receiving the enzyme, you also get a temporary buff of whatever the effect is + # Stamina and Health items can go beyond their original max value, but the buffs cannot. + items.extend(item_factory(item) for item in ["Stamina Enzyme"]*10) + items.extend(item_factory(item) for item in ["Health Enzyme"]*10) + items.extend(item_factory(item) for item in ["Speed Enzyme"]*5) + items.extend(item_factory(item) for item in ["Luck Enzyme"]*5) + items.extend(item_factory(item) for item in ["Farming Enzyme"]*5) + items.extend(item_factory(item) for item in ["Foraging Enzyme"]*5) + items.extend(item_factory(item) for item in ["Fishing Enzyme"]*5) + items.extend(item_factory(item) for item in ["Mining Enzyme"]*5) + items.extend(item_factory(item) for item in ["Magnetism Enzyme"]*2) + items.extend(item_factory(item) for item in ["Defense Enzyme"]*5) + items.extend(item_factory(item) for item in ["Attack Enzyme"]*5) + items.extend(item_factory(item) for item in ["Max Stamina Enzyme"]*3) + items.extend(item_factory(item) for item in ["Squid Ink Enzyme"]) + items.extend(item_factory(item) for item in ["Monster Musk Enzyme"]) + items.extend(item_factory(item) for item in ["Oil Of Garlic Enzyme"]) + items.extend(item_factory(item) for item in ["Tipsy Enzyme"]) + + +def create_endgame_locations_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): + if options.include_endgame_locations == IncludeEndgameLocations.option_false: + return + + items_to_add = [] + items_to_add.extend(items_by_group[Group.ENDGAME_LOCATION_ITEMS]) + if options.friendsanity != Friendsanity.option_all_with_marriage: + for portrait in items_by_group[Group.REQUIRES_FRIENDSANITY_MARRIAGE]: + items_to_add.remove(portrait) + items.extend(item_factory(item) for item in items_to_add) def create_goal_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): @@ -499,233 +614,66 @@ def create_goal_items(item_factory: StardewItemFactory, options: StardewValleyOp items.append(item_factory(Wallet.metal_detector)) -def create_archaeology_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - mods = options.mods - if ModNames.archaeology not in mods: +def create_archaeology_items(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): + if ModNames.archaeology not in content.registered_packs: return items.append(item_factory(Wallet.metal_detector)) -def create_filler_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions) -> List[Item]: - if options.festival_locations == FestivalLocations.option_disabled: - return [] - - return [item_factory(item) for item in items_by_group[Group.FESTIVAL] if - item.classification == ItemClassification.filler] - - -def create_magic_mod_spells(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if ModNames.magic not in options.mods: +def create_magic_mod_spells(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): + if ModNames.magic not in content.registered_packs: return items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]]) -def create_deepwoods_pendants(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if ModNames.deepwoods not in options.mods: +def create_deepwoods_pendants(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): + if ModNames.deepwoods not in content.registered_packs: return items.extend([item_factory(item) for item in ["Pendant of Elders", "Pendant of Community", "Pendant of Depths"]]) -def create_sve_special_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if ModNames.sve not in options.mods: +def create_sve_special_items(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): + if ModNames.sve not in content.registered_packs: return - items.extend([item_factory(item) for item in items_by_group[Group.MOD_WARP] if item.mod_name == ModNames.sve]) + items.extend([item_factory(item) for item in items_by_group[Group.MOD_WARP] if ModNames.sve in item.content_packs]) -def create_quest_rewards_sve(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - if ModNames.sve not in options.mods: +def create_quest_rewards_sve(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]): + if not content.is_enabled(ModNames.sve): return - exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true + ginger_island_included = content.is_enabled(ginger_island_content_pack) items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items]) - if not exclude_ginger_island: + if ginger_island_included: items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items_ginger_island]) if options.quest_locations.has_no_story_quests(): return items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items]) - if exclude_ginger_island: + if not ginger_island_included: return items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items_ginger_island]) -def create_unique_filler_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random, - available_item_slots: int) -> List[Item]: - items = [] - - items.extend(create_filler_festival_rewards(item_factory, options)) - - if len(items) > available_item_slots: - items = random.sample(items, available_item_slots) - return items - - -def weapons_count(options: StardewValleyOptions): +def weapons_count(content: StardewContent): weapon_count = 5 - if ModNames.sve in options.mods: + if ModNames.sve in content.registered_packs: weapon_count += 1 return weapon_count -def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random, - items_already_added: List[Item], - available_item_slots: int) -> List[Item]: - include_traps = options.trap_difficulty != TrapDifficulty.option_no_traps - items_already_added_names = [item.name for item in items_already_added] - useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL] - if pack.name not in items_already_added_names] - trap_items = [trap for trap in items_by_group[Group.TRAP] - if trap.name not in items_already_added_names and - Group.DEPRECATED not in trap.groups and - (trap.mod_name is None or trap.mod_name in options.mods) and - options.trap_distribution[trap.name] > 0] - player_buffs = get_allowed_player_buffs(options.enabled_filler_buffs) - - priority_filler_items = [] - priority_filler_items.extend(useful_resource_packs) - priority_filler_items.extend(player_buffs) - - if include_traps: - priority_filler_items.extend(trap_items) - - exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true - all_filler_packs = remove_excluded_items(get_all_filler_items(include_traps, exclude_ginger_island), options) - all_filler_packs.extend(player_buffs) - priority_filler_items = remove_excluded_items(priority_filler_items, options) - - number_priority_items = len(priority_filler_items) - if available_item_slots < number_priority_items: - chosen_priority_items = [item_factory(resource_pack) for resource_pack in - random.sample(priority_filler_items, available_item_slots)] - return chosen_priority_items - - items = [] - chosen_priority_items = [item_factory(resource_pack, - ItemClassification.trap if resource_pack.classification == ItemClassification.trap else ItemClassification.useful) - for resource_pack in priority_filler_items] - items.extend(chosen_priority_items) - available_item_slots -= number_priority_items - all_filler_packs = [filler_pack for filler_pack in all_filler_packs - if Group.MAXIMUM_ONE not in filler_pack.groups or - (filler_pack.name not in [priority_item.name for priority_item in - priority_filler_items] and filler_pack.name not in items_already_added_names)] - - filler_weights = get_filler_weights(options, all_filler_packs) - - while available_item_slots > 0: - resource_pack = random.choices(all_filler_packs, weights=filler_weights, k=1)[0] - exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups - while exactly_2 and available_item_slots == 1: - resource_pack = random.choices(all_filler_packs, weights=filler_weights, k=1)[0] - exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups - classification = ItemClassification.useful if resource_pack.classification == ItemClassification.progression else resource_pack.classification - items.append(item_factory(resource_pack, classification)) - available_item_slots -= 1 - if exactly_2: - items.append(item_factory(resource_pack, classification)) - available_item_slots -= 1 - if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups: - index = all_filler_packs.index(resource_pack) - all_filler_packs.pop(index) - filler_weights.pop(index) - - return items - - -def get_filler_weights(options: StardewValleyOptions, all_filler_packs: List[ItemData]): - weights = [] - for filler in all_filler_packs: - if filler.name in options.trap_distribution: - num = options.trap_distribution[filler.name] - else: - num = options.trap_distribution.default_weight - weights.append(num) - return weights - - -def filter_deprecated_items(items: List[ItemData]) -> List[ItemData]: - return [item for item in items if Group.DEPRECATED not in item.groups] - - -def filter_ginger_island_items(exclude_island: bool, items: List[ItemData]) -> List[ItemData]: - return [item for item in items if not exclude_island or Group.GINGER_ISLAND not in item.groups] - - -def filter_mod_items(mods: Set[str], items: List[ItemData]) -> List[ItemData]: - return [item for item in items if item.mod_name is None or item.mod_name in mods] - - -def remove_excluded_items(items, options: StardewValleyOptions): - return remove_excluded_items_island_mods(items, options.exclude_ginger_island == ExcludeGingerIsland.option_true, options.mods.value) - - -def remove_excluded_items_island_mods(items, exclude_ginger_island: bool, mods: Set[str]): - deprecated_filter = filter_deprecated_items(items) - ginger_island_filter = filter_ginger_island_items(exclude_ginger_island, deprecated_filter) - mod_filter = filter_mod_items(mods, ginger_island_filter) - return mod_filter - - -def generate_filler_choice_pool(options: StardewValleyOptions) -> list[str]: - include_traps = options.trap_difficulty != TrapDifficulty.option_no_traps - exclude_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true - - available_filler = get_all_filler_items(include_traps, exclude_island) - available_filler = remove_limited_amount_packs(available_filler) - - return [item.name for item in available_filler] - - -def remove_limited_amount_packs(packs): - return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.AT_LEAST_TWO not in pack.groups] - - -def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool) -> List[ItemData]: - all_filler_items = [pack for pack in items_by_group[Group.RESOURCE_PACK]] - all_filler_items.extend(items_by_group[Group.TRASH]) - if include_traps: - all_filler_items.extend(items_by_group[Group.TRAP]) - all_filler_items = remove_excluded_items_island_mods(all_filler_items, exclude_ginger_island, set()) - return all_filler_items - - -def get_allowed_player_buffs(buff_option: EnabledFillerBuffs) -> List[ItemData]: - allowed_buffs = [] - if BuffOptionName.luck in buff_option: - allowed_buffs.append(item_table[Buff.luck]) - if BuffOptionName.damage in buff_option: - allowed_buffs.append(item_table[Buff.damage]) - if BuffOptionName.defense in buff_option: - allowed_buffs.append(item_table[Buff.defense]) - if BuffOptionName.immunity in buff_option: - allowed_buffs.append(item_table[Buff.immunity]) - if BuffOptionName.health in buff_option: - allowed_buffs.append(item_table[Buff.health]) - if BuffOptionName.energy in buff_option: - allowed_buffs.append(item_table[Buff.energy]) - if BuffOptionName.bite in buff_option: - allowed_buffs.append(item_table[Buff.bite_rate]) - if BuffOptionName.fish_trap in buff_option: - allowed_buffs.append(item_table[Buff.fish_trap]) - if BuffOptionName.fishing_bar in buff_option: - allowed_buffs.append(item_table[Buff.fishing_bar]) - if BuffOptionName.quality in buff_option: - allowed_buffs.append(item_table[Buff.quality]) - if BuffOptionName.glow in buff_option: - allowed_buffs.append(item_table[Buff.glow]) - return allowed_buffs - - def get_stardrop_classification(options) -> ItemClassification: - return ItemClassification.progression_skip_balancing if world_is_perfection(options) or world_is_stardrops(options) else ItemClassification.useful + return ItemClassification.progression_skip_balancing \ + if goal_is_perfection(options) or goal_is_stardrops(options) or EatsanityOptionName.shop in options.eatsanity \ + else ItemClassification.useful -def world_is_perfection(options) -> bool: +def goal_is_perfection(options) -> bool: return options.goal == Goal.option_perfection -def world_is_stardrops(options) -> bool: +def goal_is_stardrops(options) -> bool: return options.goal == Goal.option_mystery_of_the_stardrops diff --git a/worlds/stardew_valley/items/item_data.py b/worlds/stardew_valley/items/item_data.py index 6abc96f4e626..6fac08a592cf 100644 --- a/worlds/stardew_valley/items/item_data.py +++ b/worlds/stardew_valley/items/item_data.py @@ -1,145 +1,197 @@ -import csv -import enum -from dataclasses import dataclass, field -from functools import reduce -from pathlib import Path -from typing import Dict, List, Protocol, Union, Set, Optional - -from BaseClasses import Item, ItemClassification -from .. import data -from ..logic.logic_event import all_events - -ITEM_CODE_OFFSET = 717000 - -world_folder = Path(__file__).parent - - -class Group(enum.Enum): - RESOURCE_PACK = enum.auto() - FRIENDSHIP_PACK = enum.auto() - COMMUNITY_REWARD = enum.auto() - TRASH = enum.auto() - FOOTWEAR = enum.auto() - HATS = enum.auto() - RING = enum.auto() - WEAPON = enum.auto() - WEAPON_GENERIC = enum.auto() - WEAPON_SWORD = enum.auto() - WEAPON_CLUB = enum.auto() - WEAPON_DAGGER = enum.auto() - WEAPON_SLINGSHOT = enum.auto() - PROGRESSIVE_TOOLS = enum.auto() - SKILL_LEVEL_UP = enum.auto() - SKILL_MASTERY = enum.auto() - BUILDING = enum.auto() - WIZARD_BUILDING = enum.auto() - DESERT_TRANSPORTATION = enum.auto() - ISLAND_TRANSPORTATION = enum.auto() - ARCADE_MACHINE_BUFFS = enum.auto() - BASE_RESOURCE = enum.auto() - WARP_TOTEM = enum.auto() - GEODE = enum.auto() - ORE = enum.auto() - FERTILIZER = enum.auto() - SEED = enum.auto() - CROPSANITY = enum.auto() - FISHING_RESOURCE = enum.auto() - SEASON = enum.auto() - TRAVELING_MERCHANT_DAY = enum.auto() - MUSEUM = enum.auto() - FRIENDSANITY = enum.auto() - FESTIVAL = enum.auto() - RARECROW = enum.auto() - TRAP = enum.auto() - BONUS = enum.auto() - MAXIMUM_ONE = enum.auto() - AT_LEAST_TWO = enum.auto() - DEPRECATED = enum.auto() - RESOURCE_PACK_USEFUL = enum.auto() - SPECIAL_ORDER_BOARD = enum.auto() - SPECIAL_ORDER_QI = enum.auto() - BABY = enum.auto() - GINGER_ISLAND = enum.auto() - WALNUT_PURCHASE = enum.auto() - TV_CHANNEL = enum.auto() - QI_CRAFTING_RECIPE = enum.auto() - CHEFSANITY = enum.auto() - CHEFSANITY_STARTER = enum.auto() - CHEFSANITY_QOS = enum.auto() - CHEFSANITY_PURCHASE = enum.auto() - CHEFSANITY_FRIENDSHIP = enum.auto() - CHEFSANITY_SKILL = enum.auto() - CRAFTSANITY = enum.auto() - BOOK_POWER = enum.auto() - LOST_BOOK = enum.auto() - PLAYER_BUFF = enum.auto() - # Mods - MAGIC_SPELL = enum.auto() - MOD_WARP = enum.auto() - - -@dataclass(frozen=True) -class ItemData: - code_without_offset: Optional[int] - name: str - classification: ItemClassification - mod_name: Optional[str] = None - groups: Set[Group] = field(default_factory=frozenset) - - def __post_init__(self): - if not isinstance(self.groups, frozenset): - super().__setattr__("groups", frozenset(self.groups)) - - @property - def code(self): - return ITEM_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None - - def has_any_group(self, *group: Group) -> bool: - groups = set(group) - return bool(groups.intersection(self.groups)) - - -class StardewItemFactory(Protocol): - def __call__(self, name: Union[str, ItemData], override_classification: ItemClassification = None) -> Item: - raise NotImplementedError - - -def load_item_csv(): - from importlib.resources import files - - items = [] - with files(data).joinpath("items.csv").open() as file: - item_reader = csv.DictReader(file) - for item in item_reader: - id = int(item["id"]) if item["id"] else None - classification = reduce((lambda a, b: a | b), {ItemClassification[str_classification] for str_classification in item["classification"].split(",")}) - groups = {Group[group] for group in item["groups"].split(",") if group} - mod_name = str(item["mod_name"]) if item["mod_name"] else None - items.append(ItemData(id, item["name"], classification, mod_name, groups)) - return items - - -events = [ - ItemData(None, e, ItemClassification.progression) - for e in sorted(all_events) -] - -all_items: List[ItemData] = load_item_csv() + events -item_table: Dict[str, ItemData] = {} -items_by_group: Dict[Group, List[ItemData]] = {} - - -def initialize_groups(): - for item in all_items: - for group in item.groups: - item_group = items_by_group.get(group, list()) - item_group.append(item) - items_by_group[group] = item_group - - -def initialize_item_table(): - item_table.update({item.name: item for item in all_items}) - - -initialize_item_table() -initialize_groups() +from __future__ import annotations + +import csv +import enum +from dataclasses import dataclass, field +from functools import reduce +from typing import Protocol + +from BaseClasses import ItemClassification, Item +from .. import data +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..logic.logic_event import all_events + +ITEM_CODE_OFFSET = 717000 + + +class StardewItemFactory(Protocol): + def __call__(self, item: str | ItemData, /, *, classification_pre_fill: ItemClassification = None, + classification_post_fill: ItemClassification = None) -> Item: + """ + :param item: The item to create. Can be the name of the item or the item data. + :param classification_pre_fill: The classification to use for the item before the fill. If None, the basic classification of the item is used. + :param classification_post_fill: The classification to use for the item after the fill. If None, the pre_fill classification will be used. + """ + raise NotImplementedError + + +class Group(enum.Enum): + FRIENDSHIP_PACK = enum.auto() + COMMUNITY_REWARD = enum.auto() + TRASH_BEAR = enum.auto() + TRASH = enum.auto() + FOOTWEAR = enum.auto() + WEAPON = enum.auto() + WEAPON_GENERIC = enum.auto() + WEAPON_SWORD = enum.auto() + WEAPON_CLUB = enum.auto() + WEAPON_DAGGER = enum.auto() + WEAPON_SLINGSHOT = enum.auto() + PROGRESSIVE_TOOLS = enum.auto() + SKILL_LEVEL_UP = enum.auto() + SKILL_MASTERY = enum.auto() + BUILDING = enum.auto() + WIZARD_BUILDING = enum.auto() + DESERT_TRANSPORTATION = enum.auto() + ISLAND_TRANSPORTATION = enum.auto() + ARCADE_MACHINE_BUFFS = enum.auto() + BASE_RESOURCE = enum.auto() + WARP_TOTEM = enum.auto() + GEODE = enum.auto() + ORE = enum.auto() + FERTILIZER = enum.auto() + CROPSANITY = enum.auto() + FISHING_RESOURCE = enum.auto() + SEASON = enum.auto() + TRAVELING_MERCHANT_DAY = enum.auto() + MUSEUM = enum.auto() + FRIENDSANITY = enum.auto() + FESTIVAL = enum.auto() + RARECROW = enum.auto() + TRAP = enum.auto() + BONUS = enum.auto() + MAXIMUM_ONE = enum.auto() + AT_LEAST_TWO = enum.auto() + DEPRECATED = enum.auto() + SPECIAL_ORDER_BOARD = enum.auto() + SPECIAL_ORDER_QI = enum.auto() + BABY = enum.auto() + GINGER_ISLAND = enum.auto() + WALNUT_PURCHASE = enum.auto() + TV_CHANNEL = enum.auto() + QI_CRAFTING_RECIPE = enum.auto() + CHEFSANITY = enum.auto() + CHEFSANITY_STARTER = enum.auto() + CHEFSANITY_QOS = enum.auto() + CHEFSANITY_PURCHASE = enum.auto() + CHEFSANITY_FRIENDSHIP = enum.auto() + CHEFSANITY_SKILL = enum.auto() + CRAFTSANITY = enum.auto() + BOOK_POWER = enum.auto() + LOST_BOOK = enum.auto() + PLAYER_BUFF = enum.auto() + EASY_SECRET = enum.auto() + FISHING_SECRET = enum.auto() + SECRET_NOTES_SECRET = enum.auto() + MOVIESANITY = enum.auto() + TRINKET = enum.auto() + EATSANITY_ENZYME = enum.auto() + ENDGAME_LOCATION_ITEMS = enum.auto() + REQUIRES_FRIENDSANITY_MARRIAGE = enum.auto() + BOOKSELLER = enum.auto() + + # Types of filler + FILLER_FARMING = enum.auto() + FILLER_FISHING = enum.auto() + FILLER_FRUIT_TREES = enum.auto() + FILLER_FOOD = enum.auto() + FILLER_BUFF_FOOD = enum.auto() + FILLER_CONSUMABLE = enum.auto() + FILLER_MACHINE = enum.auto() + FILLER_STORAGE = enum.auto() + FILLER_QUALITY_OF_LIFE = enum.auto() + FILLER_MATERIALS = enum.auto() + FILLER_CURRENCY = enum.auto() + FILLER_MONEY = enum.auto() + FILLER_HAT = enum.auto() + FILLER_DECORATION = enum.auto() + FILLER_RING = enum.auto() + + # Mods + MAGIC_SPELL = enum.auto() + MOD_WARP = enum.auto() + + +FILLER_GROUPS = [Group.FILLER_FARMING, Group.FILLER_FISHING, Group.FILLER_FRUIT_TREES, Group.FILLER_FOOD, Group.FILLER_BUFF_FOOD, + Group.FILLER_CONSUMABLE, Group.FILLER_MACHINE, Group.FILLER_STORAGE, Group.FILLER_QUALITY_OF_LIFE, Group.FILLER_MATERIALS, + Group.FILLER_CURRENCY, Group.FILLER_MONEY, Group.FILLER_HAT, Group.FILLER_DECORATION, Group.FILLER_RING, ] + + +@dataclass(frozen=True) +class ItemData: + code_without_offset: int | None + name: str + classification: ItemClassification + content_packs: frozenset[str] = frozenset() + """All the content packs required for this item to be available.""" + groups: set[Group] = field(default_factory=frozenset) + + def __post_init__(self): + if not isinstance(self.groups, frozenset): + super().__setattr__("groups", frozenset(self.groups)) + + @property + def code(self) -> int | None: + return ITEM_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None + + def has_any_group(self, *group: Group) -> bool: + groups = set(group) + return bool(groups.intersection(self.groups)) + + def has_all_groups(self, *group: Group) -> bool: + groups = set(group) + return bool(groups.issubset(self.groups)) + + def has_limited_amount(self) -> bool: + return self.has_any_group(Group.MAXIMUM_ONE, Group.AT_LEAST_TWO) + + +def load_item_csv(): + from importlib.resources import files + + items = [] + with files(data).joinpath("items.csv").open() as file: + item_reader = csv.DictReader(file) + for item in item_reader: + item_id = int(item["id"]) if item["id"] else None + item_name = item["name"] + classification = reduce((lambda a, b: a | b), {ItemClassification[str_classification] for str_classification in item["classification"].split(",")}) + csv_groups = [Group[group] for group in item["groups"].split(",") if group] + groups = set(csv_groups) + csv_content_packs = [cp for cp in item["content_packs"].split(",") if cp] + content_packs = frozenset(csv_content_packs) + + assert len(csv_groups) == len(groups), f"Item '{item_name}' has duplicate groups: {csv_groups}" + assert len(csv_content_packs) == len(content_packs) + + if Group.GINGER_ISLAND in groups: + content_packs |= {ginger_island_content_pack.name} + + items.append(ItemData(item_id, item_name, classification, content_packs, groups)) + return items + + +events = [ + ItemData(None, e, ItemClassification.progression) + for e in sorted(all_events) +] + +all_items: list[ItemData] = load_item_csv() + events +item_table: dict[str, ItemData] = {} +items_by_group: dict[Group, list[ItemData]] = {} + + +def initialize_groups(): + for item in all_items: + for group in item.groups: + item_group = items_by_group.get(group, list()) + item_group.append(item) + items_by_group[group] = item_group + + +def initialize_item_table(): + item_table.update({item.name: item for item in all_items}) + + +initialize_item_table() +initialize_groups() diff --git a/worlds/stardew_valley/locations.py b/worlds/stardew_valley/locations.py index fa4d50ce792a..613698ac1bfe 100644 --- a/worlds/stardew_valley/locations.py +++ b/worlds/stardew_valley/locations.py @@ -1,18 +1,24 @@ import csv import enum +import logging from dataclasses import dataclass from random import Random -from typing import Optional, Dict, Protocol, List, FrozenSet, Iterable +from typing import Optional, Dict, Protocol, List, Iterable from . import data from .bundles.bundle_room import BundleRoom from .content.game_content import StardewContent +from .content.vanilla.ginger_island import ginger_island_content_pack +from .content.vanilla.qi_board import qi_board_content_pack from .data.game_item import ItemTag from .data.museum_data import all_museum_items from .mods.mod_data import ModNames -from .options import ExcludeGingerIsland, ArcadeMachineLocations, SpecialOrderLocations, Museumsanity, \ +from .options import ArcadeMachineLocations, SpecialOrderLocations, Museumsanity, \ FestivalLocations, ElevatorProgression, BackpackProgression, FarmType from .options import StardewValleyOptions, Craftsanity, Chefsanity, Cooksanity, Shipsanity, Monstersanity +from .options.options import BackpackSize, Moviesanity, Eatsanity, IncludeEndgameLocations, Friendsanity +from .strings.ap_names.ap_option_names import WalnutsanityOptionName, SecretsanityOptionName, EatsanityOptionName, ChefsanityOptionName, StartWithoutOptionName +from .strings.backpack_tiers import Backpack from .strings.goal_names import Goal from .strings.quest_names import ModQuest, Quest from .strings.region_names import Region, LogicRegion @@ -20,10 +26,13 @@ LOCATION_CODE_OFFSET = 717000 +logger = logging.getLogger(__name__) + class LocationTags(enum.Enum): MANDATORY = enum.auto() BUNDLE = enum.auto() + TRASH_BEAR = enum.auto() COMMUNITY_CENTER_BUNDLE = enum.auto() CRAFTS_ROOM_BUNDLE = enum.auto() PANTRY_BUNDLE = enum.auto() @@ -33,7 +42,10 @@ class LocationTags(enum.Enum): VAULT_BUNDLE = enum.auto() COMMUNITY_CENTER_ROOM = enum.auto() RACCOON_BUNDLES = enum.auto() + MEME_BUNDLE = enum.auto() BACKPACK = enum.auto() + BACKPACK_TIER = enum.auto() + SPLIT_BACKPACK = enum.auto() TOOL_UPGRADE = enum.auto() HOE_UPGRADE = enum.auto() PICKAXE_UPGRADE = enum.auto() @@ -42,6 +54,7 @@ class LocationTags(enum.Enum): TRASH_CAN_UPGRADE = enum.auto() FISHING_ROD_UPGRADE = enum.auto() PAN_UPGRADE = enum.auto() + STARTING_TOOLS = enum.auto() THE_MINES_TREASURE = enum.auto() CROPSANITY = enum.auto() ELEVATOR = enum.auto() @@ -67,6 +80,7 @@ class LocationTags(enum.Enum): FESTIVAL = enum.auto() FESTIVAL_HARD = enum.auto() DESERT_FESTIVAL_CHEF = enum.auto() + DESERT_FESTIVAL_CHEF_MEAL = enum.auto() SPECIAL_ORDER_BOARD = enum.auto() SPECIAL_ORDER_QI = enum.auto() REQUIRES_QI_ORDERS = enum.auto() @@ -97,10 +111,42 @@ class LocationTags(enum.Enum): CHEFSANITY_SKILL = enum.auto() CHEFSANITY_STARTER = enum.auto() CRAFTSANITY = enum.auto() + CRAFTSANITY_CRAFT = enum.auto() + CRAFTSANITY_RECIPE = enum.auto() BOOKSANITY = enum.auto() BOOKSANITY_POWER = enum.auto() BOOKSANITY_SKILL = enum.auto() BOOKSANITY_LOST = enum.auto() + SECRETSANITY = enum.auto() + EASY_SECRET = enum.auto() + FISHING_SECRET = enum.auto() + DIFFICULT_SECRET = enum.auto() + SECRET_NOTE = enum.auto() + REPLACES_PREVIOUS_LOCATION = enum.auto() + ANY_MOVIE = enum.auto() + MOVIE = enum.auto() + MOVIE_SNACK = enum.auto() + HATSANITY = enum.auto() + HAT_EASY = enum.auto() + HAT_TAILORING = enum.auto() + HAT_MEDIUM = enum.auto() + HAT_DIFFICULT = enum.auto() + HAT_RNG = enum.auto() + HAT_NEAR_PERFECTION = enum.auto() + HAT_POST_PERFECTION = enum.auto() + HAT_IMPOSSIBLE = enum.auto() + EATSANITY = enum.auto() + EATSANITY_CROP = enum.auto() + EATSANITY_COOKING = enum.auto() + EATSANITY_FISH = enum.auto() + EATSANITY_ARTISAN = enum.auto() + EATSANITY_SHOP = enum.auto() + EATSANITY_POISONOUS = enum.auto() + ENDGAME_LOCATIONS = enum.auto() + REQUIRES_FRIENDSANITY = enum.auto() + REQUIRES_FRIENDSANITY_MARRIAGE = enum.auto() + + BEACH_FARM = enum.auto() # Mods # Skill Mods LUCK_LEVEL = enum.auto() @@ -115,14 +161,15 @@ class LocationTags(enum.Enum): @dataclass(frozen=True) class LocationData: - code_without_offset: Optional[int] + code_without_offset: int | None region: str name: str - mod_name: Optional[str] = None - tags: FrozenSet[LocationTags] = frozenset() + content_packs: frozenset[str] = frozenset() + """All the content packs required for this location to be active.""" + tags: frozenset[LocationTags] = frozenset() @property - def code(self) -> Optional[int]: + def code(self) -> int | None: return LOCATION_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None @@ -134,16 +181,28 @@ def __call__(self, name: str, code: Optional[int], region: str) -> None: def load_location_csv() -> List[LocationData]: from importlib.resources import files + locations = [] with files(data).joinpath("locations.csv").open() as file: - reader = csv.DictReader(file) - return [LocationData(int(location["id"]) if location["id"] else None, - location["region"], - location["name"], - str(location["mod_name"]) if location["mod_name"] else None, - frozenset(LocationTags[group] - for group in location["tags"].split(",") - if group)) - for location in reader] + location_reader = csv.DictReader(file) + for location in location_reader: + location_id = int(location["id"]) if location["id"] else None + location_name = location["name"] + csv_tags = [LocationTags[tag] for tag in location["tags"].split(",") if tag] + tags = frozenset(csv_tags) + csv_content_packs = [cp for cp in location["content_packs"].split(",") if cp] + content_packs = frozenset(csv_content_packs) + + assert len(csv_tags) == len(tags), f"Location '{location_name}' has duplicate tags: {csv_tags}" + assert len(csv_content_packs) == len(content_packs) + + if LocationTags.GINGER_ISLAND in tags: + content_packs |= {ginger_island_content_pack.name} + if LocationTags.SPECIAL_ORDER_QI in tags or LocationTags.REQUIRES_QI_ORDERS in tags: + content_packs |= {qi_board_content_pack.name} + + locations.append(LocationData(location_id, location["region"], location_name, content_packs, tags)) + + return locations events_locations = [ @@ -161,6 +220,8 @@ def load_location_csv() -> List[LocationData]: LocationData(None, Region.farm, Goal.craft_master), LocationData(None, LogicRegion.shipping, Goal.legend), LocationData(None, Region.farm, Goal.mystery_of_the_stardrops), + LocationData(None, Region.farm, Goal.mad_hatter), + LocationData(None, Region.farm, Goal.ultimate_foodie), LocationData(None, Region.farm, Goal.allsanity), LocationData(None, Region.qi_walnut_room, Goal.perfection), ] @@ -296,10 +357,12 @@ def extend_hard_festival_locations(randomized_locations: List[LocationData], opt def extend_desert_festival_chef_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random): - festival_chef_locations = locations_by_tag[LocationTags.DESERT_FESTIVAL_CHEF] - number_to_add = 5 if options.festival_locations == FestivalLocations.option_easy else 10 - locations_to_add = random.sample(festival_chef_locations, number_to_add) - randomized_locations.extend(locations_to_add) + if options.festival_locations == FestivalLocations.option_easy: + randomized_locations.append(location_table["Desert Chef"]) + elif options.festival_locations == FestivalLocations.option_hard: + festival_chef_locations = locations_by_tag[LocationTags.DESERT_FESTIVAL_CHEF_MEAL] + location_to_add = random.choice(festival_chef_locations) + randomized_locations.append(location_to_add) def extend_special_order_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): @@ -307,16 +370,15 @@ def extend_special_order_locations(randomized_locations: List[LocationData], opt board_locations = filter_disabled_locations(options, content, locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]) randomized_locations.extend(board_locations) - include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false - if options.special_order_locations & SpecialOrderLocations.value_qi and include_island: + if content.is_enabled(qi_board_content_pack): include_arcade = options.arcade_machine_locations != ArcadeMachineLocations.option_disabled qi_orders = [location for location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI] if include_arcade or LocationTags.JUNIMO_KART not in location.tags] randomized_locations.extend(qi_orders) -def extend_walnut_purchase_locations(randomized_locations: List[LocationData], options: StardewValleyOptions): - if options.exclude_ginger_island == ExcludeGingerIsland.option_true: +def extend_walnut_purchase_locations(randomized_locations: List[LocationData], content: StardewContent): + if not content.is_enabled(ginger_island_content_pack): return randomized_locations.append(location_table["Repair Ticket Machine"]) randomized_locations.append(location_table["Repair Boat Hull"]) @@ -332,14 +394,14 @@ def extend_mandatory_locations(randomized_locations: List[LocationData], options randomized_locations.extend(filtered_mandatory_locations) -def extend_situational_quest_locations(randomized_locations: List[LocationData], options: StardewValleyOptions): +def extend_situational_quest_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): if options.quest_locations.has_no_story_quests(): return - if ModNames.distant_lands in options.mods: - if ModNames.alecto in options.mods: - randomized_locations.append(location_table[ModQuest.WitchOrder]) + if ModNames.distant_lands in content.registered_packs: + if ModNames.alecto in content.registered_packs: + randomized_locations.append(location_table[f"Quest: {ModQuest.WitchOrder}"]) else: - randomized_locations.append(location_table[ModQuest.CorruptedCropsTask]) + randomized_locations.append(location_table[f"Quest: {ModQuest.CorruptedCropsTask}"]) def extend_bundle_locations(randomized_locations: List[LocationData], bundle_rooms: List[BundleRoom]): @@ -351,19 +413,35 @@ def extend_bundle_locations(randomized_locations: List[LocationData], bundle_roo randomized_locations.append(location_table[bundle.name]) -def extend_backpack_locations(randomized_locations: List[LocationData], options: StardewValleyOptions): +def extend_trash_bear_locations(randomized_locations: List[LocationData], trash_bear_requests: Dict[str, List[str]]): + for request_type in trash_bear_requests: + randomized_locations.append(location_table[f"Trash Bear {request_type}"]) + + +def extend_backpack_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): if options.backpack_progression == BackpackProgression.option_vanilla: return - backpack_locations = [location for location in locations_by_tag[LocationTags.BACKPACK]] - filtered_backpack_locations = filter_modded_locations(options, backpack_locations) + + no_start_backpack = StartWithoutOptionName.backpack in options.start_without + if options.backpack_size == BackpackSize.option_12: + backpack_locations = [location for location in locations_by_tag[LocationTags.BACKPACK_TIER] if no_start_backpack or LocationTags.STARTING_TOOLS not in location.tags] + else: + num_per_tier = options.backpack_size.count_per_tier() + backpack_tier_names = Backpack.get_purchasable_tiers(ModNames.big_backpack in content.registered_packs, no_start_backpack) + backpack_locations = [] + for tier in backpack_tier_names: + for i in range(1, num_per_tier + 1): + backpack_locations.append(location_table[f"{tier} {i}"]) + + filtered_backpack_locations = filter_modded_locations(backpack_locations, content) randomized_locations.extend(filtered_backpack_locations) -def extend_elevator_locations(randomized_locations: List[LocationData], options: StardewValleyOptions): +def extend_elevator_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): if options.elevator_progression == ElevatorProgression.option_vanilla: return elevator_locations = [location for location in locations_by_tag[LocationTags.ELEVATOR]] - filtered_elevator_locations = filter_modded_locations(options, elevator_locations) + filtered_elevator_locations = filter_modded_locations(elevator_locations, content) randomized_locations.extend(filtered_elevator_locations) @@ -396,14 +474,14 @@ def extend_shipsanity_locations(randomized_locations: List[LocationData], option randomized_locations.extend(filtered_ship_locations) return shipsanity_locations = set() - if shipsanity == Shipsanity.option_fish or shipsanity == Shipsanity.option_full_shipment_with_fish: + if shipsanity == Shipsanity.option_fish or shipsanity == Shipsanity.option_crops_and_fish or shipsanity == Shipsanity.option_full_shipment_with_fish: shipsanity_locations = shipsanity_locations.union({location for location in locations_by_tag[LocationTags.SHIPSANITY_FISH]}) - if shipsanity == Shipsanity.option_crops: + if shipsanity == Shipsanity.option_crops or shipsanity == Shipsanity.option_crops_and_fish: shipsanity_locations = shipsanity_locations.union({location for location in locations_by_tag[LocationTags.SHIPSANITY_CROP]}) if shipsanity == Shipsanity.option_full_shipment or shipsanity == Shipsanity.option_full_shipment_with_fish: shipsanity_locations = shipsanity_locations.union({location for location in locations_by_tag[LocationTags.SHIPSANITY_FULL_SHIPMENT]}) - filtered_shipsanity_locations = filter_disabled_locations(options, content, list(shipsanity_locations)) + filtered_shipsanity_locations = filter_disabled_locations(options, content, sorted(list(shipsanity_locations), key=lambda x: x.name)) randomized_locations.extend(filtered_shipsanity_locations) @@ -422,18 +500,18 @@ def extend_cooksanity_locations(randomized_locations: List[LocationData], option def extend_chefsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): chefsanity = options.chefsanity - if chefsanity == Chefsanity.option_none: + if chefsanity == Chefsanity.preset_none: return chefsanity_locations_by_name = {} # Dictionary to not make duplicates - if chefsanity & Chefsanity.option_queen_of_sauce: + if ChefsanityOptionName.queen_of_sauce in chefsanity: chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_QOS]}) - if chefsanity & Chefsanity.option_purchases: + if ChefsanityOptionName.purchases in chefsanity: chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_PURCHASE]}) - if chefsanity & Chefsanity.option_friendship: + if ChefsanityOptionName.friendship in chefsanity: chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_FRIENDSHIP]}) - if chefsanity & Chefsanity.option_skills: + if ChefsanityOptionName.skills in chefsanity: chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_SKILL]}) filtered_chefsanity_locations = filter_disabled_locations(options, content, list(chefsanity_locations_by_name.values())) @@ -468,18 +546,141 @@ def extend_walnutsanity_locations(randomized_locations: List[LocationData], opti if not options.walnutsanity: return - if "Puzzles" in options.walnutsanity: + if WalnutsanityOptionName.puzzles in options.walnutsanity: randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_PUZZLE]) - if "Bushes" in options.walnutsanity: + if WalnutsanityOptionName.bushes in options.walnutsanity: randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_BUSH]) - if "Dig Spots" in options.walnutsanity: + if WalnutsanityOptionName.dig_spots in options.walnutsanity: randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_DIG]) - if "Repeatables" in options.walnutsanity: + if WalnutsanityOptionName.repeatables in options.walnutsanity: randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_REPEATABLE]) +def extend_movies_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): + if options.moviesanity == Moviesanity.option_none: + return + + locations = [] + if options.moviesanity == Moviesanity.option_one: + locations.extend(locations_by_tag[LocationTags.ANY_MOVIE]) + if options.moviesanity >= Moviesanity.option_all_movies: + locations.extend(locations_by_tag[LocationTags.MOVIE]) + if options.moviesanity >= Moviesanity.option_all_movies_and_all_snacks: + locations.extend(locations_by_tag[LocationTags.MOVIE_SNACK]) + filtered_locations = filter_disabled_locations(options, content, locations) + randomized_locations.extend(filtered_locations) + + +def extend_secrets_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): + if not options.secretsanity: + return + + locations = [] + if SecretsanityOptionName.easy in options.secretsanity: + locations.extend(locations_by_tag[LocationTags.EASY_SECRET]) + if SecretsanityOptionName.fishing in options.secretsanity: + locations.extend(locations_by_tag[LocationTags.FISHING_SECRET]) + if SecretsanityOptionName.difficult in options.secretsanity: + locations.extend(locations_by_tag[LocationTags.DIFFICULT_SECRET]) + if SecretsanityOptionName.secret_notes in options.secretsanity: + locations.extend(locations_by_tag[LocationTags.SECRET_NOTE]) + for location_dupe in locations_by_tag[LocationTags.REPLACES_PREVIOUS_LOCATION]: + second_part_of_name = location_dupe.name.split(":")[-1] + for location in randomized_locations: + second_part_of_dupe_name = location.name.split(":")[-1] + if second_part_of_name == second_part_of_dupe_name: + randomized_locations.remove(location) + filtered_locations = filter_disabled_locations(options, content, locations) + randomized_locations.extend(filtered_locations) + + +def extend_hats_locations(randomized_locations: List[LocationData], content: StardewContent): + hatsanity = content.features.hatsanity + if not hatsanity.is_enabled: + return + + for hat in content.hats.values(): + if not hatsanity.is_included(hat): + continue + + randomized_locations.append(location_table[hatsanity.to_location_name(hat)]) + + +def eatsanity_item_is_included(location: LocationData, options: StardewValleyOptions, content: StardewContent) -> bool: + eat_prefix = "Eat " + drink_prefix = "Drink " + if location.name.startswith(eat_prefix): + item_name = location.name[len(eat_prefix):] + elif location.name.startswith(drink_prefix): + item_name = location.name[len(drink_prefix):] + else: + raise Exception(f"Eatsanity Location does not have a recognized prefix: '{location.name}'") + + # if not item_name in content.game_items: + # return False + if EatsanityOptionName.poisonous in options.eatsanity.value: + return True + if location in locations_by_tag[LocationTags.EATSANITY_POISONOUS]: + return False + return True + + +def extend_eatsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): + if options.eatsanity.value == Eatsanity.preset_none: + return + + eatsanity_locations = [] + if EatsanityOptionName.crops in options.eatsanity: + eatsanity_locations.extend(locations_by_tag[LocationTags.EATSANITY_CROP]) + if EatsanityOptionName.cooking in options.eatsanity: + eatsanity_locations.extend(locations_by_tag[LocationTags.EATSANITY_COOKING]) + if EatsanityOptionName.fish in options.eatsanity: + eatsanity_locations.extend(locations_by_tag[LocationTags.EATSANITY_FISH]) + if EatsanityOptionName.artisan in options.eatsanity: + eatsanity_locations.extend(locations_by_tag[LocationTags.EATSANITY_ARTISAN]) + if EatsanityOptionName.shop in options.eatsanity: + eatsanity_locations.extend(locations_by_tag[LocationTags.EATSANITY_SHOP]) + + eatsanity_locations = [location for location in eatsanity_locations if eatsanity_item_is_included(location, options, content)] + eatsanity_locations = filter_disabled_locations(options, content, eatsanity_locations) + randomized_locations.extend(eatsanity_locations) + + +def extend_endgame_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): + if options.include_endgame_locations.value == IncludeEndgameLocations.option_false: + return + + has_friendsanity_marriage = options.friendsanity == Friendsanity.option_all_with_marriage + has_friendsanity = (not has_friendsanity_marriage) and options.friendsanity != Friendsanity.option_none + + endgame_locations = [] + endgame_locations.extend(locations_by_tag[LocationTags.ENDGAME_LOCATIONS]) + + endgame_locations = [location for location in endgame_locations if + LocationTags.REQUIRES_FRIENDSANITY_MARRIAGE not in location.tags or has_friendsanity_marriage] + endgame_locations = [location for location in endgame_locations if LocationTags.REQUIRES_FRIENDSANITY not in location.tags or has_friendsanity] + endgame_locations = filter_disabled_locations(options, content, endgame_locations) + randomized_locations.extend(endgame_locations) + + +def extend_filler_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, content: StardewContent): + days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + i = 1 + while len(randomized_locations) < 90: + location_name = f"Traveling Merchant Sunday Item {i}" + while any(location.name == location_name for location in randomized_locations): + i += 1 + location_name = f"Traveling Merchant Sunday Item {i}" + logger.debug(f"Player too few locations, adding Traveling Merchant Items #{i}") + for day in days: + location_name = f"Traveling Merchant {day} Item {i}" + randomized_locations.append(location_table[location_name]) + + + def create_locations(location_collector: StardewLocationCollector, bundle_rooms: List[BundleRoom], + trash_bear_requests: Dict[str, List[str]], options: StardewValleyOptions, content: StardewContent, random: Random): @@ -487,12 +688,13 @@ def create_locations(location_collector: StardewLocationCollector, extend_mandatory_locations(randomized_locations, options, content) extend_bundle_locations(randomized_locations, bundle_rooms) - extend_backpack_locations(randomized_locations, options) + extend_trash_bear_locations(randomized_locations, trash_bear_requests) + extend_backpack_locations(randomized_locations, options, content) if content.features.tool_progression.is_progressive: randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE]) - extend_elevator_locations(randomized_locations, options) + extend_elevator_locations(randomized_locations, options, content) skill_progression = content.features.skill_progression if skill_progression.is_progressive: @@ -516,7 +718,7 @@ def create_locations(location_collector: StardewLocationCollector, extend_festival_locations(randomized_locations, options, random) extend_special_order_locations(randomized_locations, options, content) - extend_walnut_purchase_locations(randomized_locations, options) + extend_walnut_purchase_locations(randomized_locations, content) extend_monstersanity_locations(randomized_locations, options, content) extend_shipsanity_locations(randomized_locations, options, content) @@ -526,9 +728,16 @@ def create_locations(location_collector: StardewLocationCollector, extend_quests_locations(randomized_locations, options, content) extend_book_locations(randomized_locations, content) extend_walnutsanity_locations(randomized_locations, options) + extend_movies_locations(randomized_locations, options, content) + extend_secrets_locations(randomized_locations, options, content) + extend_hats_locations(randomized_locations, content) + extend_eatsanity_locations(randomized_locations, options, content) + extend_endgame_locations(randomized_locations, options, content) # Mods - extend_situational_quest_locations(randomized_locations, options) + extend_situational_quest_locations(randomized_locations, options, content) + + extend_filler_locations(randomized_locations, options, content) for location_data in randomized_locations: location_collector(location_data.name, location_data.code, location_data.region) @@ -538,21 +747,34 @@ def filter_deprecated_locations(locations: Iterable[LocationData]) -> Iterable[L return [location for location in locations if LocationTags.DEPRECATED not in location.tags] -def filter_farm_type(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: +def filter_animals_quest(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: # On Meadowlands, "Feeding Animals" replaces "Raising Animals" if options.farm_type == FarmType.option_meadowlands: - return (location for location in locations if location.name != Quest.raising_animals) + return (location for location in locations if location.name != f"Quest: {Quest.raising_animals}") else: - return (location for location in locations if location.name != Quest.feeding_animals) + return (location for location in locations if location.name != f"Quest: {Quest.feeding_animals}") + + +def filter_farm_exclusives(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: + # Some locations are only on specific farms + if options.farm_type != FarmType.option_beach: + return (location for location in locations if LocationTags.BEACH_FARM not in location.tags) + return locations + + +def filter_farm_type(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: + animals_filter = filter_animals_quest(options, locations) + exclusives_filter = filter_farm_exclusives(options, animals_filter) + return exclusives_filter -def filter_ginger_island(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: - include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false +def filter_ginger_island(content: StardewContent, locations: Iterable[LocationData]) -> Iterable[LocationData]: + include_island = content.is_enabled(ginger_island_content_pack) return (location for location in locations if include_island or LocationTags.GINGER_ISLAND not in location.tags) -def filter_qi_order_locations(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: - include_qi_orders = options.special_order_locations & SpecialOrderLocations.value_qi +def filter_qi_order_locations(content: StardewContent, locations: Iterable[LocationData]) -> Iterable[LocationData]: + include_qi_orders = content.is_enabled(qi_board_content_pack) return (location for location in locations if include_qi_orders or LocationTags.REQUIRES_QI_ORDERS not in location.tags) @@ -563,15 +785,15 @@ def filter_masteries_locations(content: StardewContent, locations: Iterable[Loca return (location for location in locations if LocationTags.REQUIRES_MASTERIES not in location.tags) -def filter_modded_locations(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]: - return (location for location in locations if location.mod_name is None or location.mod_name in options.mods) +def filter_modded_locations(locations: Iterable[LocationData], content: StardewContent) -> Iterable[LocationData]: + return (location for location in locations if content.are_all_enabled(location.content_packs)) def filter_disabled_locations(options: StardewValleyOptions, content: StardewContent, locations: Iterable[LocationData]) -> Iterable[LocationData]: locations_deprecated_filter = filter_deprecated_locations(locations) locations_farm_filter = filter_farm_type(options, locations_deprecated_filter) - locations_island_filter = filter_ginger_island(options, locations_farm_filter) - locations_qi_filter = filter_qi_order_locations(options, locations_island_filter) + locations_island_filter = filter_ginger_island(content, locations_farm_filter) + locations_qi_filter = filter_qi_order_locations(content, locations_island_filter) locations_masteries_filter = filter_masteries_locations(content, locations_qi_filter) - locations_mod_filter = filter_modded_locations(options, locations_masteries_filter) + locations_mod_filter = filter_modded_locations(locations_masteries_filter, content) return locations_mod_filter diff --git a/worlds/stardew_valley/logic/ability_logic.py b/worlds/stardew_valley/logic/ability_logic.py index 52dbd5abaf27..51c49c144a34 100644 --- a/worlds/stardew_valley/logic/ability_logic.py +++ b/worlds/stardew_valley/logic/ability_logic.py @@ -1,8 +1,10 @@ from .base_logic import BaseLogicMixin, BaseLogic -from ..stardew_rule import StardewRule +from ..options import FarmType +from ..stardew_rule import StardewRule, False_, True_ +from ..strings.ap_names.ap_option_names import CustomLogicOptionName from ..strings.region_names import Region from ..strings.skill_names import Skill, ModSkill -from ..strings.tool_names import ToolMaterial, Tool +from ..strings.tool_names import ToolMaterial, Tool, FishingRod class AbilityLogicMixin(BaseLogicMixin): @@ -12,6 +14,13 @@ def __init__(self, *args, **kwargs): class AbilityLogic(BaseLogic): + + def can_mine_stone(self) -> StardewRule: + can_reach_any_mining_region = self.logic.region.can_reach_any(Region.mines, Region.skull_cavern, Region.volcano, Region.quarry_mine) + if self.options.farm_type in [FarmType.option_hill_top, FarmType.option_four_corners]: + can_reach_any_mining_region = can_reach_any_mining_region | self.logic.region.can_reach(Region.farm) + return self.logic.tool.has_tool(Tool.pickaxe) & can_reach_any_mining_region + def can_mine_perfectly(self) -> StardewRule: return self.logic.mine.can_progress_in_the_mines_from_floor(160) @@ -20,15 +29,17 @@ def can_mine_perfectly_in_the_skull_cavern(self) -> StardewRule: self.logic.region.can_reach(Region.skull_cavern)) def can_farm_perfectly(self) -> StardewRule: - tool_rule = self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iridium) & self.logic.tool.can_water(4) + tool_rule = self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iridium) & self.logic.tool.can_water(5) return tool_rule & self.logic.skill.has_farming_level(10) def can_fish_perfectly(self) -> StardewRule: skill_rule = self.logic.skill.has_level(Skill.fishing, 10) - return skill_rule & self.logic.tool.has_fishing_rod(4) + return skill_rule & self.logic.tool.has_fishing_rod(FishingRod.iridium) def can_chop_trees(self) -> StardewRule: - return self.logic.tool.has_tool(Tool.axe) & self.logic.region.can_reach(Region.forest) + can_reach_any_tree_region = self.logic.region.can_reach_any(Region.forest, Region.backwoods, Region.bus_stop, Region.mountain, Region.desert, + Region.island_west, Region.island_north) + return self.logic.tool.has_tool(Tool.axe) & can_reach_any_tree_region def can_chop_perfectly(self) -> StardewRule: magic_rule = (self.logic.magic.can_use_clear_debris_instead_of_tool_level(3)) & self.logic.mod.skill.has_mod_level(ModSkill.magic, 10) @@ -36,3 +47,20 @@ def can_chop_perfectly(self) -> StardewRule: foraging_rule = self.logic.skill.has_level(Skill.foraging, 10) region_rule = self.logic.region.can_reach(Region.forest) return region_rule & ((tool_rule & foraging_rule) | magic_rule) + + def can_scythe_vines(self) -> StardewRule: + can_reach_any_vine_region = self.logic.region.can_reach_any(Region.forest, Region.railroad) + return self.logic.tool.has_scythe() & can_reach_any_vine_region & self.logic.season.has_any_not_winter() + + def can_chair_skip(self) -> StardewRule: + if CustomLogicOptionName.chair_skips not in self.options.custom_logic: + return False_() + + if CustomLogicOptionName.critical_free_samples in self.options.custom_logic: + if self.options.farm_type == FarmType.option_standard or \ + self.options.farm_type == FarmType.option_riverland or \ + self.options.farm_type == FarmType.option_forest or \ + self.options.farm_type == FarmType.option_beach: + return True_() + + return self.logic.money.can_spend_at(Region.carpenter, 350) diff --git a/worlds/stardew_valley/logic/action_logic.py b/worlds/stardew_valley/logic/action_logic.py index 64cb18c001bf..711ffd2b5893 100644 --- a/worlds/stardew_valley/logic/action_logic.py +++ b/worlds/stardew_valley/logic/action_logic.py @@ -3,8 +3,10 @@ from ..stardew_rule import StardewRule, True_ from ..strings.generic_names import Generic from ..strings.geode_names import Geode +from ..strings.metal_names import Mineral from ..strings.region_names import Region -from ..strings.tool_names import Tool +from ..strings.season_names import Season +from ..strings.tv_channel_names import Channel class ActionLogicMixin(BaseLogicMixin): @@ -19,10 +21,13 @@ def can_watch(self, channel: str = None): tv_rule = True_() if channel is None: return tv_rule + if channel == Channel.sinister_signal: + sacrifice_rule = self.logic.relationship.has_children(1) & self.logic.region.can_reach(Region.witch_hut) & self.logic.has(Mineral.prismatic_shard) + return self.logic.received(channel) & tv_rule & sacrifice_rule & self.logic.season.has(Season.fall) return self.logic.received(channel) & tv_rule def can_pan_at(self, region: str, material: str) -> StardewRule: - return self.logic.region.can_reach(region) & self.logic.tool.has_tool(Tool.pan, material) + return self.logic.region.can_reach(region) & self.logic.tool.has_pan(material) @cache_self1 def can_open_geode(self, geode: str) -> StardewRule: @@ -31,3 +36,6 @@ def can_open_geode(self, geode: str) -> StardewRule: if geode == Generic.any: return blacksmith_access & self.logic.or_(*(self.logic.has(geode_type) for geode_type in geodes)) return blacksmith_access & self.logic.has(geode) + + def can_speak_junimo(self) -> StardewRule: + return self.logic.received("Forest Magic") diff --git a/worlds/stardew_valley/logic/animal_logic.py b/worlds/stardew_valley/logic/animal_logic.py index 701cdeb1aab4..57b54c26d458 100644 --- a/worlds/stardew_valley/logic/animal_logic.py +++ b/worlds/stardew_valley/logic/animal_logic.py @@ -1,5 +1,3 @@ -import typing - from .base_logic import BaseLogicMixin, BaseLogic from ..stardew_rule import StardewRule from ..strings.building_names import Building diff --git a/worlds/stardew_valley/logic/artisan_logic.py b/worlds/stardew_valley/logic/artisan_logic.py index 93c45530af73..8748f1a16cb0 100644 --- a/worlds/stardew_valley/logic/artisan_logic.py +++ b/worlds/stardew_valley/logic/artisan_logic.py @@ -2,9 +2,12 @@ from ..data.artisan import MachineSource from ..data.game_item import ItemTag from ..stardew_rule import StardewRule +from ..strings.animal_product_names import AnimalProduct from ..strings.artisan_good_names import ArtisanGood +from ..strings.building_names import Building from ..strings.crop_names import Vegetable, Fruit from ..strings.fish_names import Fish, all_fish +from ..strings.flower_names import all_flowers from ..strings.forageable_names import Mushroom from ..strings.generic_names import Generic from ..strings.machine_names import Machine @@ -21,6 +24,9 @@ def initialize_rules(self): # TODO remove this one too once fish are converted to sources self.registry.artisan_good_rules.update({ArtisanGood.specific_smoked_fish(fish): self.can_smoke(fish) for fish in all_fish}) self.registry.artisan_good_rules.update({ArtisanGood.specific_bait(fish): self.can_bait(fish) for fish in all_fish}) + self.registry.artisan_good_rules.update({AnimalProduct.specific_roe(fish): self.can_get_roe(fish) for fish in all_fish}) + self.registry.artisan_good_rules.update({ArtisanGood.specific_aged_roe(fish): self.can_preserves_jar(AnimalProduct.specific_roe(fish)) for fish in all_fish}) + self.registry.artisan_good_rules.update({ArtisanGood.specific_honey(flower): self.can_get_honey(flower) for flower in all_flowers}) def has_jelly(self) -> StardewRule: return self.logic.artisan.can_preserves_jar(Fruit.any) @@ -73,6 +79,10 @@ def can_smoke(self, item: str) -> StardewRule: machine_rule = self.logic.has(Machine.fish_smoker) return machine_rule & self.logic.has(item) + def can_get_roe(self, item: str) -> StardewRule: + machine_rule = self.logic.building.has_building(Building.fish_pond) + return machine_rule & self.logic.has(item) + def can_bait(self, item: str) -> StardewRule: machine_rule = self.logic.has(Machine.bait_maker) return machine_rule & self.logic.has(item) @@ -87,3 +97,11 @@ def can_dehydrate(self, item: str) -> StardewRule: if item == Mushroom.any_edible: return machine_rule & self.logic.has_any(*(mushroom.name for mushroom in self.content.find_tagged_items(ItemTag.EDIBLE_MUSHROOM))) return machine_rule & self.logic.has(item) + + def can_get_honey(self, flower: str) -> StardewRule: + machine_rule = self.logic.has(Machine.bee_house) + flower_rule = self.logic.has(flower) + return machine_rule & flower_rule + + def can_replicate_gem(self, gem: str) -> StardewRule: + return self.logic.has(Machine.crystalarium) & self.logic.has(gem) diff --git a/worlds/stardew_valley/logic/base_logic.py b/worlds/stardew_valley/logic/base_logic.py index dce1c328a7bf..735893c4c37d 100644 --- a/worlds/stardew_valley/logic/base_logic.py +++ b/worlds/stardew_valley/logic/base_logic.py @@ -25,6 +25,8 @@ def __init__(self): self.festival_rules: Dict[str, StardewRule] = {} self.quest_rules: Dict[str, StardewRule] = {} self.special_order_rules: Dict[str, StardewRule] = {} + self.meme_item_rules: Dict[str, StardewRule] = {} + self.shirt_rules: Dict[str, StardewRule] = {} self.sve_location_rules: Dict[str, StardewRule] = {} diff --git a/worlds/stardew_valley/logic/building_logic.py b/worlds/stardew_valley/logic/building_logic.py index 0d96f216e006..c896ffa0465e 100644 --- a/worlds/stardew_valley/logic/building_logic.py +++ b/worlds/stardew_valley/logic/building_logic.py @@ -2,8 +2,13 @@ from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from ..stardew_rule import StardewRule, true_ -from ..strings.building_names import Building +from ..stardew_rule import StardewRule, true_, false_ +from ..strings.building_names import Building, WizardBuilding +from ..strings.crop_names import Fruit +from ..strings.fish_names import Fish, WaterItem +from ..strings.forageable_names import Forageable +from ..strings.material_names import Material +from ..strings.metal_names import MetalBar, Mineral from ..strings.region_names import Region AUTO_BUILDING_BUILDINGS = {Building.shipping_bin, Building.pet_bowl, Building.farm_house} @@ -41,12 +46,33 @@ def has_building(self, building_name: str) -> StardewRule: # Those buildings are special. The mod auto-builds them when received, no need to go to Robin. if building_name in AUTO_BUILDING_BUILDINGS: - return self.logic.received(Building.shipping_bin) + return self.logic.received(building_name) carpenter_rule = self.logic.building.can_construct_buildings item, count = building_progression.to_progressive_item(building_name) return self.logic.received(item, count) & carpenter_rule + @cache_self1 + def has_wizard_building(self, building_name: str) -> StardewRule: + return self.logic.region.can_reach(Region.wizard_tower) & self.logic.received(building_name) + @cached_property def can_construct_buildings(self) -> StardewRule: return self.logic.region.can_reach(Region.carpenter) + + def can_purchase_wizard_blueprint(self, building_name: str) -> StardewRule: + # This rule is part of the region, so not needed here + # rule = self.logic.region.can_reach(Region.wizard_tower) & self.logic.quest.has_magic_ink() + if building_name == WizardBuilding.earth_obelisk: + return self.logic.money.can_spend(500_000) & self.logic.has_all(MetalBar.iridium, Mineral.earth_crystal) + if building_name == WizardBuilding.water_obelisk: + return self.logic.money.can_spend(500_000) & self.logic.has_all(MetalBar.iridium, Fish.clam, WaterItem.coral) + if building_name == WizardBuilding.desert_obelisk: + return self.logic.money.can_spend(1_000_000) & self.logic.has_all(MetalBar.iridium, Forageable.coconut, Forageable.cactus_fruit) + if building_name == WizardBuilding.island_obelisk: + return self.logic.money.can_spend(1_000_000) & self.logic.has_all(MetalBar.iridium, Forageable.dragon_tooth, Fruit.banana) + if building_name == WizardBuilding.junimo_hut: + return self.logic.money.can_spend(20_000) & self.logic.has_all(MetalBar.iridium, Material.stone, Fruit.starfruit, Material.fiber) + if building_name == WizardBuilding.gold_clock: + return self.logic.money.can_spend(10_000_000) + return false_ diff --git a/worlds/stardew_valley/logic/bundle_logic.py b/worlds/stardew_valley/logic/bundle_logic.py index 9af91c731c8e..856308b73a41 100644 --- a/worlds/stardew_valley/logic/bundle_logic.py +++ b/worlds/stardew_valley/logic/bundle_logic.py @@ -5,6 +5,8 @@ from ..bundles.bundle import Bundle from ..stardew_rule import StardewRule, True_ from ..strings.ap_names.community_upgrade_names import CommunityUpgrade +from ..strings.building_names import Building +from ..strings.bundle_names import MemeBundleName from ..strings.currency_names import Currency from ..strings.machine_names import Machine from ..strings.quality_names import CropQuality, ForageQuality, FishQuality, ArtisanQuality @@ -24,19 +26,31 @@ def can_complete_bundle(self, bundle: Bundle) -> StardewRule: item_rules = [] qualities = [] time_to_grind = 0 - can_speak_junimo = self.logic.region.can_reach(Region.wizard_tower) + building_rule = self.logic.true_ + can_speak_junimo = self.logic.action.can_speak_junimo() + number_items_required = bundle.number_required for bundle_item in bundle.items: - if Currency.is_currency(bundle_item.get_item()): - return can_speak_junimo & self.logic.money.can_trade(bundle_item.get_item(), bundle_item.amount) - - item_rules.append(bundle_item.get_item()) + item = bundle_item.get_item() + if Currency.is_currency(item): + return can_speak_junimo & self.logic.money.can_trade(item, bundle_item.amount) + if item == Building.well: + building_rule = self.logic.building.has_building(item) + number_items_required -= 1 + else: + item_rules.append(item) if bundle_item.amount > 50: time_to_grind = bundle_item.amount // 50 qualities.append(bundle_item.quality) quality_rules = self.get_quality_rules(qualities) - item_rules = self.logic.has_n(*item_rules, count=bundle.number_required) + item_rules = self.logic.has_n(*item_rules, count=number_items_required) time_rule = self.logic.time.has_lived_months(time_to_grind) - return can_speak_junimo & item_rules & quality_rules & time_rule + special_rule = self.get_special_bundle_requirement(bundle) + return can_speak_junimo & item_rules & quality_rules & time_rule & building_rule & special_rule + + def get_special_bundle_requirement(self, bundle: Bundle) -> StardewRule: + if bundle.name == MemeBundleName.pomnut: + return self.logic.building.has_building(Building.stable) + return self.logic.true_ def get_quality_rules(self, qualities: List[str]) -> StardewRule: crop_quality = CropQuality.get_highest(qualities) @@ -72,3 +86,8 @@ def can_access_raccoon_bundles(self) -> StardewRule: # 1 - Break the tree # 2 - Build the house, which summons the bundle racoon. This one is done manually if quests are turned off return self.logic.received(CommunityUpgrade.raccoon, 2) + + def can_feed_trash_bear(self, *items: str) -> StardewRule: + return (self.logic.received("Trash Bear Arrival") & + self.logic.region.can_reach(Region.forest) & + self.logic.has_all(*items)) diff --git a/worlds/stardew_valley/logic/combat_logic.py b/worlds/stardew_valley/logic/combat_logic.py index 14e8978de222..eb964437e339 100644 --- a/worlds/stardew_valley/logic/combat_logic.py +++ b/worlds/stardew_valley/logic/combat_logic.py @@ -2,9 +2,13 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic +from ..options import Monstersanity from ..stardew_rule import StardewRule, False_ from ..strings.ap_names.ap_weapon_names import APWeapon +from ..strings.ap_names.event_names import Event +from ..strings.boot_names import tier_by_boots from ..strings.performance_names import Performance +from ..strings.region_names import Region valid_weapons = (APWeapon.weapon, APWeapon.sword, APWeapon.club, APWeapon.dagger) @@ -34,24 +38,31 @@ def can_fight_at_level(self, level: str) -> StardewRule: @cached_property def has_any_weapon(self) -> StardewRule: - return self.logic.received_any(*valid_weapons) + return self.logic.received(Event.received_progressive_weapon) @cached_property def has_decent_weapon(self) -> StardewRule: - return self.logic.or_(*(self.logic.received(weapon, 2) for weapon in valid_weapons)) + return self.logic.received(Event.received_progressive_weapon, 2) @cached_property def has_good_weapon(self) -> StardewRule: - return self.logic.or_(*(self.logic.received(weapon, 3) for weapon in valid_weapons)) + return self.logic.received(Event.received_progressive_weapon, 3) @cached_property def has_great_weapon(self) -> StardewRule: - return self.logic.or_(*(self.logic.received(weapon, 4) for weapon in valid_weapons)) + return self.logic.received(Event.received_progressive_weapon, 4) @cached_property def has_galaxy_weapon(self) -> StardewRule: - return self.logic.or_(*(self.logic.received(weapon, 5) for weapon in valid_weapons)) + return self.logic.received(Event.received_progressive_weapon, 5) @cached_property def has_slingshot(self) -> StardewRule: return self.logic.received(APWeapon.slingshot) + + @cache_self1 + def has_specific_boots(self, boots: str) -> StardewRule: + tier = tier_by_boots[boots] + if tier >= 4 and self.options.monstersanity == Monstersanity.option_none: + tier = 3 # no tier 4 boots in the pool, instead tier 4 boots can be purchased after tier 3 is received + return self.logic.received(APWeapon.footwear, tier) & self.logic.region.can_reach(Region.adventurer_guild) diff --git a/worlds/stardew_valley/logic/cooking_logic.py b/worlds/stardew_valley/logic/cooking_logic.py index 0959b90a8fd2..1d4e6779a841 100644 --- a/worlds/stardew_valley/logic/cooking_logic.py +++ b/worlds/stardew_valley/logic/cooking_logic.py @@ -3,13 +3,14 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \ - QueenOfSauceSource, CookingRecipe, ShopFriendshipSource + QueenOfSauceSource, CookingRecipe, ShopFriendshipSource, all_cooking_recipes from ..data.recipe_source import CutsceneSource, ShopTradeSource from ..options import Chefsanity from ..stardew_rule import StardewRule, True_, False_ +from ..strings.ap_names.ap_option_names import ChefsanityOptionName from ..strings.building_names import Building +from ..strings.craftable_names import Craftable from ..strings.region_names import LogicRegion -from ..strings.skill_names import Skill from ..strings.tv_channel_names import Channel @@ -22,13 +23,15 @@ def __init__(self, *args, **kwargs): class CookingLogic(BaseLogic): @cached_property def can_cook_in_kitchen(self) -> StardewRule: - return self.logic.building.has_building(Building.kitchen) | self.logic.skill.has_level(Skill.foraging, 9) + return self.logic.building.has_building(Building.kitchen) | self.logic.has(Craftable.cookout_kit) # Should be cached - def can_cook(self, recipe: CookingRecipe = None) -> StardewRule: + def can_cook(self, recipe: CookingRecipe | str = None) -> StardewRule: cook_rule = self.logic.region.can_reach(LogicRegion.kitchen) if recipe is None: return cook_rule + if isinstance(recipe, str): + recipe = next(filter(lambda x: x.meal == recipe, all_cooking_recipes)) recipe_rule = self.logic.cooking.knows_recipe(recipe.source, recipe.meal) ingredients_rule = self.logic.has_all(*sorted(recipe.ingredients)) @@ -36,23 +39,23 @@ def can_cook(self, recipe: CookingRecipe = None) -> StardewRule: # Should be cached def knows_recipe(self, source: RecipeSource, meal_name: str) -> StardewRule: - if self.options.chefsanity == Chefsanity.option_none: + if self.options.chefsanity == Chefsanity.preset_none: return self.logic.cooking.can_learn_recipe(source) if isinstance(source, StarterSource): return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, ShopTradeSource) and self.options.chefsanity & Chefsanity.option_purchases: + if isinstance(source, ShopTradeSource) and ChefsanityOptionName.purchases in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, ShopSource) and self.options.chefsanity & Chefsanity.option_purchases: + if isinstance(source, ShopSource) and ChefsanityOptionName.purchases in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, SkillSource) and self.options.chefsanity & Chefsanity.option_skills: + if isinstance(source, SkillSource) and ChefsanityOptionName.skills in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, CutsceneSource) and self.options.chefsanity & Chefsanity.option_friendship: + if isinstance(source, CutsceneSource) and ChefsanityOptionName.friendship in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, FriendshipSource) and self.options.chefsanity & Chefsanity.option_friendship: + if isinstance(source, FriendshipSource) and ChefsanityOptionName.friendship in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, QueenOfSauceSource) and self.options.chefsanity & Chefsanity.option_queen_of_sauce: + if isinstance(source, QueenOfSauceSource) and ChefsanityOptionName.queen_of_sauce in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) - if isinstance(source, ShopFriendshipSource) and self.options.chefsanity & Chefsanity.option_purchases: + if isinstance(source, ShopFriendshipSource) and ChefsanityOptionName.purchases in self.options.chefsanity: return self.logic.cooking.received_recipe(meal_name) return self.logic.cooking.can_learn_recipe(source) @@ -79,3 +82,14 @@ def can_learn_recipe(self, source: RecipeSource) -> StardewRule: @cache_self1 def received_recipe(self, meal_name: str): return self.logic.received(f"{meal_name} Recipe") + + def can_have_cooked_recipes(self, number: int) -> StardewRule: + if number <= 0: + return self.logic.true_ + recipe_rules = [] + for recipe in all_cooking_recipes: + if recipe.content_pack and not self.content.is_enabled(recipe.content_pack): + continue + recipe_rules.append(self.can_cook(recipe)) + number = min(len(recipe_rules), number) + return self.logic.count(number, *recipe_rules) diff --git a/worlds/stardew_valley/logic/crafting_logic.py b/worlds/stardew_valley/logic/crafting_logic.py index 01dfc5173cb0..7b9059b573e9 100644 --- a/worlds/stardew_valley/logic/crafting_logic.py +++ b/worlds/stardew_valley/logic/crafting_logic.py @@ -1,9 +1,9 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic from .. import options -from ..data.craftable_data import CraftingRecipe +from ..data.craftable_data import CraftingRecipe, all_crafting_recipes, all_crafting_recipes_by_name from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \ - FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource + FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource, ShopWithKnownRecipeSource from ..options import Craftsanity, SpecialOrderLocations from ..stardew_rule import StardewRule, True_, False_ from ..strings.region_names import Region @@ -55,6 +55,8 @@ def can_learn_recipe(self, recipe: CraftingRecipe) -> StardewRule: return self.logic.received_all(*recipe.source.ap_item) if isinstance(recipe.source, ShopTradeSource): return self.logic.money.can_trade_at(recipe.source.region, recipe.source.currency, recipe.source.price) + if isinstance(recipe.source, ShopWithKnownRecipeSource): + return self.knows_recipe(all_crafting_recipes_by_name[recipe.source.recipe_required]) & self.logic.money.can_spend_at(recipe.source.region, recipe.source.price) if isinstance(recipe.source, ShopSource): return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price) if isinstance(recipe.source, SkillCraftsanitySource): @@ -83,3 +85,14 @@ def can_learn_recipe(self, recipe: CraftingRecipe) -> StardewRule: @cache_self1 def received_recipe(self, item_name: str): return self.logic.received(f"{item_name} Recipe") + + def can_have_crafted_recipes(self, number: int) -> StardewRule: + if number <= 0: + return self.logic.true_ + recipe_rules = [] + for recipe in all_crafting_recipes: + if recipe.content_pack is not None and not self.content.are_all_enabled(recipe.content_pack): + continue + recipe_rules.append(self.can_craft(recipe)) + number = min(len(recipe_rules), number) + return self.logic.count(number, *recipe_rules) diff --git a/worlds/stardew_valley/logic/farming_logic.py b/worlds/stardew_valley/logic/farming_logic.py index 54c8c8af20e8..e0f5f34ccdf5 100644 --- a/worlds/stardew_valley/logic/farming_logic.py +++ b/worlds/stardew_valley/logic/farming_logic.py @@ -3,8 +3,10 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .. import options -from ..stardew_rule import StardewRule, True_, false_ +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..stardew_rule import StardewRule +from ..strings.ap_names.ap_option_names import CustomLogicOptionName +from ..strings.craftable_names import Bomb from ..strings.fertilizer_names import Fertilizer from ..strings.region_names import Region, LogicRegion from ..strings.season_names import Season @@ -27,17 +29,30 @@ def __init__(self, *args, **kwargs): class FarmingLogic(BaseLogic): @cached_property - def has_farming_tools(self) -> StardewRule: - return self.logic.tool.has_tool(Tool.hoe) & self.logic.tool.can_water(0) + def has_farming_tools_and_water(self) -> StardewRule: + if CustomLogicOptionName.rain_watering in self.options.custom_logic: + return self.has_hoeing_tool + return self.has_hoeing_tool & self.logic.tool.can_water() + + @cached_property + def has_farming_and_watering_tools(self) -> StardewRule: + return self.has_hoeing_tool & self.logic.tool.can_water() + + @cached_property + def has_hoeing_tool(self) -> StardewRule: + if CustomLogicOptionName.bomb_hoeing in self.options.custom_logic: + return self.logic.tool.has_tool(Tool.hoe) | self.logic.has_any(Bomb.cherry_bomb, Bomb.bomb, Bomb.mega_bomb) + return self.logic.tool.has_tool(Tool.hoe) def has_fertilizer(self, tier: int) -> StardewRule: - if tier <= 0: - return True_() + assert 0 <= tier <= 3 + if tier == 0: + return self.logic.true_ if tier == 1: return self.logic.has(Fertilizer.basic) if tier == 2: return self.logic.has(Fertilizer.quality) - if tier >= 3: + if tier == 3: return self.logic.has(Fertilizer.deluxe) return self.logic.false_ @@ -45,7 +60,8 @@ def has_fertilizer(self, tier: int) -> StardewRule: @cache_self1 def can_plant_and_grow_item(self, seasons: Union[str, Tuple[str]]) -> StardewRule: if seasons == (): # indoor farming - return (self.logic.region.can_reach(Region.greenhouse) | self.logic.farming.has_island_farm()) & self.logic.farming.has_farming_tools + return (self.logic.region.can_reach(Region.greenhouse) & self.logic.farming.has_farming_and_watering_tools) |\ + (self.logic.farming.has_island_farm() & self.logic.farming.has_farming_tools_and_water) if isinstance(seasons, str): seasons = (seasons,) @@ -53,6 +69,6 @@ def can_plant_and_grow_item(self, seasons: Union[str, Tuple[str]]) -> StardewRul return self.logic.or_(*(self.logic.region.can_reach(farming_region_by_season[season]) for season in seasons)) def has_island_farm(self) -> StardewRule: - if self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_false: + if self.content.is_enabled(ginger_island_content_pack): return self.logic.region.can_reach(Region.island_west) - return false_ + return self.logic.false_ diff --git a/worlds/stardew_valley/logic/festival_logic.py b/worlds/stardew_valley/logic/festival_logic.py index 72efffe83a2c..5ea0b986bab7 100644 --- a/worlds/stardew_valley/logic/festival_logic.py +++ b/worlds/stardew_valley/logic/festival_logic.py @@ -3,15 +3,16 @@ from ..stardew_rule import StardewRule from ..strings.animal_product_names import AnimalProduct from ..strings.book_names import Book -from ..strings.craftable_names import Fishing from ..strings.crop_names import Fruit, Vegetable from ..strings.festival_check_names import FestivalCheck from ..strings.fish_names import Fish from ..strings.forageable_names import Forageable from ..strings.generic_names import Generic +from ..strings.gift_names import Gift from ..strings.machine_names import Machine from ..strings.monster_names import Monster from ..strings.region_names import Region +from ..strings.season_names import Season class FestivalLogicMixin(BaseLogicMixin): @@ -42,7 +43,7 @@ def initialize_rules(self): FestivalCheck.rarecrow_2: self.logic.money.can_spend(5000), FestivalCheck.fishing_competition: self.logic.festival.can_win_fishing_competition(), FestivalCheck.rarecrow_4: self.logic.money.can_spend(5000), - FestivalCheck.mermaid_pearl: self.logic.has(Forageable.secret_note), + FestivalCheck.mermaid_show: self.logic.true_, FestivalCheck.cone_hat: self.logic.money.can_spend(2500), FestivalCheck.iridium_fireplace: self.logic.money.can_spend(15000), FestivalCheck.rarecrow_7: self.logic.money.can_spend(5000) & self.logic.museum.can_donate_museum_artifacts(20), @@ -94,25 +95,24 @@ def initialize_rules(self): FestivalCheck.willy_challenge: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.scorpion_carp]), FestivalCheck.desert_scholar: self.logic.true_, FestivalCheck.squidfest_day_1_copper: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]), - FestivalCheck.squidfest_day_1_iron: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.has(Fishing.bait), - FestivalCheck.squidfest_day_1_gold: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.has(Fishing.deluxe_bait), - FestivalCheck.squidfest_day_1_iridium: self.logic.festival.can_squidfest_day_1_iridium_reward(), + FestivalCheck.squidfest_day_1_iron: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.fishing.can_use_any_bait(), + FestivalCheck.squidfest_day_1_gold: self.logic.festival.can_squidfest_iridium_reward(), + FestivalCheck.squidfest_day_1_iridium: self.logic.festival.can_squidfest_iridium_reward(), FestivalCheck.squidfest_day_2_copper: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]), - FestivalCheck.squidfest_day_2_iron: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.has(Fishing.bait), - FestivalCheck.squidfest_day_2_gold: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.has(Fishing.deluxe_bait), - FestivalCheck.squidfest_day_2_iridium: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & - self.logic.fishing.has_specific_bait(self.content.fishes[Fish.squid]), + FestivalCheck.squidfest_day_2_iron: self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.fishing.can_use_any_bait(), + FestivalCheck.squidfest_day_2_gold: self.logic.festival.can_squidfest_iridium_reward(), + FestivalCheck.squidfest_day_2_iridium: self.logic.festival.can_squidfest_iridium_reward(), }) for i in range(1, 11): check_name = f"{FestivalCheck.trout_derby_reward_pattern}{i}" self.registry.festival_rules[check_name] = self.logic.fishing.can_catch_fish(self.content.fishes[Fish.rainbow_trout]) - def can_squidfest_day_1_iridium_reward(self) -> StardewRule: - return self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.fishing.has_specific_bait(self.content.fishes[Fish.squid]) + def can_squidfest_iridium_reward(self) -> StardewRule: + return self.logic.fishing.can_catch_fish(self.content.fishes[Fish.squid]) & self.logic.fishing.can_use_specific_bait(Fish.squid) def has_squidfest_day_1_iridium_reward(self) -> StardewRule: if self.options.festival_locations == FestivalLocations.option_disabled: - return self.logic.festival.can_squidfest_day_1_iridium_reward() + return self.logic.festival.can_squidfest_iridium_reward() else: return self.logic.received(f"Book: {Book.the_art_o_crabbing}") @@ -122,6 +122,9 @@ def can_win_egg_hunt(self) -> StardewRule: def can_succeed_luau_soup(self) -> StardewRule: if self.options.festival_locations != FestivalLocations.option_hard: return self.logic.true_ + return self.can_get_luau_soup_delight() + + def can_get_luau_soup_delight(self) -> StardewRule: eligible_fish = (Fish.blobfish, Fish.crimsonfish, Fish.ice_pip, Fish.lava_eel, Fish.legend, Fish.angler, Fish.catfish, Fish.glacierfish, Fish.mutant_carp, Fish.spookfish, Fish.stingray, Fish.sturgeon, Fish.super_cucumber) fish_rule = self.logic.has_any(*(f for f in eligible_fish if f in self.content.fishes)) # To filter stingray @@ -137,7 +140,9 @@ def can_succeed_luau_soup(self) -> StardewRule: def can_succeed_grange_display(self) -> StardewRule: if self.options.festival_locations != FestivalLocations.option_hard: return self.logic.true_ + return self.can_get_grange_display_max_score() + def can_get_grange_display_max_score(self) -> StardewRule: # Other animal products are not counted in the animal product category good_animal_products = [ AnimalProduct.duck_egg, AnimalProduct.duck_feather, AnimalProduct.egg, AnimalProduct.goat_milk, AnimalProduct.golden_egg, AnimalProduct.large_egg, @@ -157,7 +162,7 @@ def can_succeed_grange_display(self) -> StardewRule: fish_rule = self.logic.fishing.can_fish_anywhere(50) # Hazelnut always available since the grange display is in fall - forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods)) + forage_rule = self.logic.region.can_reach_any(Region.forest, Region.backwoods) # More than half the minerals are good enough mineral_rule = self.logic.action.can_open_geode(Generic.any) @@ -186,3 +191,8 @@ def has_all_rarecrows(self) -> StardewRule: for rarecrow_number in range(1, 9): rules.append(self.logic.received(f"Rarecrow #{rarecrow_number}")) return self.logic.and_(*rules) + + def has_golden_pumpkin(self) -> StardewRule: + if self.options.festival_locations == FestivalLocations.option_disabled: + return self.logic.season.has(Season.fall) + return self.logic.received(Gift.golden_pumpkin) & self.logic.season.has(Season.fall) diff --git a/worlds/stardew_valley/logic/fish_pond_logic.py b/worlds/stardew_valley/logic/fish_pond_logic.py new file mode 100644 index 000000000000..34aa2b8a7e0d --- /dev/null +++ b/worlds/stardew_valley/logic/fish_pond_logic.py @@ -0,0 +1,50 @@ +from .base_logic import BaseLogic, BaseLogicMixin +from ..data.fish_pond_data import fish_pond_quests +from ..stardew_rule import StardewRule +from ..strings.building_names import Building +from ..strings.fish_names import Fish + + +class FishPondLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fish_pond = FishPondLogic(*args, **kwargs) + + +class FishPondLogic(BaseLogic): + + def can_get_fish_pond_reward(self, fish: str, population: int, desired_item: str) -> StardewRule: + building_rule = self.logic.building.has_building(Building.fish_pond) + if fish == Fish.any: + return self.logic.fishing.can_fish_anywhere() & building_rule + + fish_rule = self.logic.has(fish) + + if population <= 1: + return building_rule & fish_rule + + assert fish in fish_pond_quests, f"Cannot raise the population of {fish} to {population} in a fish pond to get {desired_item} without knowing the required quest items" + # if fish not in fish_pond_quests: + # return building_rule & fish_rule + + item_rules = [] + fish_quests = fish_pond_quests[fish] + for i in range(0, population): + if i not in fish_quests: + continue + quests_for_that_level = fish_quests[i] + num_quests = len(quests_for_that_level) + if num_quests <= 0: + continue + + level_rules = [] + for quest_item in quests_for_that_level: + if quest_item == desired_item: + continue + level_rules.append(self.logic.has(quest_item)) + if num_quests <= 2: + item_rules.append(self.logic.count(num_quests, *level_rules)) + else: + item_rules.append(self.logic.count(num_quests - 1, *level_rules)) + + return building_rule & fish_rule & self.logic.and_(*item_rules) diff --git a/worlds/stardew_valley/logic/fishing_logic.py b/worlds/stardew_valley/logic/fishing_logic.py index 544f322057e9..7bf42928d221 100644 --- a/worlds/stardew_valley/logic/fishing_logic.py +++ b/worlds/stardew_valley/logic/fishing_logic.py @@ -2,10 +2,11 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic +from ..content.vanilla.qi_board import qi_board_content_pack from ..data import fish_data from ..data.fish_data import FishItem -from ..options import ExcludeGingerIsland, SpecialOrderLocations -from ..stardew_rule import StardewRule, True_, False_ +from ..stardew_rule import StardewRule, True_ +from ..strings.ap_names.ap_option_names import CustomLogicOptionName from ..strings.ap_names.mods.mod_items import SVEQuestItem from ..strings.craftable_names import Fishing from ..strings.fish_names import SVEFish @@ -13,8 +14,7 @@ from ..strings.quality_names import FishQuality from ..strings.region_names import Region from ..strings.skill_names import Skill - -fishing_regions = (Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west) +from ..strings.tool_names import FishingRod class FishingLogicMixin(BaseLogicMixin): @@ -24,48 +24,80 @@ def __init__(self, *args, **kwargs): class FishingLogic(BaseLogic): + + @cached_property + def can_reach_any_fishing_regions(self) -> StardewRule: + return self.logic.region.can_reach_any(Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west) + @cache_self1 def can_fish_anywhere(self, difficulty: int = 0) -> StardewRule: - return self.logic.fishing.can_fish(difficulty) & self.logic.region.can_reach_any(fishing_regions) + return self.logic.fishing.can_fish(difficulty) & self.logic.fishing.can_reach_any_fishing_regions def can_fish_in_freshwater(self) -> StardewRule: - return self.logic.fishing.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain)) + return self.logic.fishing.can_fish() & self.logic.region.can_reach_any(Region.forest, Region.town, Region.mountain) @cached_property def has_max_fishing(self) -> StardewRule: - return self.logic.tool.has_fishing_rod(4) & self.logic.skill.has_level(Skill.fishing, 10) + # Advanced Iridium is not necessary for max fishing + return self.logic.tool.has_fishing_rod(FishingRod.iridium) & self.logic.skill.has_level(Skill.fishing, 10) @cached_property def can_fish_chests(self) -> StardewRule: - return self.logic.tool.has_fishing_rod(4) & self.logic.skill.has_level(Skill.fishing, 6) + return self.logic.tool.has_fishing_rod(FishingRod.iridium) & self.logic.skill.has_level(Skill.fishing, 6) @cache_self1 def can_fish_at(self, region: str) -> StardewRule: return self.logic.fishing.can_fish() & self.logic.region.can_reach(region) - @cache_self1 - def can_fish(self, difficulty: int = 0) -> StardewRule: - skill_required = min(10, max(0, int((difficulty / 10) - 1))) + def can_fish_with_cast_distance(self, region: str, distance: int) -> StardewRule: + if distance >= 7: + required_levels = 15 + elif distance >= 6: + required_levels = 8 + elif distance >= 5: + required_levels = 4 + elif distance >= 4: + required_levels = 1 + else: + required_levels = 0 + return self.logic.fishing.can_fish_at(region) & self.logic.skill.has_level(Skill.fishing, required_levels) + + def can_fish(self, difficulty: int = 0, minimum_level: int = 0) -> StardewRule: + skill_required = int((difficulty / 10) - 1) if difficulty <= 40: skill_required = 0 + if CustomLogicOptionName.extreme_fishing in self.options.custom_logic: + skill_required -= 4 + elif CustomLogicOptionName.hard_fishing in self.options.custom_logic: + skill_required -= 2 + elif CustomLogicOptionName.easy_fishing in self.options.custom_logic and difficulty > 20: + skill_required += 2 + + skill_required = min(10, max(minimum_level, skill_required)) + skill_rule = self.logic.skill.has_level(Skill.fishing, skill_required) # Training rod only works with fish < 50. Fiberglass does not help you to catch higher difficulty fish, so it's skipped in logic. - number_fishing_rod_required = 1 if difficulty < 50 else (2 if difficulty < 80 else 4) - return self.logic.tool.has_fishing_rod(number_fishing_rod_required) & skill_rule + if difficulty < 50: + fishing_rod_required = FishingRod.training + elif difficulty < 80: + fishing_rod_required = FishingRod.bamboo + else: + fishing_rod_required = FishingRod.iridium + return self.logic.tool.has_fishing_rod(fishing_rod_required) & skill_rule @cache_self1 def can_catch_fish(self, fish: FishItem) -> StardewRule: quest_rule = True_() if fish.extended_family: quest_rule = self.logic.fishing.can_start_extended_family_quest() - region_rule = self.logic.region.can_reach_any(fish.locations) + region_rule = self.logic.region.can_reach_any(*fish.locations) season_rule = self.logic.season.has_any(fish.seasons) if fish.difficulty == -1: difficulty_rule = self.logic.fishing.can_crab_pot else: - difficulty_rule = self.logic.fishing.can_fish(120 if fish.legendary else fish.difficulty) + difficulty_rule = self.logic.fishing.can_fish(120 if fish.legendary else fish.difficulty, fish.minimum_level) if fish.name == SVEFish.kittyfish: item_rule = self.logic.received(SVEQuestItem.kittyfish_spell) @@ -80,18 +112,17 @@ def can_catch_fish_for_fishsanity(self, fish: FishItem) -> StardewRule: return self.logic.fishing.can_catch_fish(fish) def can_start_extended_family_quest(self) -> StardewRule: - if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: - return False_() - if not self.options.special_order_locations & SpecialOrderLocations.value_qi: - return False_() - return (self.logic.region.can_reach(Region.qi_walnut_room) & - self.logic.and_(*(self.logic.fishing.can_catch_fish(fish) for fish in fish_data.vanilla_legendary_fish))) + if self.content.is_enabled(qi_board_content_pack): + return (self.logic.region.can_reach(Region.qi_walnut_room) & + self.logic.and_(*(self.logic.fishing.can_catch_fish(fish) for fish in fish_data.vanilla_legendary_fish))) + + return self.logic.false_ def can_catch_quality_fish(self, fish_quality: str) -> StardewRule: if fish_quality == FishQuality.basic: - return True_() + return self.logic.true_ if fish_quality == FishQuality.silver: - return self.logic.tool.has_fishing_rod(2) + return self.logic.tool.has_fishing_rod(FishingRod.bamboo) if fish_quality == FishQuality.gold: return self.logic.skill.has_level(Skill.fishing, 4) & self.can_use_tackle(Fishing.quality_bobber) if fish_quality == FishQuality.iridium: @@ -100,7 +131,7 @@ def can_catch_quality_fish(self, fish_quality: str) -> StardewRule: raise ValueError(f"Quality {fish_quality} is unknown.") def can_use_tackle(self, tackle: str) -> StardewRule: - return self.logic.tool.has_fishing_rod(4) & self.logic.has(tackle) + return self.logic.tool.has_fishing_rod(FishingRod.iridium) & self.logic.has(tackle) def can_catch_every_fish(self) -> StardewRule: rules = [self.has_max_fishing] @@ -112,12 +143,27 @@ def can_catch_every_fish(self) -> StardewRule: return self.logic.and_(*rules) + def can_catch_many_fish(self, number: int) -> StardewRule: + rules = [ + self.logic.fishing.can_catch_fish(fish) + for fish in self.content.fishes.values() + ] + if number > len(rules): + number = len(rules) + return self.logic.count(number, *rules) + def has_specific_bait(self, fish: FishItem) -> StardewRule: return self.can_catch_fish(fish) & self.logic.has(Machine.bait_maker) + def can_use_specific_bait(self, fish_name: str) -> StardewRule: + return self.has_specific_bait(self.content.fishes[fish_name]) & self.logic.tool.has_fishing_rod(FishingRod.fiberglass) + + def can_use_any_bait(self) -> StardewRule: + return self.logic.has(Fishing.bait) & self.logic.tool.has_fishing_rod(FishingRod.fiberglass) + @cached_property def can_crab_pot_anywhere(self) -> StardewRule: - return self.logic.fishing.can_crab_pot & self.logic.region.can_reach_any(fishing_regions) + return self.logic.fishing.can_crab_pot & self.can_reach_any_fishing_regions @cache_self1 def can_crab_pot_at(self, region: str) -> StardewRule: diff --git a/worlds/stardew_valley/logic/gift_logic.py b/worlds/stardew_valley/logic/gift_logic.py index 11667783d696..7a460e12f19c 100644 --- a/worlds/stardew_valley/logic/gift_logic.py +++ b/worlds/stardew_valley/logic/gift_logic.py @@ -1,6 +1,7 @@ from functools import cached_property from .base_logic import BaseLogic, BaseLogicMixin +from ..data.secret_note_data import RequiredGifts from ..stardew_rule import StardewRule from ..strings.animal_product_names import AnimalProduct from ..strings.gift_names import Gift @@ -17,3 +18,32 @@ class GiftLogic(BaseLogic): @cached_property def has_any_universal_love(self) -> StardewRule: return self.logic.has_any(Gift.golden_pumpkin, Gift.pearl, "Prismatic Shard", AnimalProduct.rabbit_foot) + + def can_gift_to(self, npc: str, item: str) -> StardewRule: + return self.logic.relationship.can_meet(npc) & self.logic.has(item) + + def can_gift_any_to(self, npc: str, *items: str) -> StardewRule: + return self.logic.relationship.can_meet(npc) & self.logic.has_any(*items) + + def can_fulfill(self, required_gifts: RequiredGifts | list[RequiredGifts]) -> StardewRule: + if isinstance(required_gifts, RequiredGifts): + return self.can_gift_all_to(required_gifts) + return self.can_gift_all_to_all(required_gifts) + + def can_gift_all_to(self, required_gifts: RequiredGifts) -> StardewRule: + return self.logic.relationship.can_meet(required_gifts.npc) & self.logic.has_all(*required_gifts.gifts) + + def can_gift_all_to_all(self, required_gifts: list[RequiredGifts]) -> StardewRule: + items = [gift for required_gift in required_gifts for gift in required_gift.gifts] + return self.logic.relationship.can_meet_all(*[required_gift.npc for required_gift in required_gifts]) & self.logic.has_all(*items) + + def can_give_loved_gifts_to_everyone(self) -> StardewRule: + rules = [] + + for npc in self.content.villagers: + meet_rule = self.logic.relationship.can_meet(npc) + rules.append(meet_rule) + + rules.append(self.has_any_universal_love) + + return self.logic.and_(*rules) diff --git a/worlds/stardew_valley/logic/goal_logic.py b/worlds/stardew_valley/logic/goal_logic.py index 7995ea2e0ddc..ecc696408a2e 100644 --- a/worlds/stardew_valley/logic/goal_logic.py +++ b/worlds/stardew_valley/logic/goal_logic.py @@ -5,8 +5,11 @@ from ..mods.mod_data import ModNames from ..options import options from ..stardew_rule import StardewRule +from ..strings.ap_names.ap_option_names import SecretsanityOptionName from ..strings.building_names import Building +from ..strings.crop_names import Fruit from ..strings.quest_names import Quest +from ..strings.region_names import Region from ..strings.season_names import Season from ..strings.wallet_item_names import Wallet @@ -97,11 +100,8 @@ def can_complete_full_shipment(self, all_location_names_in_slot: list[str]) -> S def can_complete_gourmet_chef(self) -> StardewRule: cooksanity_prefix = "Cook " all_recipes_names = [] - exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true for location in locations_by_tag[LocationTags.COOKSANITY]: - if exclude_island and LocationTags.GINGER_ISLAND in location.tags: - continue - if location.mod_name and location.mod_name not in self.options.mods: + if not self.content.are_all_enabled(location.content_packs): continue all_recipes_names.append(location.name[len(cooksanity_prefix):]) all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names] @@ -110,17 +110,14 @@ def can_complete_gourmet_chef(self) -> StardewRule: def can_complete_craft_master(self) -> StardewRule: craftsanity_prefix = "Craft " all_recipes_names = [] - exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true exclude_masteries = not self.content.features.skill_progression.are_masteries_shuffled for location in locations_by_tag[LocationTags.CRAFTSANITY]: if not location.name.startswith(craftsanity_prefix): continue - if exclude_island and LocationTags.GINGER_ISLAND in location.tags: - continue # FIXME Remove when recipes are in content packs if exclude_masteries and LocationTags.REQUIRES_MASTERIES in location.tags: continue - if location.mod_name and location.mod_name not in self.options.mods: + if not self.content.are_all_enabled(location.content_packs): continue all_recipes_names.append(location.name[len(craftsanity_prefix):]) all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names] @@ -133,7 +130,6 @@ def can_complete_mystery_of_the_stardrop(self) -> StardewRule: other_rules = [] number_of_stardrops_to_receive = 0 number_of_stardrops_to_receive += 1 # The Mines level 100 - number_of_stardrops_to_receive += 1 # Old Master Cannoli number_of_stardrops_to_receive += 1 # Museum Stardrop number_of_stardrops_to_receive += 1 # Krobus Stardrop @@ -154,11 +150,41 @@ def can_complete_mystery_of_the_stardrop(self) -> StardewRule: else: other_rules.append(self.logic.relationship.has_hearts_with_any_bachelor(13)) - if ModNames.deepwoods in self.options.mods: # Petting the Unicorn + # Old Master Cannoli + if SecretsanityOptionName.easy in self.options.secretsanity: + number_of_stardrops_to_receive += 1 + else: + other_rules.append(self.logic.has(Fruit.sweet_gem_berry) & self.logic.region.can_reach(Region.secret_woods)) + + if self.content.is_enabled(ModNames.deepwoods): # Petting the Unicorn number_of_stardrops_to_receive += 1 return self.logic.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules, allow_empty=True) + def can_complete_mad_hatter(self, all_location_names_in_slot: list[str]) -> StardewRule: + if not self.content.features.hatsanity.is_enabled: + raise Exception("Cannot play Mad Hatter Goal without Hatsanity") + + rules = [] + + for hatsanity_location in locations_by_tag[LocationTags.HATSANITY]: + if hatsanity_location.name not in all_location_names_in_slot: + continue + rules.append(self.logic.region.can_reach_location(hatsanity_location.name)) + return self.logic.and_(*rules) + + def can_complete_ultimate_foodie(self, all_location_names_in_slot: list[str]) -> StardewRule: + if not self.options.eatsanity.value: + raise Exception("Cannot play Ultimate Foodie Goal without Eatsanity") + + rules = [] + + for eatsanity_location in locations_by_tag[LocationTags.EATSANITY]: + if eatsanity_location.name not in all_location_names_in_slot: + continue + rules.append(self.logic.region.can_reach_location(eatsanity_location.name)) + return self.logic.and_(*rules) + def can_complete_allsanity(self) -> StardewRule: return self.logic.has_progress_percent(100) diff --git a/worlds/stardew_valley/logic/grind_logic.py b/worlds/stardew_valley/logic/grind_logic.py index e18c13c15aab..74fa07c73d7c 100644 --- a/worlds/stardew_valley/logic/grind_logic.py +++ b/worlds/stardew_valley/logic/grind_logic.py @@ -1,12 +1,6 @@ -from typing import Union, TYPE_CHECKING - from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .book_logic import BookLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .time_logic import TimeLogicMixin +from ..options import ExcludeGingerIsland from ..stardew_rule import StardewRule, HasProgressionPercent from ..strings.book_names import Book from ..strings.craftable_names import Consumable @@ -17,11 +11,6 @@ from ..strings.region_names import Region from ..strings.tool_names import Tool -if TYPE_CHECKING: - from .tool_logic import ToolLogicMixin -else: - ToolLogicMixin = object - MIN_MEDIUM_ITEMS = 10 MAX_MEDIUM_ITEMS = 999 PERCENT_REQUIRED_FOR_MAX_MEDIUM_ITEM = 24 @@ -29,7 +18,7 @@ EASY_ITEMS = {Material.wood, Material.stone, Material.fiber, Material.sap} MIN_EASY_ITEMS = 300 MAX_EASY_ITEMS = 2997 -PERCENT_REQUIRED_FOR_MAX_EASY_ITEM = 6 +PERCENT_REQUIRED_FOR_MAX_EASY_ITEM = 8 class GrindLogicMixin(BaseLogicMixin): @@ -59,9 +48,8 @@ def can_grind_artifact_troves(self, quantity: int) -> StardewRule: def can_grind_prize_tickets(self, quantity: int) -> StardewRule: claiming_rule = self.logic.region.can_reach(Region.mayor_house) - return self.logic.and_(claiming_rule, self.logic.has(Currency.prize_ticket), - # Assuming two per month if the player does not grind it. - self.logic.time.has_lived_months(quantity // 2)) + help_wanted_rules = self.logic.quest.can_complete_help_wanteds(quantity * 3) + return self.logic.and_(claiming_rule, self.logic.has(Currency.prize_ticket), help_wanted_rules) def can_grind_fishing_treasure_chests(self, quantity: int) -> StardewRule: return self.logic.and_(self.logic.has(WaterChest.fishing_chest), @@ -73,12 +61,23 @@ def can_grind_artifact_spots(self, quantity: int) -> StardewRule: # Assuming twelve per month if the player does not grind it. self.logic.time.has_lived_months(quantity // 12)) + def can_grind_weeds(self, quantity: int) -> StardewRule: + regions = [Region.farm, Region.town, Region.forest, Region.secret_woods, Region.backwoods, Region.mountain, Region.railroad, Region.mutant_bug_lair] + if self.options.exclude_ginger_island == ExcludeGingerIsland.option_false: + regions.extend([Region.island_east, Region.island_west]) + return self.logic.and_(self.logic.tool.has_scythe(), + self.logic.region.can_reach_all(*regions), + # Assuming 1000 per month if the player does not grind it + self.logic.time.has_lived_months(quantity // 1000)) + def can_grind_item(self, quantity: int, item: str | None = None) -> StardewRule: - if item in EASY_ITEMS: - return self.logic.grind.can_grind_easy_item(quantity) - else: + if item is None: return self.logic.grind.can_grind_medium_item(quantity) + if item in EASY_ITEMS: + return self.logic.grind.can_grind_easy_item(quantity) & self.logic.has(item) + return self.logic.grind.can_grind_medium_item(quantity) & self.logic.has(item) + @cache_self1 def can_grind_medium_item(self, quantity: int) -> StardewRule: if quantity <= MIN_MEDIUM_ITEMS: diff --git a/worlds/stardew_valley/logic/harvesting_logic.py b/worlds/stardew_valley/logic/harvesting_logic.py index 6478e3495346..27226f6ec077 100644 --- a/worlds/stardew_valley/logic/harvesting_logic.py +++ b/worlds/stardew_valley/logic/harvesting_logic.py @@ -27,8 +27,14 @@ def can_harvest_from_mushroom_cave(self) -> StardewRule: @cache_self1 def can_forage_from(self, source: ForagingSource) -> StardewRule: seasons_rule = self.logic.season.has_any(source.seasons) - regions_rule = self.logic.region.can_reach_any(source.regions) - return seasons_rule & regions_rule + if source.require_all_regions: + regions_rule = self.logic.region.can_reach_all(*source.regions) + else: + regions_rule = self.logic.region.can_reach_any(*source.regions) + if source.grind_months == 0: + return seasons_rule & regions_rule + else: + return seasons_rule & regions_rule & self.logic.time.has_lived_months(source.grind_months) @cache_self1 def can_harvest_tree_from(self, source: HarvestFruitTreeSource) -> StardewRule: diff --git a/worlds/stardew_valley/logic/hats_logic.py b/worlds/stardew_valley/logic/hats_logic.py new file mode 100644 index 000000000000..be68923cffe2 --- /dev/null +++ b/worlds/stardew_valley/logic/hats_logic.py @@ -0,0 +1,28 @@ +from functools import cached_property + +from .base_logic import BaseLogicMixin, BaseLogic +from ..data.hats_data import HatItem +from ..stardew_rule import StardewRule +from ..strings.fish_names import Fish +from ..strings.region_names import LogicRegion + + +class HatLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.hat = HatLogic(*args, **kwargs) + + +class HatLogic(BaseLogic): + + @cached_property + def can_get_unlikely_hat_at_outfit_services(self) -> StardewRule: + return self.logic.region.can_reach(LogicRegion.desert_festival) & self.logic.time.has_lived_months(12) + + @cached_property + def has_bucket_hat(self) -> StardewRule: + trout_derby_rule = self.logic.region.can_reach(LogicRegion.trout_derby) & self.logic.fishing.can_catch_fish(self.content.fishes[Fish.rainbow_trout]) + return trout_derby_rule + + def can_wear(self, hat: HatItem) -> StardewRule: + return self.logic.has(hat.clarified_name) diff --git a/worlds/stardew_valley/logic/logic.py b/worlds/stardew_valley/logic/logic.py index 42bfb9cc2604..37260d1494ad 100644 --- a/worlds/stardew_valley/logic/logic.py +++ b/worlds/stardew_valley/logic/logic.py @@ -17,16 +17,20 @@ from .crafting_logic import CraftingLogicMixin from .farming_logic import FarmingLogicMixin from .festival_logic import FestivalLogicMixin +from .fish_pond_logic import FishPondLogicMixin from .fishing_logic import FishingLogicMixin from .gift_logic import GiftLogicMixin from .goal_logic import GoalLogicMixin from .grind_logic import GrindLogicMixin from .harvesting_logic import HarvestingLogicMixin from .has_logic import HasLogicMixin -from .logic_event import all_logic_events +from .hats_logic import HatLogicMixin +from .logic_event import all_item_events +from .meme_items_logic import MemeItemsLogicMixin from .mine_logic import MineLogicMixin from .money_logic import MoneyLogicMixin from .monster_logic import MonsterLogicMixin +from .movie_logic import MovieLogicMixin from .museum_logic import MuseumLogicMixin from .pet_logic import PetLogicMixin from .quality_logic import QualityLogicMixin @@ -37,28 +41,34 @@ from .requirement_logic import RequirementLogicMixin from .season_logic import SeasonLogicMixin from .shipping_logic import ShippingLogicMixin +from .shirts_logic import ShirtLogicMixin from .skill_logic import SkillLogicMixin from .source_logic import SourceLogicMixin +from .special_items_logic import SpecialItemsLogicMixin from .special_order_logic import SpecialOrderLogicMixin +from .tailoring_logic import TailoringLogicMixin from .time_logic import TimeLogicMixin from .tool_logic import ToolLogicMixin from .traveling_merchant_logic import TravelingMerchantLogicMixin from .wallet_logic import WalletLogicMixin from .walnut_logic import WalnutLogicMixin from ..content.game_content import StardewContent +from ..content.vanilla.ginger_island import ginger_island_content_pack from ..data.craftable_data import all_crafting_recipes from ..data.museum_data import all_museum_items from ..data.recipe_data import all_cooking_recipes from ..mods.logic.magic_logic import MagicLogicMixin from ..mods.logic.mod_logic import ModLogicMixin -from ..options import ExcludeGingerIsland, StardewValleyOptions -from ..stardew_rule import False_, True_, StardewRule +from ..options import StardewValleyOptions, BundleRandomization, IncludeEndgameLocations +from ..stardew_rule import False_, StardewRule, Or, Reach from ..strings.animal_names import Animal from ..strings.animal_product_names import AnimalProduct from ..strings.ap_names.community_upgrade_names import CommunityUpgrade from ..strings.artisan_good_names import ArtisanGood +from ..strings.boot_names import tier_by_boots from ..strings.building_names import Building -from ..strings.craftable_names import Consumable, Ring, Fishing, Lighting, WildSeeds +from ..strings.catalogue_names import items_by_catalogue +from ..strings.craftable_names import Consumable, Ring, Fishing, Lighting, WildSeeds, Furniture from ..strings.crop_names import Fruit, Vegetable from ..strings.currency_names import Currency from ..strings.decoration_names import Decoration @@ -73,16 +83,16 @@ from ..strings.ingredient_names import Ingredient from ..strings.machine_names import Machine from ..strings.material_names import Material -from ..strings.metal_names import Ore, MetalBar, Mineral, Fossil, Artifact +from ..strings.metal_names import Ore, MetalBar, Mineral, Fossil from ..strings.monster_drop_names import Loot from ..strings.monster_names import Monster from ..strings.region_names import Region, LogicRegion from ..strings.season_names import Season from ..strings.seed_names import Seed, TreeSeed from ..strings.skill_names import Skill -from ..strings.tool_names import Tool, ToolMaterial +from ..strings.special_item_names import SpecialItem +from ..strings.tool_names import Tool, ToolMaterial, FishingRod from ..strings.villager_names import NPC -from ..strings.wallet_item_names import Wallet logger = logging.getLogger(__name__) @@ -93,7 +103,8 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin, SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin, SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin, - RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, FestivalLogicMixin, WalnutLogicMixin, GoalLogicMixin): + RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, FestivalLogicMixin, WalnutLogicMixin, GoalLogicMixin, SpecialItemsLogicMixin, + MovieLogicMixin, MemeItemsLogicMixin, HatLogicMixin, ShirtLogicMixin, TailoringLogicMixin, FishPondLogicMixin): player: int options: StardewValleyOptions content: StardewContent @@ -107,15 +118,16 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC self.registry.museum_rules.update({donation.item_name: self.museum.can_find_museum_item(donation) for donation in all_museum_items}) for recipe in all_cooking_recipes: - if recipe.mod_name and recipe.mod_name not in self.options.mods: + if recipe.content_pack and not self.content.is_enabled(recipe.content_pack): continue + can_cook_rule = self.cooking.can_cook(recipe) if recipe.meal in self.registry.cooking_rules: can_cook_rule = can_cook_rule | self.registry.cooking_rules[recipe.meal] self.registry.cooking_rules[recipe.meal] = can_cook_rule for recipe in all_crafting_recipes: - if recipe.mod_name and recipe.mod_name not in self.options.mods: + if recipe.content_pack is not None and not self.content.are_all_enabled(recipe.content_pack): continue can_craft_rule = self.crafting.can_craft(recipe) if recipe.item in self.registry.crafting_rules: @@ -145,11 +157,14 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC "Junimo Kart Small Buff": self.arcade.has_junimo_kart_power_level(2), "Magic Rock Candy": self.region.can_reach(Region.desert) & self.has("Prismatic Shard"), "Muscle Remedy": self.money.can_spend_at(Region.hospital, 1000), + "Stardrop": self.received("Stardrop"), + "Iridium Snake Milk": self.quest.can_drink_snake_milk(), # self.has(Ingredient.vinegar)), # self.received("Deluxe Fertilizer Recipe") & self.has(MetalBar.iridium) & self.has(SVItem.sap), # | (self.ability.can_cook() & self.relationship.has_hearts(NPC.emily, 3) & self.has(Forageable.leek) & self.has(Forageable.dandelion) & # | (self.ability.can_cook() & self.relationship.has_hearts(NPC.jodi, 7) & self.has(AnimalProduct.cow_milk) & self.has(Ingredient.sugar)), AnimalProduct.any_egg: self.has_any(AnimalProduct.chicken_egg, AnimalProduct.duck_egg), + AnimalProduct.any_milk: self.has_any(AnimalProduct.cow_milk, AnimalProduct.goat_milk), AnimalProduct.brown_egg: self.animal.has_animal(Animal.chicken), AnimalProduct.chicken_egg: self.has_any(AnimalProduct.egg, AnimalProduct.brown_egg, AnimalProduct.large_egg, AnimalProduct.large_brown_egg), AnimalProduct.cow_milk: self.has_any(AnimalProduct.milk, AnimalProduct.large_milk), @@ -164,9 +179,8 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC AnimalProduct.large_milk: self.animal.has_happy_animal(Animal.cow), AnimalProduct.milk: self.animal.has_animal(Animal.cow), AnimalProduct.rabbit_foot: self.animal.has_happy_animal(Animal.rabbit), - AnimalProduct.roe: self.fishing.can_fish_anywhere() & self.building.has_building(Building.fish_pond), - AnimalProduct.squid_ink: self.mine.can_mine_in_the_mines_floor_81_120() | (self.building.has_building(Building.fish_pond) & self.has(Fish.squid)), - AnimalProduct.sturgeon_roe: self.has(Fish.sturgeon) & self.building.has_building(Building.fish_pond), + AnimalProduct.roe: self.fish_pond.can_get_fish_pond_reward(Fish.any, 1, AnimalProduct.roe), + AnimalProduct.squid_ink: self.mine.can_mine_in_the_mines_floor_81_120() | self.fish_pond.can_get_fish_pond_reward(Fish.squid, 1, AnimalProduct.squid_ink) | self.fish_pond.can_get_fish_pond_reward(Fish.midnight_squid, 1, AnimalProduct.squid_ink), AnimalProduct.truffle: self.animal.has_animal(Animal.pig) & self.season.has_any_not_winter(), AnimalProduct.void_egg: self.has(AnimalProduct.void_egg_starter), # Should also check void chicken if there was an alternative to obtain it without void egg AnimalProduct.wool: self.animal.has_animal(Animal.rabbit) | self.animal.has_animal(Animal.sheep), @@ -174,18 +188,17 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC AnimalProduct.slime_egg_blue: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(3), AnimalProduct.slime_egg_red: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(6), AnimalProduct.slime_egg_purple: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(9), - AnimalProduct.slime_egg_tiger: self.can_fish_pond(Fish.lionfish, *(Forageable.ginger, Fruit.pineapple, Fruit.mango)) & self.time.has_lived_months(12) & + AnimalProduct.slime_egg_tiger: self.fish_pond.can_get_fish_pond_reward(Fish.lionfish, 9, AnimalProduct.slime_egg_tiger) & self.time.has_lived_months(12) & self.building.has_building(Building.slime_hutch) & self.monster.can_kill(Monster.tiger_slime), AnimalProduct.duck_egg_starter: self.logic.false_, # It could be purchased at the Feast of the Winter Star, but it's random every year, so not considering it yet... AnimalProduct.dinosaur_egg_starter: self.logic.false_, # Dinosaur eggs are also part of the museum rules, and I don't want to touch them yet. AnimalProduct.egg_starter: self.logic.false_, # It could be purchased at the Desert Festival, but festival logic is quite a mess, so not considering it yet... AnimalProduct.golden_egg_starter: self.received(AnimalProduct.golden_egg) & (self.money.can_spend_at(Region.ranch, 100000) | self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100)), - AnimalProduct.void_egg_starter: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)), + AnimalProduct.void_egg_starter: self.money.can_spend_at(Region.sewer, 5000), ArtisanGood.aged_roe: self.artisan.can_preserves_jar(AnimalProduct.roe), ArtisanGood.battery_pack: (self.has(Machine.lightning_rod) & self.season.has_any_not_winter()) | self.has(Machine.solar_panel), - ArtisanGood.caviar: self.artisan.can_preserves_jar(AnimalProduct.sturgeon_roe), - ArtisanGood.cheese: (self.has(AnimalProduct.cow_milk) & self.has(Machine.cheese_press)) | (self.region.can_reach(Region.desert) & self.has(Mineral.emerald)), - ArtisanGood.cloth: (self.has(AnimalProduct.wool) & self.has(Machine.loom)) | (self.region.can_reach(Region.desert) & self.has(Mineral.aquamarine)), + ArtisanGood.cheese: (self.has(AnimalProduct.cow_milk) & self.has(Machine.cheese_press)) | (self.region.can_reach(Region.desert) & self.artisan.can_replicate_gem(Mineral.emerald)), + ArtisanGood.cloth: (self.has(AnimalProduct.wool) & self.has(Machine.loom)) | (self.region.can_reach(Region.desert) & self.artisan.can_replicate_gem(Mineral.aquamarine)), ArtisanGood.dinosaur_mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.dinosaur_egg), ArtisanGood.duck_mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.duck_egg), ArtisanGood.goat_cheese: self.has(AnimalProduct.goat_milk) & self.has(Machine.cheese_press), @@ -203,11 +216,10 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC Beverage.pina_colada: self.money.can_spend_at(Region.island_resort, 600), Beverage.triple_shot_espresso: self.has("Hot Java Ring"), Consumable.butterfly_powder: self.money.can_spend_at(Region.sewer, 20000), - Consumable.far_away_stone: self.region.can_reach(Region.mines_floor_100) & self.has(Artifact.ancient_doll), Consumable.fireworks_red: self.region.can_reach(Region.casino), Consumable.fireworks_purple: self.region.can_reach(Region.casino), Consumable.fireworks_green: self.region.can_reach(Region.casino), - Consumable.golden_animal_cracker: self.skill.has_mastery(Skill.farming), + Consumable.golden_animal_cracker: self.skill.has_mastery(Skill.farming) & (self.fishing.can_fish_chests | self.region.can_reach(Region.skull_cavern_25)), Consumable.mystery_box: self.received(CommunityUpgrade.mr_qi_plane_ride), Consumable.gold_mystery_box: self.received(CommunityUpgrade.mr_qi_plane_ride) & self.skill.has_mastery(Skill.foraging), Currency.calico_egg: self.region.can_reach(LogicRegion.desert_festival), @@ -229,30 +241,32 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC Fish.snail: self.fishing.can_crab_pot_at(Region.town), Fishing.curiosity_lure: self.monster.can_kill(self.monster.all_monsters_by_name[Monster.mummy]), Fishing.lead_bobber: self.skill.has_level(Skill.fishing, 6) & self.money.can_spend_at(Region.fish_shop, 200), - Forageable.hay: self.building.has_building(Building.silo) & self.tool.has_tool(Tool.scythe), # - Forageable.journal_scrap: self.region.can_reach_all((Region.island_west, Region.island_north, Region.island_south, Region.volcano_floor_10)) & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),# - Forageable.secret_note: self.quest.has_magnifying_glass() & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()), # + Fishing.golden_bobber: self.region.can_reach(LogicRegion.desert_festival) & self.fishing.can_fish_chests, + Forageable.hay: self.building.has_building(Building.silo) & self.tool.has_scythe(), # + Forageable.journal_scrap: self.region.can_reach_all(Region.island_west, Region.island_north, Region.island_south, Region.volcano_floor_10) & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),# + Forageable.secret_note: self.region.can_reach(LogicRegion.secret_notes), # Fossil.bone_fragment: (self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.pickaxe)) | self.monster.can_kill(Monster.skeleton), Fossil.fossilized_leg: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.pickaxe), Fossil.fossilized_ribs: self.region.can_reach(Region.island_south) & self.tool.has_tool(Tool.hoe) & self.received("Open Professor Snail Cave"), Fossil.fossilized_skull: self.action.can_open_geode(Geode.golden_coconut), Fossil.fossilized_spine: self.fishing.can_fish_at(Region.dig_site), - Fossil.fossilized_tail: self.action.can_pan_at(Region.dig_site, ToolMaterial.copper), + Fossil.fossilized_tail: self.action.can_pan_at(Region.dig_site, ToolMaterial.iridium), Fossil.mummified_bat: self.region.can_reach(Region.volcano_floor_10), - Fossil.mummified_frog: self.region.can_reach(Region.island_east) & self.tool.has_tool(Tool.scythe), + Fossil.mummified_frog: self.region.can_reach(Region.island_east) & self.tool.has_scythe(), Fossil.snake_skull: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.hoe), Fossil.snake_vertebrae: self.region.can_reach(Region.island_west) & self.tool.has_tool(Tool.hoe), + Furniture.exotic_double_bed: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 50), Geode.artifact_trove: self.has(Geode.omni) & self.region.can_reach(Region.desert), Geode.frozen: self.mine.can_mine_in_the_mines_floor_41_80(), Geode.geode: self.mine.can_mine_in_the_mines_floor_1_40(), Geode.golden_coconut: self.region.can_reach(Region.island_north), - Geode.magma: self.mine.can_mine_in_the_mines_floor_81_120() | (self.has(Fish.lava_eel) & self.building.has_building(Building.fish_pond)), - Geode.omni: self.mine.can_mine_in_the_mines_floor_41_80() | self.region.can_reach(Region.desert) | self.tool.has_tool(Tool.pan, ToolMaterial.iron) | self.received(Wallet.rusty_key) | (self.has(Fish.octopus) & self.building.has_building(Building.fish_pond)) | self.region.can_reach(Region.volcano_floor_10), + Geode.magma: self.mine.can_mine_in_the_mines_floor_81_120(), # Could add self.fish_pond.can_get_fish_pond_reward(Fish.lava_eel, 9, Geode.magma) but it makes a logic loop + Geode.omni: self.count(2, *(self.mine.can_mine_in_the_mines_floor_81_120(), self.region.can_reach_all((Region.desert, Region.oasis, Region.sewer)), self.tool.has_pan(ToolMaterial.iron), (self.region.can_reach_all((Region.island_west, Region.island_north,)) & self.has(Consumable.treasure_totem)))), # Could add self.fish_pond.can_get_fish_pond_reward(Fish.octopus, 9, Geode.omni) but it makes a logic loop Gift.bouquet: self.relationship.has_hearts_with_any_bachelor(8) & self.money.can_spend_at(Region.pierre_store, 100), - Gift.golden_pumpkin: self.season.has(Season.fall) | self.action.can_open_geode(Geode.artifact_trove), - Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts_with_any_bachelor(10) & self.building.has_building(Building.kitchen) & self.has(Consumable.rain_totem), + Gift.golden_pumpkin: self.festival.has_golden_pumpkin(), + Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts_with_any_bachelor(10) & self.building.has_building(Building.kitchen) & (self.has(Consumable.rain_totem) | self.season.has_any_not_winter()), Gift.movie_ticket: self.money.can_spend_at(Region.movie_ticket_stand, 1000), - Gift.pearl: (self.has(Fish.blobfish) & self.building.has_building(Building.fish_pond)) | self.action.can_open_geode(Geode.artifact_trove), + Gift.pearl: self.fish_pond.can_get_fish_pond_reward(Fish.blobfish, 9, Gift.pearl) | self.action.can_open_geode(Geode.artifact_trove), Gift.tea_set: self.season.has(Season.winter) & self.time.has_lived_max_months, Gift.void_ghost_pendant: self.money.can_trade_at(Region.desert, Loot.void_essence, 200) & self.relationship.has_hearts(NPC.krobus, 10), Gift.wilted_bouquet: self.has(Machine.furnace) & self.has(Gift.bouquet) & self.has(Material.coal), @@ -271,15 +285,17 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC Machine.crab_pot: self.skill.has_level(Skill.fishing, 3) & self.money.can_spend_at(Region.fish_shop, 1500), Machine.enricher: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20), Machine.pressure_nozzle: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20), + Machine.sewing_machine: (self.region.can_reach(Region.haley_house) & self.has(ArtisanGood.cloth)) | (self.received(Machine.sewing_machine) & self.region.can_reach(Region.secret_woods)), + Machine.statue_endless_fortune: self.has_statue_of_endless_fortune(), Material.cinder_shard: self.region.can_reach(Region.volcano_floor_5), - Material.clay: self.region.can_reach_any((Region.farm, Region.beach, Region.quarry)) & self.tool.has_tool(Tool.hoe), - Material.coal: self.mine.can_mine_in_the_mines_floor_41_80() | self.tool.has_tool(Tool.pan), - Material.fiber: True_(), + Material.clay: self.region.can_reach_any(Region.farm, Region.beach, Region.quarry) & self.tool.has_tool(Tool.hoe), + Material.coal: self.mine.can_mine_in_the_mines_floor_41_80() | self.tool.has_pan(), + Material.fiber: self.ability.can_scythe_vines(), Material.hardwood: self.tool.has_tool(Tool.axe, ToolMaterial.copper) & (self.region.can_reach(Region.secret_woods) | self.region.can_reach(Region.island_west)), - Material.moss: self.season.has_any_not_winter() & (self.tool.has_tool(Tool.scythe) | self.combat.has_any_weapon) & self.region.can_reach(Region.forest), + Material.moss: self.season.has_any_not_winter() & (self.tool.has_scythe() | self.combat.has_any_weapon) & self.region.can_reach(Region.forest), Material.sap: self.ability.can_chop_trees(), - Material.stone: self.tool.has_tool(Tool.pickaxe), - Material.wood: self.tool.has_tool(Tool.axe), + Material.stone: self.ability.can_mine_stone(), + Material.wood: self.ability.can_chop_trees(), Meal.ice_cream: (self.season.has(Season.summer) & self.money.can_spend_at(Region.town, 250)) | self.money.can_spend_at(Region.oasis, 240), Meal.strange_bun: self.relationship.has_hearts(NPC.shane, 7) & self.has(Ingredient.wheat_flour) & self.has(Fish.periwinkle) & self.has(ArtisanGood.void_mayonnaise), MetalBar.copper: self.can_smelt(Ore.copper), @@ -288,13 +304,19 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC MetalBar.iron: self.can_smelt(Ore.iron), MetalBar.quartz: self.can_smelt(Mineral.quartz) | self.can_smelt("Fire Quartz") | (self.has(Machine.recycling_machine) & (self.has(Trash.broken_cd) | self.has(Trash.broken_glasses))), MetalBar.radioactive: self.can_smelt(Ore.radioactive), - Ore.copper: self.mine.can_mine_in_the_mines_floor_1_40() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_tool(Tool.pan, ToolMaterial.copper), - Ore.gold: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_tool(Tool.pan, ToolMaterial.gold), - Ore.iridium: self.count(2, *(self.mine.can_mine_in_the_skull_cavern(), self.can_fish_pond(Fish.super_cucumber), self.tool.has_tool(Tool.pan, ToolMaterial.iridium))), - Ore.iron: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_tool(Tool.pan, ToolMaterial.iron), - Ore.radioactive: self.ability.can_mine_perfectly() & self.region.can_reach(Region.qi_walnut_room), + Mineral.any_gem: self.museum.has_any_gem(), + Ore.copper: self.mine.can_mine_in_the_mines_floor_1_40() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_pan(), + Ore.gold: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_pan(ToolMaterial.gold), + Ore.iridium: self.ability.can_mine_perfectly_in_the_skull_cavern() | (self.mine.can_mine_in_the_skull_cavern() & self.tool.has_pan(ToolMaterial.gold)), # Could add self.fish_pond.can_get_fish_pond_reward(Fish.super_cucumber, 9, Ore.iridium) but it makes a logic loop + Ore.iron: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_pan(ToolMaterial.iron), + Ore.radioactive: self.special_order.can_get_radioactive_ore(), RetainingSoil.basic: self.money.can_spend_at(Region.pierre_store, 100), RetainingSoil.quality: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150), + SpecialItem.lucky_purple_shorts: self.special_items.has_purple_shorts(), + SpecialItem.trimmed_purple_shorts: self.has(SpecialItem.lucky_purple_shorts) & self.has(Machine.sewing_machine), + SpecialItem.far_away_stone: self.special_items.has_far_away_stone(), + SpecialItem.solid_gold_lewis: self.special_items.has_solid_gold_lewis(), + SpecialItem.advanced_tv_remote: self.special_items.has_advanced_tv_remote(), SpeedGro.basic: self.money.can_spend_at(Region.pierre_store, 100), SpeedGro.deluxe: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150), Trash.broken_cd: self.fishing.can_crab_pot_anywhere, @@ -312,11 +334,11 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC Fish.clam: self.tool.can_forage(Generic.any, Region.beach), Fish.cockle: self.tool.can_forage(Generic.any, Region.beach), WaterItem.green_algae: self.fishing.can_fish_in_freshwater(), - WaterItem.cave_jelly: self.fishing.can_fish_at(Region.mines_floor_100) & self.tool.has_fishing_rod(2), - WaterItem.river_jelly: self.fishing.can_fish_at(Region.town) & self.tool.has_fishing_rod(2), - WaterItem.sea_jelly: self.fishing.can_fish_at(Region.beach) & self.tool.has_fishing_rod(2), + WaterItem.cave_jelly: self.fishing.can_fish_at(Region.mines_floor_100) & self.tool.has_fishing_rod(FishingRod.bamboo), + WaterItem.river_jelly: self.fishing.can_fish_at(Region.town) & self.tool.has_fishing_rod(FishingRod.bamboo), + WaterItem.sea_jelly: self.fishing.can_fish_at(Region.beach) & self.tool.has_fishing_rod(FishingRod.bamboo), WaterItem.seaweed: self.fishing.can_fish_at(Region.tide_pools), - WaterItem.white_algae: self.fishing.can_fish_at(Region.mines_floor_20), + WaterItem.white_algae: self.fishing.can_fish_at(Region.mines_floor_20) & self.tool.has_fishing_rod(FishingRod.bamboo), WildSeeds.grass_starter: self.money.can_spend_at(Region.pierre_store, 100), }) # @formatter:on @@ -351,6 +373,10 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_() self.registry.item_rules[recipe] = obtention_rule | crafting_rule + if self.options.bundle_randomization == BundleRandomization.option_meme: + self.meme.initialize_rules() + self.registry.item_rules.update(self.registry.meme_item_rules) + self.quest.initialize_rules() self.quest.update_rules(self.mod.quest.get_modded_quest_rules()) @@ -359,31 +385,59 @@ def __init__(self, player: int, options: StardewValleyOptions, content: StardewC self.special_order.initialize_rules() self.special_order.update_rules(self.mod.special_order.get_modded_special_orders_rules()) + self.shirt.initialize_rules() + self.registry.item_rules.update(self.registry.shirt_rules) + + for catalogue in items_by_catalogue: + for item in items_by_catalogue[catalogue]: + self.registry.item_rules[item] = self.has(catalogue) + + for boots in tier_by_boots: + self.registry.item_rules[boots] = self.combat.has_specific_boots(boots) + def setup_events(self, register_event: Callable[[str, str, StardewRule], None]) -> None: - for logic_event in all_logic_events: - rule = self.registry.item_rules[logic_event.item] - register_event(logic_event.name, logic_event.region, rule) - self.registry.item_rules[logic_event.item] = self.received(logic_event.name) + for item_event in all_item_events: + rule = self.registry.item_rules[item_event.item] + + if isinstance(rule, Or) and bool(reaches := [r for r in rule.current_rules if isinstance(r, Reach) and r.resolution_hint == "Region"]): + logger.debug("Sharding rule for %s in multiple logic events, placed in %s.", item_event.item, [r.spot for r in reaches]) + + for i, reach in enumerate(reaches): + location_name = f"{item_event.name} sharded_{i}" + new_rule = self.region.can_reach(item_event.region) + register_event(item_event.name, reach.spot, new_rule, location_name=location_name) + + remaining_rules = [r for r in rule.current_rules if not isinstance(r, Reach) or r.resolution_hint != "Region"] + if remaining_rules: + register_event(item_event.name, item_event.region, Or(*remaining_rules)) + + else: + register_event(item_event.name, item_event.region, rule) + + self.registry.item_rules[item_event.item] = self.received(item_event.name) def can_smelt(self, item: str) -> StardewRule: return self.has(Machine.furnace) & self.has(item) def has_island_trader(self) -> StardewRule: - if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: - return False_() - return self.region.can_reach(Region.island_trader) + if self.content.is_enabled(ginger_island_content_pack): + return self.region.can_reach(Region.island_trader) + return self.logic.false_ def has_abandoned_jojamart(self) -> StardewRule: - return self.received(CommunityUpgrade.movie_theater, 1) + return (self.received(CommunityUpgrade.movie_theater, 1) & self.season.has_any_not_winter()) | self.has_movie_theater() def has_movie_theater(self) -> StardewRule: return self.received(CommunityUpgrade.movie_theater, 2) def can_use_obelisk(self, obelisk: str) -> StardewRule: - return self.region.can_reach(Region.farm) & self.received(obelisk) + return self.region.can_reach(Region.farm) & self.building.has_wizard_building(obelisk) + + def can_purchase_statue_of_endless_fortune(self) -> StardewRule: + return self.money.can_spend_at(Region.casino, 1_000_000) - def can_fish_pond(self, fish: str, *items: str) -> StardewRule: - rule = self.building.has_building(Building.fish_pond) & self.has(fish) - if items: - rule = rule & self.has_all(*items) - return rule + def has_statue_of_endless_fortune(self) -> StardewRule: + can_purchase_rule = self.can_purchase_statue_of_endless_fortune() + if self.options.include_endgame_locations == IncludeEndgameLocations.option_true: + return can_purchase_rule & self.received(Machine.statue_endless_fortune) + return can_purchase_rule diff --git a/worlds/stardew_valley/logic/logic_event.py b/worlds/stardew_valley/logic/logic_event.py index 9af1d622578f..2e8e00640727 100644 --- a/worlds/stardew_valley/logic/logic_event.py +++ b/worlds/stardew_valley/logic/logic_event.py @@ -1,11 +1,14 @@ +from __future__ import annotations + from dataclasses import dataclass from ..strings.ap_names import event_names +from ..strings.material_names import Material from ..strings.metal_names import MetalBar, Ore from ..strings.region_names import Region all_events = event_names.all_events.copy() -all_logic_events = list() +all_item_events: list[LogicItemEvent] = list() @dataclass(frozen=True) @@ -25,9 +28,9 @@ def __init__(self, item: str, region: str): def register_item_event(item: str, region: str = Region.farm): event = LogicItemEvent(item, region) - all_logic_events.append(event) + all_item_events.append(event) all_events.add(event.name) -for i in (MetalBar.copper, MetalBar.iron, MetalBar.gold, MetalBar.iridium, Ore.copper, Ore.iron, Ore.gold, Ore.iridium): +for i in (Material.coal, MetalBar.copper, MetalBar.iron, MetalBar.gold, MetalBar.iridium, Ore.copper, Ore.iron, Ore.gold, Ore.iridium): register_item_event(i) diff --git a/worlds/stardew_valley/logic/meme_items_logic.py b/worlds/stardew_valley/logic/meme_items_logic.py new file mode 100644 index 000000000000..aaf8c2bfea66 --- /dev/null +++ b/worlds/stardew_valley/logic/meme_items_logic.py @@ -0,0 +1,57 @@ +from .base_logic import BaseLogicMixin, BaseLogic +from ..data.game_item import ItemTag +from ..strings.ap_names.ap_weapon_names import APWeapon +from ..strings.meme_item_names import MemeItem +from ..strings.ring_names import all_ring_names +from ..strings.special_item_names import NotReallyAnItem + + +class MemeItemsLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.meme = MemeItemsLogic(*args, **kwargs) + + +class MemeItemsLogic(BaseLogic): + + def initialize_rules(self): + self.registry.meme_item_rules.update({ + MemeItem.trap: self.logic.true_, + MemeItem.pot_of_gold: self.can_cheat(), + MemeItem.seed_spot: self.can_cheat(), + MemeItem.green_rain_weeds_0: self.can_cheat(), + MemeItem.lumber: self.can_cheat(), + MemeItem.weeds: self.can_cheat(), + MemeItem.twig: self.can_cheat(), + MemeItem.artifact_spot: self.can_cheat(), + MemeItem.warp_totem_qis_arena: self.can_cheat(), + MemeItem.supply_crate: self.can_cheat(), + MemeItem.slime_crate: self.can_cheat(), + MemeItem.decorative_pot: self.can_cheat(), + MemeItem.camping_stove: self.can_cheat(), + MemeItem.worn_pants: self.has_any_pants(), + MemeItem.worn_left_ring: self.has_any_ring(), + MemeItem.worn_right_ring: self.has_any_ring(), + MemeItem.worn_shirt: self.has_any_shirt(), + MemeItem.worn_boots: self.has_any_boots(), + MemeItem.worn_hat: self.has_any_hat(), + NotReallyAnItem.death: self.logic.true_, + }) + + def can_cheat(self): + return self.logic.true_ + + def has_any_pants(self): + return self.logic.true_ + + def has_any_shirt(self): + return self.logic.true_ + + def has_any_hat(self): + return self.logic.has_any(*[item.name for item in self.content.find_tagged_items(ItemTag.HAT)]) + + def has_any_ring(self): + return self.logic.received_any(*all_ring_names) + + def has_any_boots(self): + return self.logic.received(APWeapon.footwear) diff --git a/worlds/stardew_valley/logic/mine_logic.py b/worlds/stardew_valley/logic/mine_logic.py index 2dacf674603d..42bd709434d8 100644 --- a/worlds/stardew_valley/logic/mine_logic.py +++ b/worlds/stardew_valley/logic/mine_logic.py @@ -2,6 +2,7 @@ from .base_logic import BaseLogicMixin, BaseLogic from .. import options from ..stardew_rule import StardewRule, True_ +from ..strings.ap_names.ap_option_names import CustomLogicOptionName from ..strings.performance_names import Performance from ..strings.region_names import Region from ..strings.skill_names import Skill @@ -26,8 +27,7 @@ def can_mine_in_the_mines_floor_81_120(self) -> StardewRule: return self.logic.region.can_reach(Region.mines_floor_85) def can_mine_in_the_skull_cavern(self) -> StardewRule: - return (self.logic.mine.can_progress_in_the_mines_from_floor(120) & - self.logic.region.can_reach(Region.skull_cavern)) + return self.logic.region.can_reach(Region.skull_cavern_mining) @cache_self1 def get_weapon_rule_for_floor_tier(self, tier: int): @@ -43,23 +43,40 @@ def get_weapon_rule_for_floor_tier(self, tier: int): @cache_self1 def can_progress_in_the_mines_from_floor(self, floor: int) -> StardewRule: - tier = floor // 40 + assert floor >= 0 + # 0-39, 40-79, 80-119 + mine_tier = floor // 40 + combat_tier = mine_tier rules = [] - weapon_rule = self.logic.mine.get_weapon_rule_for_floor_tier(tier) + if CustomLogicOptionName.extreme_combat in self.options.custom_logic: + combat_tier -= 2 + elif CustomLogicOptionName.hard_combat in self.options.custom_logic: + combat_tier -= 1 + elif CustomLogicOptionName.easy_combat in self.options.custom_logic: + combat_tier += 1 + combat_tier = max(0, combat_tier) + + if CustomLogicOptionName.extreme_mining in self.options.custom_logic: + mine_tier -= 2 + elif CustomLogicOptionName.hard_mining in self.options.custom_logic: + mine_tier -= 1 + elif self.options.tool_progression.is_progressive and CustomLogicOptionName.easy_mining in self.options.custom_logic: + mine_tier += 1 + mine_tier = max(0, mine_tier) + + weapon_rule = self.logic.mine.get_weapon_rule_for_floor_tier(combat_tier) rules.append(weapon_rule) - tool_rule = self.logic.tool.can_mine_using(ToolMaterial.tiers[tier]) + tool_rule = self.logic.tool.can_mine_using(ToolMaterial.tiers[min(5, mine_tier + 1)]) rules.append(tool_rule) # No alternative for vanilla because we assume that you will grind the levels in the mines. if self.content.features.skill_progression.is_progressive: - skill_level = min(10, max(0, tier * 2)) - rules.append(self.logic.skill.has_level(Skill.combat, skill_level)) - rules.append(self.logic.skill.has_level(Skill.mining, skill_level)) - - if tier >= 4: - rules.append(self.logic.cooking.can_cook()) + combat_level = min(10, max(0, mine_tier * 2)) + mining_level = min(10, max(0, mine_tier * 2)) + rules.append(self.logic.skill.has_level(Skill.combat, combat_level)) + rules.append(self.logic.skill.has_level(Skill.mining, mining_level)) return self.logic.and_(*rules) @@ -73,19 +90,47 @@ def has_mine_elevator_to_floor(self, floor: int) -> StardewRule: @cache_self1 def can_progress_in_the_skull_cavern_from_floor(self, floor: int) -> StardewRule: - tier = floor // 50 + assert floor >= 0 + # 0-49, 50-99, 100-149, 150-199, 200-249 + mining_tier = floor // 50 + combat_tier = mining_tier rules = [] - weapon_rule = self.logic.combat.has_great_weapon + if CustomLogicOptionName.extreme_combat in self.options.custom_logic: + weapon_rule = self.logic.combat.has_decent_weapon + combat_tier -= 2 + elif CustomLogicOptionName.hard_combat in self.options.custom_logic: + weapon_rule = self.logic.combat.has_good_weapon + combat_tier -= 1 + elif CustomLogicOptionName.easy_combat in self.options.custom_logic: + weapon_rule = self.logic.combat.has_galaxy_weapon + combat_tier += 1 + else: + weapon_rule = self.logic.combat.has_great_weapon + combat_tier = max(0, combat_tier) + + if CustomLogicOptionName.extreme_mining in self.options.custom_logic: + mining_tier -= 2 + elif CustomLogicOptionName.hard_mining in self.options.custom_logic: + mining_tier -= 1 + elif self.options.tool_progression.is_progressive and CustomLogicOptionName.easy_mining in self.options.custom_logic: + mining_tier += 1 + tool_tier = mining_tier + 2 + tool_tier = min(5, max(1, tool_tier)) + mining_tier = max(0, mining_tier) + rules.append(weapon_rule) - tool_rule = self.logic.tool.can_mine_using(ToolMaterial.tiers[min(4, max(0, tier + 2))]) + tool_rule = self.logic.tool.can_mine_using(ToolMaterial.tiers[tool_tier]) rules.append(tool_rule) # No alternative for vanilla because we assume that you will grind the levels in the mines. if self.content.features.skill_progression.is_progressive: - skill_level = min(10, max(0, tier * 2 + 6)) - rules.extend((self.logic.skill.has_level(Skill.combat, skill_level), - self.logic.skill.has_level(Skill.mining, skill_level))) + combat_level = min(10, max(0, combat_tier * 2 + 6)) + mining_level = min(10, max(0, mining_tier * 2 + 6)) + rules.append(self.logic.skill.has_level(Skill.combat, combat_level)) + rules.append(self.logic.skill.has_level(Skill.mining, mining_level)) + + rules.append(self.logic.cooking.can_cook()) return self.logic.and_(*rules) diff --git a/worlds/stardew_valley/logic/money_logic.py b/worlds/stardew_valley/logic/money_logic.py index 8f459a172bb1..77aa3f5e9067 100644 --- a/worlds/stardew_valley/logic/money_logic.py +++ b/worlds/stardew_valley/logic/money_logic.py @@ -1,10 +1,19 @@ +from Options import DeathLink from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from ..data.shop import ShopSource -from ..options import SpecialOrderLocations +from ..content.vanilla.qi_board import qi_board_content_pack +from ..data.shop import ShopSource, HatMouseSource from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_, true_ -from ..strings.currency_names import Currency +from ..strings.animal_names import Animal +from ..strings.ap_names.ap_option_names import CustomLogicOptionName +from ..strings.ap_names.event_names import Event +from ..strings.artisan_good_names import ArtisanGood +from ..strings.building_names import Building +from ..strings.crop_names import Vegetable +from ..strings.currency_names import Currency, MemeCurrency +from ..strings.food_names import Beverage from ..strings.region_names import Region, LogicRegion +from ..strings.season_names import Season qi_gem_rewards = ("100 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems", "25 Qi Gems", "20 Qi Gems", "15 Qi Gems", "10 Qi Gems") @@ -20,52 +29,77 @@ class MoneyLogic(BaseLogic): @cache_self1 def can_have_earned_total(self, amount: int) -> StardewRule: + + if CustomLogicOptionName.nightmare_money in self.options.custom_logic: + amount /= 20 + elif CustomLogicOptionName.extreme_money in self.options.custom_logic: + amount /= 8 + elif CustomLogicOptionName.hard_money in self.options.custom_logic: + amount /= 2 + elif CustomLogicOptionName.easy_money in self.options.custom_logic: + amount *= 4 + if amount <= 1000: return self.logic.true_ - pierre_rule = self.logic.region.can_reach_all((Region.pierre_store, Region.forest)) - willy_rule = self.logic.region.can_reach_all((Region.fish_shop, LogicRegion.fishing)) - clint_rule = self.logic.region.can_reach_all((Region.blacksmith, Region.mines_floor_5)) - robin_rule = self.logic.region.can_reach_all((Region.carpenter, Region.secret_woods)) shipping_rule = self.logic.shipping.can_use_shipping_bin + pierre_rule = self.logic.region.can_reach_all(Region.pierre_store, Region.forest) + willy_rule = self.logic.region.can_reach_all(Region.fish_shop, LogicRegion.fishing) + clint_rule = self.logic.region.can_reach_all(Region.blacksmith, Region.mines_floor_5) + robin_rule = self.logic.region.can_reach_all(Region.carpenter, Region.secret_woods) + farming_rule = self.logic.farming.can_plant_and_grow_item(Season.not_winter) + + if amount <= 2000: + selling_any_rule = shipping_rule | pierre_rule | willy_rule | clint_rule | robin_rule + return selling_any_rule - if amount <= 2500: - selling_any_rule = pierre_rule | willy_rule | clint_rule | robin_rule | shipping_rule + if amount <= 3000: + selling_any_rule = shipping_rule | pierre_rule | willy_rule return selling_any_rule if amount <= 5000: - selling_all_rule = (pierre_rule & willy_rule & clint_rule & robin_rule) | shipping_rule + selling_all_rule = shipping_rule | (pierre_rule & farming_rule) | (pierre_rule & willy_rule & clint_rule & robin_rule) return selling_all_rule if amount <= 10000: - return shipping_rule + return shipping_rule & farming_rule seed_rules = self.logic.region.can_reach(Region.pierre_store) if amount <= 40000: - return shipping_rule & seed_rules + return shipping_rule & seed_rules & farming_rule percent_progression_items_needed = min(90, amount // 20000) - return shipping_rule & seed_rules & HasProgressionPercent(self.player, percent_progression_items_needed) + return shipping_rule & seed_rules & farming_rule & HasProgressionPercent(self.player, percent_progression_items_needed) @cache_self1 def can_spend(self, amount: int) -> StardewRule: if self.options.starting_money == -1: return True_() - return self.logic.money.can_have_earned_total(amount * 5) + spend_earned_multiplier = 5 # We assume that if you earned 5x an amount, you can reasonably spend that amount on things + return self.logic.money.can_have_earned_total(amount * spend_earned_multiplier) # Should be cached def can_spend_at(self, region: str, amount: int) -> StardewRule: return self.logic.region.can_reach(region) & self.logic.money.can_spend(amount) + def can_shop_from_hat_mouse(self, source: HatMouseSource) -> StardewRule: + money_rule = self.logic.money.can_spend(source.price) if source.price is not None else true_ + region_rule = self.logic.region.can_reach(LogicRegion.hat_mouse) + requirements_rule = self.logic.requirement.meet_all_requirements(source.unlock_requirements) if source.unlock_requirements is not None else true_ + return money_rule & region_rule & requirements_rule + @cache_self1 def can_shop_from(self, source: ShopSource) -> StardewRule: season_rule = self.logic.season.has_any(source.seasons) - money_rule = self.logic.money.can_spend(source.money_price) if source.money_price is not None else true_ + if source.currency == Currency.money: + money_rule = self.logic.money.can_spend(source.price) if source.price is not None else true_ + else: + money_rule = self.logic.money.can_trade_at(source.shop_region, source.currency, source.price) if source.price is not None else true_ item_rules = [] if source.items_price is not None: for price, item in source.items_price: - item_rules.append(self.logic.has(item) & self.logic.grind.can_grind_item(price, item)) + item_rules.append(self.logic.grind.can_grind_item(price, item)) region_rule = self.logic.region.can_reach(source.shop_region) @@ -74,24 +108,71 @@ def can_shop_from(self, source: ShopSource) -> StardewRule: # Should be cached def can_trade(self, currency: str, amount: int) -> StardewRule: if amount == 0: - return True_() - if currency == Currency.money: + return self.logic.true_ + + if currency == Currency.money or currency == MemeCurrency.bank_money: return self.can_spend(amount) if currency == Currency.star_token: return self.logic.region.can_reach(LogicRegion.fair) if currency == Currency.qi_coin: return self.logic.region.can_reach(Region.casino) & self.logic.time.has_lived_months(amount // 1000) if currency == Currency.qi_gem: - if self.options.special_order_locations & SpecialOrderLocations.value_qi: - number_rewards = min(len(qi_gem_rewards), max(1, (amount // 10))) - return self.logic.received_n(*qi_gem_rewards, count=number_rewards) - number_rewards = 2 - return self.logic.received_n(*qi_gem_rewards, count=number_rewards) & self.logic.region.can_reach(Region.qi_walnut_room) & \ - self.logic.region.can_reach(Region.saloon) & self.can_have_earned_total(5000) + if self.content.is_enabled(qi_board_content_pack): + return self.logic.received(Event.received_qi_gems, amount * 3) + return self.logic.region.can_reach_all(Region.qi_walnut_room, Region.saloon) & self.can_have_earned_total(5000) if currency == Currency.golden_walnut: return self.can_spend_walnut(amount) - return self.logic.has(currency) & self.logic.grind.can_grind_item(amount) + if currency == MemeCurrency.code or currency == MemeCurrency.energy or currency == MemeCurrency.blood: + return self.logic.true_ + if currency == MemeCurrency.clic and amount < 100: + return self.logic.true_ + if currency == MemeCurrency.clic or currency == MemeCurrency.time: + return self.logic.time.has_lived_months(1) + + if currency == MemeCurrency.steps and amount < 6000: + return self.logic.true_ + if currency == MemeCurrency.steps: + return self.logic.time.has_lived_months(amount // 10000) + + if currency == MemeCurrency.cookies: + return self.logic.time.has_lived_months(amount // 10000) + if currency == MemeCurrency.child: + return self.logic.relationship.has_children(1) + if currency == MemeCurrency.dead_crops: + return self.logic.season.has_all() & self.logic.skill.can_get_farming_xp & self.logic.money.can_spend(amount * 100) + if currency == MemeCurrency.dead_pumpkins: + return self.logic.season.has(Season.fall) & self.logic.season.has_any([Season.spring, Season.summer, Season.winter]) & \ + self.logic.has(Vegetable.pumpkin) & self.logic.money.can_spend(amount * 100) + if currency == MemeCurrency.missed_fish: + return self.logic.fishing.can_catch_many_fish(max(1, amount // 4)) + if currency == MemeCurrency.honeywell: + return self.logic.has(ArtisanGood.honey) & self.logic.building.has_building(Building.well) + if currency == MemeCurrency.goat: + return self.logic.animal.has_animal(Animal.goat) + + if currency == MemeCurrency.sleep_days: + if not self.options.multiple_day_sleep_enabled.value: + return self.logic.false_ + if amount > 200: + return self.logic.region.can_reach(Region.farm_house) & self.logic.season.has(Season.winter) + return self.logic.region.can_reach(Region.farm_house) + + if currency == MemeCurrency.time_elapsed: + if amount <= 1000: + return self.logic.true_ + if amount <= 1400: + return self.logic.has(Beverage.coffee) + if amount <= 1800: + return self.logic.building.has_building(Building.stable) + return self.logic.has(Beverage.coffee) & self.logic.building.has_building(Building.stable) + + if currency == MemeCurrency.deathlinks: + if self.options.death_link == DeathLink.option_true: + return self.logic.time.has_lived_months(amount) + return self.logic.false_ + + return self.logic.grind.can_grind_item(amount, currency) # Should be cached def can_trade_at(self, region: str, currency: str, amount: int) -> StardewRule: diff --git a/worlds/stardew_valley/logic/monster_logic.py b/worlds/stardew_valley/logic/monster_logic.py index 5d2ac3d3f607..19991285ca0c 100644 --- a/worlds/stardew_valley/logic/monster_logic.py +++ b/worlds/stardew_valley/logic/monster_logic.py @@ -3,12 +3,9 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .combat_logic import CombatLogicMixin -from .has_logic import HasLogicMixin -from .region_logic import RegionLogicMixin -from .time_logic import TimeLogicMixin, MAX_MONTHS -from .. import options +from .time_logic import MAX_MONTHS from ..data import monster_data +from ..data.fish_data import ginger_island_river from ..stardew_rule import StardewRule from ..strings.generic_names import Generic from ..strings.region_names import Region @@ -24,11 +21,11 @@ class MonsterLogic(BaseLogic): @cached_property def all_monsters_by_name(self): - return monster_data.all_monsters_by_name_given_mods(self.options.mods.value) + return monster_data.all_monsters_by_name_given_content_packs(self.content.registered_packs) @cached_property def all_monsters_by_category(self): - return monster_data.all_monsters_by_category_given_mods(self.options.mods.value) + return monster_data.all_monsters_by_category_given_content_packs(self.content.registered_packs) def can_kill(self, monster: Union[str, monster_data.StardewMonster], amount_tier: int = 0) -> StardewRule: if amount_tier <= 0: @@ -40,30 +37,30 @@ def can_kill(self, monster: Union[str, monster_data.StardewMonster], amount_tier return self.logic.monster.can_kill_any(self.all_monsters_by_name.values()) & time_rule monster = self.all_monsters_by_name[monster] - region_rule = self.logic.region.can_reach_any(monster.locations) + region_rule = self.logic.region.can_reach_any(*monster.locations) combat_rule = self.logic.combat.can_fight_at_level(monster.difficulty) return region_rule & combat_rule & time_rule @cache_self1 - def can_kill_many(self, monster: monster_data.StardewMonster) -> StardewRule: + def can_kill_many(self, monster: Union[str, monster_data.StardewMonster]) -> StardewRule: return self.logic.monster.can_kill(monster, MAX_MONTHS / 3) @cache_self1 - def can_kill_max(self, monster: monster_data.StardewMonster) -> StardewRule: + def can_kill_max(self, monster: Union[str, monster_data.StardewMonster]) -> StardewRule: return self.logic.monster.can_kill(monster, MAX_MONTHS) # Should be cached - def can_kill_any(self, monsters: (Iterable[monster_data.StardewMonster], Hashable), amount_tier: int = 0) -> StardewRule: + def can_kill_any(self, monsters: (Iterable[Union[str, monster_data.StardewMonster]], Hashable), amount_tier: int = 0) -> StardewRule: return self.logic.or_(*(self.logic.monster.can_kill(monster, amount_tier) for monster in monsters)) # Should be cached - def can_kill_all(self, monsters: (Iterable[monster_data.StardewMonster], Hashable), amount_tier: int = 0) -> StardewRule: + def can_kill_all(self, monsters: (Iterable[Union[str, monster_data.StardewMonster]], Hashable), amount_tier: int = 0) -> StardewRule: return self.logic.and_(*(self.logic.monster.can_kill(monster, amount_tier) for monster in monsters)) def can_complete_all_monster_slaying_goals(self) -> StardewRule: rules = [self.logic.time.has_lived_max_months] - exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true + exclude_island = not self.content.is_enabled(ginger_island_river) island_regions = [Region.volcano_floor_5, Region.volcano_floor_10, Region.island_west, Region.dangerous_skull_cavern] for category in self.all_monsters_by_category: if exclude_island and all(all(location in island_regions for location in monster.locations) diff --git a/worlds/stardew_valley/logic/movie_logic.py b/worlds/stardew_valley/logic/movie_logic.py new file mode 100644 index 000000000000..c546a65005a4 --- /dev/null +++ b/worlds/stardew_valley/logic/movie_logic.py @@ -0,0 +1,58 @@ +from .base_logic import BaseLogicMixin, BaseLogic +from ..data.movies import movies_by_name, npc_snacks, Snack, Movie, snacks_by_name +from ..stardew_rule import StardewRule, Or +from ..strings.villager_names import NPC + + +class MovieLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.movie = MovieLogic(*args, **kwargs) + + +class MovieLogic(BaseLogic): + + def can_watch_movie(self, movie: str | Movie) -> StardewRule: + if isinstance(movie, str): + movie = movies_by_name[movie] + return self.logic.season.has(movie.season) + + def can_watch_movie_with_loving_npc(self, movie: str | Movie) -> StardewRule: + if isinstance(movie, str): + movie = movies_by_name[movie] + return self.can_watch_movie(movie) & self.logic.relationship.can_meet_any(*movie.loving_npcs) + + def can_invite_to_movie(self, npcs: str | list[str]) -> StardewRule: + if isinstance(npcs, str): + npc = npcs + if npc == NPC.leo: + return self.logic.relationship.has_hearts(NPC.leo, 6) + return self.logic.relationship.can_meet(npc) + return self.logic.or_(*[self.can_invite_to_movie(npc) for npc in npcs]) + + def can_watch_movie_with_loving_npc_and_snack(self, movie: str | Movie) -> StardewRule: + if isinstance(movie, str): + movie = movies_by_name[movie] + potential_partner_rules = [] + for npc in movie.loving_npcs: + meet_rule = self.can_invite_to_movie(npc) + snack_rule = self.can_buy_loved_snack(npc) + potential_partner_rules.append(meet_rule & snack_rule) + return self.can_watch_movie(movie) & Or(*potential_partner_rules) + + def can_buy_loved_snack(self, npc: str) -> StardewRule: + snacks = npc_snacks[npc] + if not snacks: + return self.logic.false_ + snacks_rule = Or(*[self.can_buy_snack(snack) for snack in snacks]) + return snacks_rule + + def can_buy_snack(self, snack: str | Snack) -> StardewRule: + if isinstance(snack, str): + snack = snacks_by_name[snack] + return self.logic.received(snack.category) + + def can_buy_snack_for_someone_who_loves_it(self, snack: str | Snack) -> StardewRule: + if isinstance(snack, str): + snack = snacks_by_name[snack] + return self.logic.received(snack.category) & self.can_invite_to_movie(snack.loving_npcs) diff --git a/worlds/stardew_valley/logic/museum_logic.py b/worlds/stardew_valley/logic/museum_logic.py index 21718db27c9f..230e7a85a16f 100644 --- a/worlds/stardew_valley/logic/museum_logic.py +++ b/worlds/stardew_valley/logic/museum_logic.py @@ -1,3 +1,5 @@ +import math + from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin from .. import options @@ -5,7 +7,9 @@ from ..stardew_rule import StardewRule, False_ from ..strings.metal_names import Mineral from ..strings.region_names import Region -from ..strings.tool_names import Tool, ToolMaterial +from ..strings.tool_names import ToolMaterial, Tool + +gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz) class MuseumLogicMixin(BaseLogicMixin): @@ -24,10 +28,12 @@ def can_donate_museum_artifacts(self, number: int) -> StardewRule: @cache_self1 def can_find_museum_item(self, item: MuseumItem) -> StardewRule: - if item.locations: - region_rule = self.logic.region.can_reach_all_except_one(item.locations) + if item.artifact_spot_locations: + number_locations = len(item.artifact_spot_locations) + number_required_locations = math.ceil(number_locations / 2) + artifact_spot_rule = self.logic.count(number_required_locations, *[self.logic.tool.can_use_tool_at(Tool.hoe, ToolMaterial.basic, spot) for spot in item.artifact_spot_locations]) else: - region_rule = False_() + artifact_spot_rule = False_() if item.geodes: geodes_rule = self.logic.and_(*(self.logic.action.can_open_geode(geode) for geode in item.geodes)) else: @@ -37,8 +43,8 @@ def can_find_museum_item(self, item: MuseumItem) -> StardewRule: time_rule = self.logic.time.has_lived_months(time_needed_to_grind) pan_rule = False_() if item.item_name == Mineral.earth_crystal or item.item_name == Mineral.fire_quartz or item.item_name == Mineral.frozen_tear: - pan_rule = self.logic.tool.has_tool(Tool.pan, ToolMaterial.iridium) - return (pan_rule | region_rule | geodes_rule) & time_rule # & monster_rule & extra_rule + pan_rule = self.logic.tool.has_pan(ToolMaterial.iridium) + return (pan_rule | artifact_spot_rule | geodes_rule) & time_rule # & monster_rule & extra_rule def can_find_museum_artifacts(self, number: int) -> StardewRule: rules = [] @@ -75,3 +81,9 @@ def can_complete_museum(self) -> StardewRule: def can_donate(self, item: str) -> StardewRule: return self.logic.has(item) & self.logic.region.can_reach(Region.museum) + + def has_any_gem(self) -> StardewRule: + return self.logic.has_any(*gems) + + def has_all_gems(self) -> StardewRule: + return self.logic.has_all(*gems) diff --git a/worlds/stardew_valley/logic/pet_logic.py b/worlds/stardew_valley/logic/pet_logic.py index 9d66e8f274d9..d8226b0d7a14 100644 --- a/worlds/stardew_valley/logic/pet_logic.py +++ b/worlds/stardew_valley/logic/pet_logic.py @@ -3,6 +3,7 @@ from .base_logic import BaseLogicMixin, BaseLogic from ..content.feature.friendsanity import pet_heart_item_name from ..stardew_rule import StardewRule, True_ +from ..strings.building_names import Building from ..strings.region_names import Region @@ -14,8 +15,7 @@ def __init__(self, *args, **kwargs): class PetLogic(BaseLogic): def has_pet_hearts(self, hearts: int = 1) -> StardewRule: - assert hearts >= 0, "You can't have negative hearts with a pet." - if hearts == 0: + if hearts <= 0: return True_() if self.content.features.friendsanity.is_pet_randomized: @@ -28,15 +28,17 @@ def received_pet_hearts(self, hearts: int) -> StardewRule: math.ceil(hearts / self.content.features.friendsanity.heart_size)) def can_befriend_pet(self, hearts: int) -> StardewRule: - assert hearts >= 0, "You can't have negative hearts with a pet." - if hearts == 0: + if hearts <= 0: return True_() + heart_size = self.content.features.friendsanity.heart_size + previous_heart = max(hearts - heart_size, 0) + previous_heart_rule = self.has_pet_hearts(previous_heart) points = hearts * 200 points_per_month = 12 * 14 points_per_water_month = 18 * 14 farm_rule = self.logic.region.can_reach(Region.farm) - time_with_water_rule = self.logic.tool.can_water(0) & self.logic.time.has_lived_months(points // points_per_water_month) + time_with_water_rule = self.logic.tool.can_water() & self.logic.time.has_lived_months(points // points_per_water_month) time_without_water_rule = self.logic.time.has_lived_months(points // points_per_month) time_rule = time_with_water_rule | time_without_water_rule - return farm_rule & time_rule + return farm_rule & time_rule & self.logic.building.has_building(Building.pet_bowl) & previous_heart_rule diff --git a/worlds/stardew_valley/logic/quality_logic.py b/worlds/stardew_valley/logic/quality_logic.py index 7f5da4be538f..4af95cf2f309 100644 --- a/worlds/stardew_valley/logic/quality_logic.py +++ b/worlds/stardew_valley/logic/quality_logic.py @@ -1,9 +1,5 @@ -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .farming_logic import FarmingLogicMixin -from .skill_logic import SkillLogicMixin from ..stardew_rule import StardewRule, True_, False_ from ..strings.quality_names import CropQuality @@ -21,13 +17,13 @@ def can_grow_crop_quality(self, quality: str) -> StardewRule: if quality == CropQuality.basic: return True_() if quality == CropQuality.silver: - return self.logic.skill.has_farming_level(5) | (self.logic.farming.has_fertilizer(1) & self.logic.skill.has_farming_level(2)) | ( - self.logic.farming.has_fertilizer(2) & self.logic.skill.has_farming_level(1)) | self.logic.farming.has_fertilizer(3) + return self.logic.skill.has_farming_level(7) | (self.logic.farming.has_fertilizer(1) & self.logic.skill.has_farming_level(3)) | ( + self.logic.farming.has_fertilizer(2) & self.logic.skill.has_farming_level(2)) | self.logic.farming.has_fertilizer(3) if quality == CropQuality.gold: return self.logic.skill.has_farming_level(10) | ( - self.logic.farming.has_fertilizer(1) & self.logic.skill.has_farming_level(5)) | ( - self.logic.farming.has_fertilizer(2) & self.logic.skill.has_farming_level(3)) | ( - self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(2)) + self.logic.farming.has_fertilizer(1) & self.logic.skill.has_farming_level(10)) | ( + self.logic.farming.has_fertilizer(2) & self.logic.skill.has_farming_level(6)) | ( + self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(3)) if quality == CropQuality.iridium: - return self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(4) + return self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(10) return False_() diff --git a/worlds/stardew_valley/logic/quest_logic.py b/worlds/stardew_valley/logic/quest_logic.py index af52d06e30bf..a1884a17f641 100644 --- a/worlds/stardew_valley/logic/quest_logic.py +++ b/worlds/stardew_valley/logic/quest_logic.py @@ -2,6 +2,7 @@ from .base_logic import BaseLogicMixin, BaseLogic from ..stardew_rule import StardewRule, Has, True_ +from ..strings.ap_names.ap_option_names import SecretsanityOptionName from ..strings.ap_names.community_upgrade_names import CommunityUpgrade from ..strings.artisan_good_names import ArtisanGood from ..strings.building_names import Building @@ -17,7 +18,8 @@ from ..strings.quest_names import Quest from ..strings.region_names import Region from ..strings.season_names import Season -from ..strings.tool_names import Tool +from ..strings.special_item_names import SpecialItem +from ..strings.tool_names import Tool, FishingRod from ..strings.villager_names import NPC from ..strings.wallet_item_names import Wallet @@ -40,14 +42,14 @@ def initialize_rules(self): Quest.feeding_animals: self.logic.quest.can_complete_quest(Quest.getting_started) & self.logic.building.has_building(Building.silo), Quest.advancement: self.logic.quest.can_complete_quest(Quest.getting_started) & self.logic.has(Craftable.scarecrow), Quest.archaeology: self.logic.tool.has_tool(Tool.hoe) | self.logic.mine.can_mine_in_the_mines_floor_1_40() | self.logic.fishing.can_fish_chests, - Quest.rat_problem: self.logic.region.can_reach_all((Region.town, Region.community_center)), - Quest.meet_the_wizard: self.logic.quest.can_complete_quest(Quest.rat_problem), + Quest.rat_problem: self.logic.region.can_reach_all(Region.town, Region.community_center), + Quest.meet_the_wizard: self.logic.region.can_reach_all(Region.community_center, Region.wizard_tower) & self.logic.received("Wizard Invitation"), Quest.forging_ahead: self.logic.has(Ore.copper) & self.logic.has(Machine.furnace), Quest.smelting: self.logic.has(MetalBar.copper), Quest.initiation: self.logic.mine.can_mine_in_the_mines_floor_1_40(), Quest.robins_lost_axe: self.logic.season.has(Season.spring) & self.logic.relationship.can_meet(NPC.robin), Quest.jodis_request: self.logic.season.has(Season.spring) & self.logic.has(Vegetable.cauliflower) & self.logic.relationship.can_meet(NPC.jodi), - Quest.mayors_shorts: self.logic.season.has(Season.summer) & self.logic.relationship.has_hearts(NPC.marnie, 2) & + Quest.mayors_shorts: self.logic.has(SpecialItem.lucky_purple_shorts) & self.logic.relationship.can_meet(NPC.lewis), Quest.blackberry_basket: self.logic.season.has(Season.fall) & self.logic.relationship.can_meet(NPC.linus) & self.logic.region.can_reach( Region.tunnel_entrance), @@ -60,12 +62,12 @@ def initialize_rules(self): Quest.knee_therapy: self.logic.season.has(Season.summer) & self.logic.has(Fruit.hot_pepper) & self.logic.relationship.can_meet(NPC.george), Quest.robins_request: self.logic.season.has(Season.winter) & self.logic.has(Material.hardwood) & self.logic.relationship.can_meet(NPC.robin), Quest.qis_challenge: True_(), # The skull cavern floor 25 already has rules - Quest.the_mysterious_qi: (self.logic.region.can_reach_all((Region.bus_tunnel, Region.railroad, Region.mayor_house)) & + Quest.the_mysterious_qi: (self.logic.region.can_reach_all(Region.bus_tunnel, Region.railroad, Region.mayor_house) & self.logic.has_all(ArtisanGood.battery_pack, Forageable.rainbow_shell, Vegetable.beet, Loot.solar_essence)), Quest.carving_pumpkins: self.logic.season.has(Season.fall) & self.logic.has(Vegetable.pumpkin) & self.logic.relationship.can_meet(NPC.caroline), Quest.a_winter_mystery: self.logic.season.has(Season.winter), Quest.strange_note: self.logic.has(Forageable.secret_note) & self.logic.has(ArtisanGood.maple_syrup), - Quest.cryptic_note: self.logic.has(Forageable.secret_note), + Quest.cryptic_note: self.logic.has(Forageable.secret_note) & self.logic.region.can_reach(Region.skull_cavern_100), Quest.fresh_fruit: self.logic.season.has(Season.spring) & self.logic.has(Fruit.apricot) & self.logic.relationship.can_meet(NPC.emily), Quest.aquatic_research: self.logic.season.has(Season.summer) & self.logic.has(Fish.pufferfish) & self.logic.relationship.can_meet(NPC.demetrius), Quest.a_soldiers_star: (self.logic.season.has(Season.summer) & self.logic.time.has_year_two & self.logic.has(Fruit.starfruit) & @@ -93,7 +95,7 @@ def initialize_rules(self): Quest.the_pirates_wife: self.logic.relationship.can_meet(NPC.kent) & self.logic.relationship.can_meet(NPC.gus) & self.logic.relationship.can_meet(NPC.sandy) & self.logic.relationship.can_meet(NPC.george) & self.logic.relationship.can_meet(NPC.wizard) & self.logic.relationship.can_meet(NPC.willy), - Quest.giant_stump: self.logic.has(Material.hardwood) + Quest.giant_stump: self.logic.received(CommunityUpgrade.raccoon) & self.logic.has(Material.hardwood) }) def update_rules(self, new_rules: Dict[str, StardewRule]): @@ -103,7 +105,7 @@ def can_complete_quest(self, quest: str) -> StardewRule: return Has(quest, self.registry.quest_rules, "quest") def has_club_card(self) -> StardewRule: - if self.options.quest_locations.has_story_quests(): + if self.options.quest_locations.has_story_quests() or SecretsanityOptionName.secret_notes in self.options.secretsanity: return self.logic.received(Wallet.club_card) return self.logic.quest.can_complete_quest(Quest.the_mysterious_qi) @@ -117,10 +119,44 @@ def has_dark_talisman(self) -> StardewRule: return self.logic.received(Wallet.dark_talisman) return self.logic.quest.can_complete_quest(Quest.dark_talisman) - def has_raccoon_shop(self) -> StardewRule: + def has_magic_ink(self) -> StardewRule: + if self.options.quest_locations.has_story_quests(): + return self.logic.received(Wallet.magic_ink) + return self.logic.quest.can_complete_quest(Quest.magic_ink) + + def has_raccoon_shop(self, tier: int = 1) -> StardewRule: + number_raccoons_required = 1 + tier # 1 for Mr Raccoon, plus 1 for each shop tier at Mrs Raccoon if self.options.quest_locations.has_story_quests(): - # 1 - Break the tree - # 2 - Build the house, which summons the bundle racoon. This one is done manually if quests are turned off - # 3 - Raccoon's wife opens the shop - return self.logic.received(CommunityUpgrade.raccoon, 3) - return self.logic.received(CommunityUpgrade.raccoon, 2) & self.logic.quest.can_complete_quest(Quest.giant_stump) + # Add one for repairing the tree. This one is done manually if quests are turned off + return self.logic.received(CommunityUpgrade.raccoon, 1 + number_raccoons_required) + return self.logic.received(CommunityUpgrade.raccoon, number_raccoons_required) & self.logic.quest.can_complete_quest(Quest.giant_stump) + + def can_complete_help_wanteds(self, number: int) -> StardewRule: + number_per_month = 7 + number_months = number // number_per_month + if number <= 7: + return self.logic.time.has_lived_months(number_months) + return self.logic.time.has_lived_months(number_months) &\ + self.can_do_item_delivery_quest() & self.can_do_gathering_quest() &\ + self.can_do_fishing_quest() & self.can_do_slaying_quest() + + def can_do_item_delivery_quest(self) -> StardewRule: + return self.logic.region.can_reach(Region.town) + + def can_do_gathering_quest(self) -> StardewRule: + return self.logic.region.can_reach_all(*(Region.town, Region.forest)) & \ + self.logic.region.can_reach_any(*(Region.mines, Region.quarry, Region.skull_cavern_25)) & \ + self.logic.tool.has_tool(Tool.axe) & \ + self.logic.tool.has_tool(Tool.pickaxe) + + def can_do_fishing_quest(self) -> StardewRule: + return self.logic.region.can_reach_all(*(Region.town, Region.beach)) & \ + self.logic.tool.has_fishing_rod(FishingRod.bamboo) + + def can_do_slaying_quest(self) -> StardewRule: + return self.logic.region.can_reach_all(*(Region.town, Region.mines_floor_10)) + + def can_drink_snake_milk(self) -> StardewRule: + if self.options.quest_locations.has_story_quests() or SecretsanityOptionName.secret_notes in self.options.secretsanity: + return self.logic.received(Wallet.iridium_snake_milk) + return self.logic.quest.can_complete_quest(Quest.cryptic_note) & self.logic.region.can_reach(Region.skull_cavern_100) diff --git a/worlds/stardew_valley/logic/received_logic.py b/worlds/stardew_valley/logic/received_logic.py index 68d65040c76a..4898d723a97c 100644 --- a/worlds/stardew_valley/logic/received_logic.py +++ b/worlds/stardew_valley/logic/received_logic.py @@ -2,16 +2,18 @@ from BaseClasses import ItemClassification from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin from .logic_event import all_events from ..items import item_table -from ..stardew_rule import StardewRule, Received, TotalReceived +from ..stardew_rule import StardewRule, Received, TotalReceived, True_ class ReceivedLogicMixin(BaseLogic, BaseLogicMixin): def received(self, item: str, count: Optional[int] = 1) -> StardewRule: assert count >= 0, "Can't receive a negative amount of item." + if count == 0: + return True_() + if item in all_events: return Received(item, self.player, count, event=True) diff --git a/worlds/stardew_valley/logic/region_logic.py b/worlds/stardew_valley/logic/region_logic.py index 81c79be097b8..e2a8f4f2b097 100644 --- a/worlds/stardew_valley/logic/region_logic.py +++ b/worlds/stardew_valley/logic/region_logic.py @@ -1,5 +1,3 @@ -from typing import Tuple - from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin from ..options import EntranceRandomization @@ -7,13 +5,12 @@ from ..strings.region_names import Region main_outside_area = {Region.menu, Region.stardew_valley, Region.farm_house, Region.farm, Region.town, Region.beach, Region.mountain, Region.forest, - Region.bus_stop, Region.backwoods, Region.bus_tunnel, Region.tunnel_entrance} -always_accessible_regions_with_non_progression_er = {*main_outside_area, Region.mines, Region.hospital, Region.carpenter, Region.alex_house, - Region.ranch, Region.farm_cave, Region.wizard_tower, Region.tent, + Region.bus_stop, Region.backwoods, Region.tunnel_entrance} +always_accessible_regions_with_non_progression_er = {*main_outside_area, Region.hospital, Region.carpenter, Region.alex_house, + Region.ranch, Region.farm_cave, Region.tent, Region.pierre_store, Region.saloon, Region.blacksmith, Region.trailer, Region.museum, Region.mayor_house, Region.haley_house, Region.sam_house, Region.jojamart, Region.fish_shop} -always_accessible_regions_without_er = {*always_accessible_regions_with_non_progression_er, Region.community_center, Region.pantry, Region.crafts_room, - Region.fish_tank, Region.boiler_room, Region.vault, Region.bulletin_board} +always_accessible_regions_without_er = {*always_accessible_regions_with_non_progression_er} always_regions_by_setting = {EntranceRandomization.option_disabled: always_accessible_regions_without_er, EntranceRandomization.option_pelican_town: always_accessible_regions_without_er, @@ -41,20 +38,16 @@ def can_reach(self, region_name: str) -> StardewRule: return Reach(region_name, "Region", self.player) - @cache_self1 - def can_reach_any(self, region_names: Tuple[str, ...]) -> StardewRule: + def can_reach_any(self, *region_names: str) -> StardewRule: if any(r in always_regions_by_setting[self.options.entrance_randomization] for r in region_names): return true_ return self.logic.or_(*(self.logic.region.can_reach(spot) for spot in region_names)) - @cache_self1 - def can_reach_all(self, region_names: Tuple[str, ...]) -> StardewRule: + def can_reach_all(self, *region_names: str) -> StardewRule: return self.logic.and_(*(self.logic.region.can_reach(spot) for spot in region_names)) - @cache_self1 - def can_reach_all_except_one(self, region_names: Tuple[str, ...]) -> StardewRule: - region_names = list(region_names) + def can_reach_all_except_one(self, *region_names: str) -> StardewRule: num_required = len(region_names) - 1 if num_required <= 0: num_required = len(region_names) diff --git a/worlds/stardew_valley/logic/relationship_logic.py b/worlds/stardew_valley/logic/relationship_logic.py index e19a6e802b14..5fa40bfdd927 100644 --- a/worlds/stardew_valley/logic/relationship_logic.py +++ b/worlds/stardew_valley/logic/relationship_logic.py @@ -6,11 +6,12 @@ from ..content.feature import friendsanity from ..data.villagers_data import Villager from ..stardew_rule import StardewRule, True_, false_, true_ +from ..strings.ap_names.ap_option_names import CustomLogicOptionName from ..strings.ap_names.mods.mod_items import SVEQuestItem from ..strings.building_names import Building from ..strings.generic_names import Generic from ..strings.gift_names import Gift -from ..strings.region_names import Region +from ..strings.region_names import Region, LogicRegion from ..strings.season_names import Season from ..strings.villager_names import NPC, ModNPC @@ -49,7 +50,9 @@ def has_children(self, number_children: int) -> StardewRule: if not self.content.features.friendsanity.is_enabled: return self.logic.relationship.can_reproduce(number_children) - return self.logic.received_n(*possible_kids, count=number_children) & self.logic.building.has_building(Building.kids_room) + return self.logic.received_n(*possible_kids, count=number_children) & \ + self.logic.building.has_building(Building.kids_room) & \ + self.logic.relationship.can_reproduce(number_children) def can_reproduce(self, number_children: int = 1) -> StardewRule: assert number_children >= 0, "Can't have a negative amount of children." @@ -121,13 +124,14 @@ def can_meet(self, npc: str) -> StardewRule: if villager is None: return false_ - rules = [self.logic.region.can_reach_any(villager.locations)] + rules = [self.logic.region.can_reach_any(*villager.locations)] if npc == NPC.kent: rules.append(self.logic.time.has_year_two) elif npc == NPC.leo: rules.append(self.logic.received("Island North Turtle")) + rules.append(self.logic.region.can_reach(Region.leo_hut)) elif npc == ModNPC.lance: rules.append(self.logic.region.can_reach(Region.volcano_floor_10)) @@ -146,20 +150,15 @@ def can_meet(self, npc: str) -> StardewRule: rules.append(self.logic.received(SVEQuestItem.morgan_schooling)) elif npc == ModNPC.goblin: - rules.append(self.logic.region.can_reach_all((Region.witch_hut, Region.wizard_tower))) + rules.append(self.logic.region.can_reach_all(Region.witch_hut, Region.wizard_tower)) return self.logic.and_(*rules) - def can_give_loved_gifts_to_everyone(self) -> StardewRule: - rules = [] + def can_meet_all(self, *npcs: str) -> StardewRule: + return self.logic.and_(*[self.can_meet(npc) for npc in npcs]) - for npc in self.content.villagers: - meet_rule = self.logic.relationship.can_meet(npc) - rules.append(meet_rule) - - rules.append(self.logic.gifts.has_any_universal_love) - - return self.logic.and_(*rules) + def can_meet_any(self, *npcs: str) -> StardewRule: + return self.logic.or_(*(self.can_meet(npc) for npc in npcs)) # Should be cached def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule: @@ -183,11 +182,10 @@ def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule: previous_heart = max(hearts - heart_size, 0) rules.append(self.logic.relationship.has_hearts(npc, previous_heart)) - if hearts > 2 or hearts > heart_size: + if CustomLogicOptionName.ignore_birthdays not in self.options.custom_logic: rules.append(self.logic.season.has(villager.birthday)) - - if villager.birthday == Generic.any: - rules.append(self.logic.season.has_all() | self.logic.time.has_year_three) # push logic back for any birthday-less villager + if villager.birthday == Generic.any: + rules.append(self.logic.season.has_all() | self.logic.time.has_year_three) # push logic back for any birthday-less villager if villager.bachelor: if hearts > 10: @@ -196,3 +194,9 @@ def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule: rules.append(self.logic.relationship.can_date(npc)) return self.logic.and_(*rules) + + def can_purchase_portrait(self, npc: str = "") -> StardewRule: + spend_rule = self.logic.money.can_spend_at(LogicRegion.traveling_cart, 30_000) + if npc == "": + return self.logic.relationship.can_get_married() & self.logic.relationship.has_hearts_with_any(14) & spend_rule + return self.logic.relationship.can_marry(npc) & self.logic.relationship.has_hearts(npc, 14) & spend_rule diff --git a/worlds/stardew_valley/logic/requirement_logic.py b/worlds/stardew_valley/logic/requirement_logic.py index 1a71810003b0..edcbc88e4725 100644 --- a/worlds/stardew_valley/logic/requirement_logic.py +++ b/worlds/stardew_valley/logic/requirement_logic.py @@ -1,10 +1,22 @@ import functools +import math from typing import Iterable from .base_logic import BaseLogicMixin, BaseLogic -from ..data.game_item import Requirement -from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, CombatRequirement, QuestRequirement, \ - RelationshipRequirement, FishingRequirement, WalnutRequirement, RegionRequirement +from ..data.game_item import Requirement, ItemTag +from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, \ + CombatRequirement, QuestRequirement, \ + SpecificFriendRequirement, FishingRequirement, WalnutRequirement, RegionRequirement, TotalEarningsRequirement, \ + GrangeDisplayRequirement, \ + ForgeInfinityWeaponRequirement, EggHuntRequirement, CaughtFishRequirement, MuseumCompletionRequirement, \ + BuildingRequirement, FullShipmentRequirement, NumberOfFriendsRequirement, FishingCompetitionRequirement, \ + LuauDelightRequirementRequirement, MovieRequirement, CookedRecipesRequirement, CraftedItemsRequirement, \ + HelpWantedRequirement, ShipOneCropRequirement, ReceivedRaccoonsRequirement, PrizeMachineRequirement, \ + AllAchievementsRequirement, PerfectionPercentRequirement, ReadAllBooksRequirement, MinesRequirement, \ + DangerousMinesRequirement, HasItemRequirement, MeetRequirement, MonsterKillRequirement, CatalogueRequirement +from ..options import IncludeEndgameLocations +from ..strings.ap_names.community_upgrade_names import CommunityUpgrade +from ..strings.region_names import Region, LogicRegion class RequirementLogicMixin(BaseLogicMixin): @@ -24,9 +36,13 @@ def meet_all_requirements(self, requirements: Iterable[Requirement]): def meet_requirement(self, requirement: Requirement): raise ValueError(f"Requirements of type{type(requirement)} have no rule registered.") + @meet_requirement.register + def _(self, requirement: HasItemRequirement): + return self.logic.has(requirement.item) + @meet_requirement.register def _(self, requirement: ToolRequirement): - return self.logic.tool.has_tool(requirement.tool, requirement.tier) + return self.logic.tool.has_tool_generic(requirement.tool, requirement.tier) @meet_requirement.register def _(self, requirement: SkillRequirement): @@ -61,9 +77,124 @@ def _(self, requirement: QuestRequirement): return self.logic.quest.can_complete_quest(requirement.quest) @meet_requirement.register - def _(self, requirement: RelationshipRequirement): + def _(self, requirement: MeetRequirement): + return self.logic.relationship.can_meet(requirement.npc) + + @meet_requirement.register + def _(self, requirement: SpecificFriendRequirement): return self.logic.relationship.has_hearts(requirement.npc, requirement.hearts) + @meet_requirement.register + def _(self, requirement: NumberOfFriendsRequirement): + return self.logic.relationship.has_hearts_with_n(requirement.friends, requirement.hearts) + @meet_requirement.register def _(self, requirement: FishingRequirement): return self.logic.fishing.can_fish_at(requirement.region) + + @meet_requirement.register + def _(self, requirement: TotalEarningsRequirement): + return self.logic.money.can_have_earned_total(requirement.amount) + + @meet_requirement.register + def _(self, requirement: GrangeDisplayRequirement): + return self.logic.region.can_reach(LogicRegion.fair) & self.logic.festival.can_get_grange_display_max_score() + + @meet_requirement.register + def _(self, requirement: EggHuntRequirement): + return self.logic.region.can_reach(LogicRegion.egg_festival) & self.logic.festival.can_win_egg_hunt() + + @meet_requirement.register + def _(self, requirement: FishingCompetitionRequirement): + return self.logic.region.can_reach(LogicRegion.festival_of_ice) & self.logic.festival.can_win_fishing_competition() + + @meet_requirement.register + def _(self, requirement: LuauDelightRequirementRequirement): + return self.logic.region.can_reach(LogicRegion.luau) & self.logic.festival.can_get_luau_soup_delight() + + @meet_requirement.register + def _(self, requirement: ForgeInfinityWeaponRequirement): + return self.logic.combat.has_galaxy_weapon & self.logic.region.can_reach(Region.volcano_floor_10) & self.logic.has("Galaxy Soul") + + @meet_requirement.register + def _(self, requirement: CaughtFishRequirement): + if requirement.unique: + return self.logic.fishing.can_catch_many_fish(requirement.number_fish) + return self.logic.fishing.can_catch_many_fish(math.ceil(requirement.number_fish / 10)) & self.logic.time.has_lived_months(requirement.number_fish // 20) + + @meet_requirement.register + def _(self, requirement: MuseumCompletionRequirement): + return self.logic.museum.can_donate_museum_items(requirement.number_donated) + + @meet_requirement.register + def _(self, requirement: FullShipmentRequirement): + return self.logic.shipping.can_ship_everything() + + @meet_requirement.register + def _(self, requirement: BuildingRequirement): + return self.logic.building.has_building(requirement.building) + + @meet_requirement.register + def _(self, requirement: MovieRequirement): + return self.logic.region.can_reach(Region.movie_theater) + + @meet_requirement.register + def _(self, requirement: CookedRecipesRequirement): + return self.logic.cooking.can_have_cooked_recipes(requirement.number_of_recipes) + + @meet_requirement.register + def _(self, requirement: CraftedItemsRequirement): + return self.logic.crafting.can_have_crafted_recipes(requirement.number_of_recipes) + + @meet_requirement.register + def _(self, requirement: HelpWantedRequirement): + return self.logic.quest.can_complete_help_wanteds(requirement.number_of_quests) + + @meet_requirement.register + def _(self, requirement: ShipOneCropRequirement): + crops = self.content.find_tagged_items(ItemTag.CROPSANITY) + crop_rules = [self.logic.shipping.can_ship(crop.name) for crop in crops] + return self.logic.and_(*crop_rules) + + @meet_requirement.register + def _(self, requirement: ReceivedRaccoonsRequirement): + amount = min(requirement.number_of_raccoons, 8, 8 + self.options.bundle_per_room) + if self.options.quest_locations.has_story_quests(): + amount += 1 + return self.logic.received(CommunityUpgrade.raccoon, amount) + + @meet_requirement.register + def _(self, requirement: PrizeMachineRequirement): + return self.logic.grind.can_grind_prize_tickets(requirement.number_of_tickets) + + @meet_requirement.register + def _(self, requirement: AllAchievementsRequirement): + return self.logic.goal.can_complete_perfection() & self.logic.has_progress_percent(100) + + @meet_requirement.register + def _(self, requirement: PerfectionPercentRequirement): + return self.logic.goal.can_complete_perfection() + + @meet_requirement.register + def _(self, requirement: ReadAllBooksRequirement): + books = self.content.find_tagged_items(ItemTag.BOOK_POWER) + book_rules = [self.logic.book.has_book_power(book.name) for book in books] + return self.logic.and_(*book_rules) + + @meet_requirement.register + def _(self, requirement: MinesRequirement): + return self.logic.mine.can_progress_in_the_mines_from_floor(requirement.floor) + + @meet_requirement.register + def _(self, requirement: DangerousMinesRequirement): + return self.logic.region.can_reach(Region.dangerous_mines_100) & self.logic.mine.has_mine_elevator_to_floor(requirement.floor) + + @meet_requirement.register + def _(self, requirement: MonsterKillRequirement): + return self.logic.monster.can_kill_any(requirement.monsters, math.log10(requirement.amount) // 1) + + @meet_requirement.register + def _(self, requirement: CatalogueRequirement): + if self.options.include_endgame_locations == IncludeEndgameLocations.option_true: + return self.logic.received(requirement.catalogue) + return self.logic.true_ diff --git a/worlds/stardew_valley/logic/season_logic.py b/worlds/stardew_valley/logic/season_logic.py index eecfd485823e..d15d0182208c 100644 --- a/worlds/stardew_valley/logic/season_logic.py +++ b/worlds/stardew_valley/logic/season_logic.py @@ -1,11 +1,8 @@ from functools import cached_property -from typing import Iterable, Union +from typing import Iterable from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .time_logic import TimeLogicMixin from ..options import SeasonRandomization from ..stardew_rule import StardewRule, True_, true_ from ..strings.generic_names import Generic @@ -38,6 +35,8 @@ def has_winter(self) -> StardewRule: @cache_self1 def has(self, season: str) -> StardewRule: + assert isinstance(season, str), "use has_any() or has_all() to check multiple seasons at once" + if season == Generic.any: return True_() seasons_order = [Season.spring, Season.summer, Season.fall, Season.winter] diff --git a/worlds/stardew_valley/logic/shipping_logic.py b/worlds/stardew_valley/logic/shipping_logic.py index 9f5ff51876e7..73f102f54483 100644 --- a/worlds/stardew_valley/logic/shipping_logic.py +++ b/worlds/stardew_valley/logic/shipping_logic.py @@ -1,15 +1,8 @@ from functools import cached_property -from typing import Union from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .building_logic import BuildingLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin from ..locations import LocationTags, locations_by_tag -from ..options import ExcludeGingerIsland -from ..options import SpecialOrderLocations from ..stardew_rule import StardewRule from ..strings.building_names import Building @@ -33,15 +26,8 @@ def can_ship(self, item: str) -> StardewRule: def can_ship_everything(self) -> StardewRule: shipsanity_prefix = "Shipsanity: " all_items_to_ship = [] - exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true - exclude_qi = not (self.options.special_order_locations & SpecialOrderLocations.value_qi) - mod_list = self.options.mods.value for location in locations_by_tag[LocationTags.SHIPSANITY_FULL_SHIPMENT]: - if exclude_island and LocationTags.GINGER_ISLAND in location.tags: - continue - if exclude_qi and LocationTags.REQUIRES_QI_ORDERS in location.tags: - continue - if location.mod_name and location.mod_name not in mod_list: + if not self.content.are_all_enabled(location.content_packs): continue all_items_to_ship.append(location.name[len(shipsanity_prefix):]) return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship) diff --git a/worlds/stardew_valley/logic/shirts_logic.py b/worlds/stardew_valley/logic/shirts_logic.py new file mode 100644 index 000000000000..84c13309e000 --- /dev/null +++ b/worlds/stardew_valley/logic/shirts_logic.py @@ -0,0 +1,16 @@ +from .base_logic import BaseLogicMixin, BaseLogic +from ..data.shirt_data import all_considered_shirts + + +class ShirtLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.shirt = ShirtLogic(*args, **kwargs) + + +class ShirtLogic(BaseLogic): + + def initialize_rules(self): + self.registry.shirt_rules.update({ + shirt.name: self.logic.tailoring.can_tailor_shirt(shirt) for shirt in all_considered_shirts + }) diff --git a/worlds/stardew_valley/logic/skill_logic.py b/worlds/stardew_valley/logic/skill_logic.py index b582eb361329..29e830c471a2 100644 --- a/worlds/stardew_valley/logic/skill_logic.py +++ b/worlds/stardew_valley/logic/skill_logic.py @@ -2,18 +2,21 @@ from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic +from ..content.vanilla.base import base_game from ..data.harvest import HarvestCropSource -from ..mods.logic.mod_skills_levels import get_mod_skill_levels from ..stardew_rule import StardewRule, true_, True_, False_ +from ..strings.ap_names.ap_option_names import EatsanityOptionName +from ..strings.currency_names import Currency +from ..strings.food_names import Meal +from ..strings.ingredient_names import Ingredient +from ..strings.metal_names import Mineral from ..strings.performance_names import Performance from ..strings.quality_names import ForageQuality -from ..strings.region_names import Region +from ..strings.region_names import Region, LogicRegion from ..strings.skill_names import Skill, all_mod_skills, all_vanilla_skills -from ..strings.tool_names import ToolMaterial, Tool +from ..strings.tool_names import ToolMaterial, Tool, FishingRod from ..strings.wallet_item_names import Wallet -vanilla_skill_items = ("Farming Level", "Mining Level", "Foraging Level", "Fishing Level", "Combat Level") - class SkillLogicMixin(BaseLogicMixin): def __init__(self, *args, **kwargs): @@ -25,17 +28,18 @@ class SkillLogic(BaseLogic): # Should be cached def can_earn_level(self, skill: str, level: int) -> StardewRule: - if level <= 0: - return True_() + assert level > 0, "There is no level before level 0." - tool_level = min(4, (level - 1) // 2) + tool_level = min(5, (level + 1) // 2) tool_material = ToolMaterial.tiers[tool_level] previous_level_rule = self.logic.skill.has_previous_level(skill, level) if skill == Skill.fishing: # Not checking crab pot as this is used for not randomized skills logic, for which players need a fishing rod to start gaining xp. - xp_rule = self.logic.tool.has_fishing_rod(max(tool_level, 3)) & self.logic.fishing.can_fish_anywhere() + # We want to cap the tool level at 4, because the advanced iridium rod is excluded from logic. + tool_level = min(4, tool_level) + xp_rule = self.logic.tool.has_fishing_rod(FishingRod.tiers[tool_level]) & self.logic.fishing.can_fish_anywhere() elif skill == Skill.farming: xp_rule = self.can_get_farming_xp & self.logic.tool.has_tool(Tool.hoe, tool_material) & self.logic.tool.can_water(tool_level) elif skill == Skill.foraging: @@ -46,7 +50,8 @@ def can_earn_level(self, skill: str, level: int) -> StardewRule: self.logic.magic.can_use_clear_debris_instead_of_tool_level(tool_level) xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5) elif skill == Skill.combat: - combat_tier = Performance.tiers[tool_level] + # Tool level starts at 1, so we need to subtract 1 to get the correct performance tier. + combat_tier = Performance.tiers[tool_level - 1] xp_rule = self.logic.combat.can_fight_at_level(combat_tier) xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5) elif skill in all_mod_skills: @@ -59,17 +64,64 @@ def can_earn_level(self, skill: str, level: int) -> StardewRule: # Should be cached def has_level(self, skill: str, level: int) -> StardewRule: - assert level >= 0, f"There is no level before level 0." + assert level >= 0, "There is no level before level 0." if level == 0: return true_ if self.content.features.skill_progression.is_progressive: + if level > 10: + if skill == Skill.fishing: + return self.logic.received(f"{skill} Level", 10) & self.has_fishing_buffs_available(level - 10) + raise f"Cannot reach level {level} {skill}" return self.logic.received(f"{skill} Level", level) return self.logic.skill.can_earn_level(skill, level) + def has_fishing_buffs_available(self, buff_levels: int) -> StardewRule: + + eat_rule = self.can_eat_fishing_buff(buff_levels) + rod_rule = self.logic.tool.has_fishing_rod(FishingRod.advanced_iridium) + enchant_rule = self.logic.region.can_reach(Region.volcano_floor_10) & self.logic.has(Mineral.prismatic_shard) & self.logic.has( + Currency.cinder_shard) & self.can_eat_fishing_buff(buff_levels - 1) + chef_rule = self.logic.region.can_reach(LogicRegion.desert_festival) & self.can_eat_fishing_buff(buff_levels - 3) + + return eat_rule | (rod_rule & enchant_rule) | chef_rule + + def can_eat_fishing_buff(self, buff_levels: int) -> StardewRule: + enzyme_rule = self.logic.true_ + food_rule = self.logic.true_ + + if buff_levels <= 0: + return self.logic.true_ + if buff_levels >= 6: + return self.logic.false_ + + potential_foods = { + 1: [Meal.maple_bar, Meal.chowder, Meal.trout_soup, Meal.shrimp_cocktail], + 2: [Meal.escargot, Meal.fish_taco], + 3: [Meal.dish_o_the_sea, Meal.fish_stew, Meal.lobster_bisque], + 4: [Meal.seafoam_pudding], + } + + foods_correct_level = [] + foods_only_with_seasoning_level = [] + for level in potential_foods: + if level >= buff_levels: + foods_correct_level.extend(potential_foods[level]) + if level == buff_levels-1: + foods_only_with_seasoning_level.extend(potential_foods[level]) + + normal_food_rule = self.logic.or_(*[self.logic.has(food) for food in foods_correct_level], allow_empty=True) + qi_seasoning_food_rule = self.logic.has(Ingredient.qi_seasoning) &\ + self.logic.or_(*[self.logic.cooking.can_cook(food) for food in foods_only_with_seasoning_level], allow_empty=True) + food_rule = normal_food_rule | qi_seasoning_food_rule + + if EatsanityOptionName.lock_effects in self.options.eatsanity: + enzyme_rule = self.logic.received("Fishing Enzyme", buff_levels) + return food_rule & enzyme_rule + def has_previous_level(self, skill: str, level: int) -> StardewRule: - assert level > 0, f"There is no level before level 0." + assert level > 0, "There is no level before level 0." if level == 1: return true_ @@ -89,10 +141,12 @@ def has_total_level(self, level: int, allow_modded_skills: bool = False) -> Star return True_() if self.content.features.skill_progression.is_progressive: - skills_items = vanilla_skill_items + skills = base_game.skills if allow_modded_skills: - skills_items += get_mod_skill_levels(self.options.mods) - return self.logic.received_n(*skills_items, count=level) + skills = self.content.skills.values() + skill_items = [skill.level_name for skill in skills] + + return self.logic.received_n(*skill_items, count=level) months_with_4_skills = max(1, (level // 4) - 1) months_with_5_skills = max(1, (level // 5) - 1) @@ -123,13 +177,13 @@ def can_get_foraging_xp(self) -> StardewRule: @cached_property def can_get_mining_xp(self) -> StardewRule: tool_rule = self.logic.tool.has_tool(Tool.pickaxe) - stone_rule = self.logic.region.can_reach_any((Region.mines_floor_5, Region.quarry, Region.skull_cavern_25, Region.volcano_floor_5)) + stone_rule = self.logic.region.can_reach_any(Region.mines_floor_5, Region.quarry, Region.skull_cavern_25, Region.volcano_floor_5) return tool_rule & stone_rule @cached_property def can_get_combat_xp(self) -> StardewRule: tool_rule = self.logic.combat.has_any_weapon() - enemy_rule = self.logic.region.can_reach_any((Region.mines_floor_5, Region.skull_cavern_25, Region.volcano_floor_5)) + enemy_rule = self.logic.region.can_reach_any(Region.mines_floor_5, Region.skull_cavern_25, Region.volcano_floor_5) return tool_rule & enemy_rule @cached_property diff --git a/worlds/stardew_valley/logic/source_logic.py b/worlds/stardew_valley/logic/source_logic.py index ecdb6f02a397..44c92a334ecf 100644 --- a/worlds/stardew_valley/logic/source_logic.py +++ b/worlds/stardew_valley/logic/source_logic.py @@ -2,12 +2,17 @@ from typing import Any, Iterable from .base_logic import BaseLogicMixin, BaseLogic +from .tailoring_logic import TailoringSource from ..data.animal import IncubatorSource, OstrichIncubatorSource from ..data.artisan import MachineSource -from ..data.game_item import GenericSource, Source, GameItem, CustomRuleSource +from ..data.fish_data import FishingSource +from ..data.game_item import GenericSource, Source, GameItem, CustomRuleSource, AllRegionsSource from ..data.harvest import ForagingSource, FruitBatsSource, MushroomCaveSource, SeasonalForagingSource, \ HarvestCropSource, HarvestFruitTreeSource, ArtifactSpotSource -from ..data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource +from ..data.monster_data import MonsterSource +from ..data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource, HatMouseSource +from ..strings.ap_names.ap_option_names import CustomLogicOptionName +from ..strings.skill_names import Skill class SourceLogicMixin(BaseLogicMixin): @@ -22,7 +27,10 @@ def has_access_to_item(self, item: GameItem): rules = [] if self.content.features.cropsanity.is_included(item): - rules.append(self.logic.received(item.name)) + unlock_rule = self.logic.received(item.name) + if CustomLogicOptionName.critical_free_samples in self.options.custom_logic: + return unlock_rule + rules.append(unlock_rule) rules.append(self.logic.source.has_access_to_any(item.sources)) return self.logic.and_(*rules) @@ -37,7 +45,11 @@ def has_access_to(self, source: Any): @has_access_to.register def _(self, source: GenericSource): - return self.logic.region.can_reach_any(source.regions) if source.regions else self.logic.true_ + return self.logic.region.can_reach_any(*source.regions) if source.regions else self.logic.true_ + + @has_access_to.register + def _(self, source: AllRegionsSource): + return self.logic.region.can_reach_all(*source.regions) if source.regions else self.logic.true_ @has_access_to.register def _(self, source: CustomRuleSource): @@ -64,6 +76,10 @@ def _(self, _: MushroomCaveSource): def _(self, source: ShopSource): return self.logic.money.can_shop_from(source) + @has_access_to.register + def _(self, source: HatMouseSource): + return self.logic.money.can_shop_from_hat_mouse(source) + @has_access_to.register def _(self, source: HarvestFruitTreeSource): return self.logic.harvesting.can_harvest_tree_from(source) @@ -103,3 +119,15 @@ def _(self, source: FishingTreasureChestSource): @has_access_to.register def _(self, source: ArtifactSpotSource): return self.logic.grind.can_grind_artifact_spots(source.amount) + + @has_access_to.register + def _(self, source: MonsterSource): + return self.logic.monster.can_kill_any(source.monsters, source.amount_tier) + + @has_access_to.register + def _(self, source: TailoringSource): + return self.logic.tailoring.can_tailor(*source.tailoring_items) + + @has_access_to.register + def _(self, source: FishingSource): + return self.logic.fishing.can_fish_at(source.region) & self.logic.skill.has_level(Skill.fishing, source.fishing_level) & self.logic.tool.has_fishing_rod(source.minimum_rod) diff --git a/worlds/stardew_valley/logic/special_items_logic.py b/worlds/stardew_valley/logic/special_items_logic.py new file mode 100644 index 000000000000..90e18b790091 --- /dev/null +++ b/worlds/stardew_valley/logic/special_items_logic.py @@ -0,0 +1,45 @@ +from .base_logic import BaseLogic, BaseLogicMixin +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..stardew_rule import StardewRule +from ..strings.ap_names.ap_option_names import SecretsanityOptionName +from ..strings.craftable_names import Consumable +from ..strings.forageable_names import Forageable +from ..strings.metal_names import Artifact +from ..strings.quest_names import Quest +from ..strings.region_names import Region +from ..strings.season_names import Season +from ..strings.special_item_names import SpecialItem +from ..strings.villager_names import NPC + + +class SpecialItemsLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.special_items = SpecialItemsLogic(*args, **kwargs) + + +class SpecialItemsLogic(BaseLogic): + + def has_purple_shorts(self) -> StardewRule: + has_first_shorts = self.logic.season.has(Season.summer) &\ + self.logic.region.can_reach(Region.ranch) &\ + self.logic.relationship.has_hearts(NPC.marnie, 2) + has_repeatable_shorts = self.logic.region.can_reach(Region.purple_shorts_maze) & self.logic.has(Consumable.warp_totem_farm) + return has_first_shorts & has_repeatable_shorts + + def has_far_away_stone(self) -> StardewRule: + sacrifice_rule = self.logic.has(Artifact.ancient_doll) & self.logic.region.can_reach_any(Region.mines_floor_100, Region.volcano_floor_10) + if SecretsanityOptionName.easy in self.options.secretsanity: + return sacrifice_rule & self.logic.received(SpecialItem.far_away_stone) + return sacrifice_rule + + def has_solid_gold_lewis(self) -> StardewRule: + if SecretsanityOptionName.secret_notes in self.options.secretsanity: + return self.logic.received(SpecialItem.solid_gold_lewis) & self.logic.region.can_reach(Region.town) + return self.logic.has(Forageable.secret_note) & self.logic.region.can_reach(Region.town) + + def has_advanced_tv_remote(self) -> StardewRule: + george_rule = self.logic.relationship.has_hearts(NPC.george, 10) + if ginger_island_content_pack.name in self.content.registered_packs: + return george_rule + return self.logic.quest.can_complete_quest(Quest.the_pirates_wife) & george_rule diff --git a/worlds/stardew_valley/logic/special_order_logic.py b/worlds/stardew_valley/logic/special_order_logic.py index 73276ce97f38..10f601afbb66 100644 --- a/worlds/stardew_valley/logic/special_order_logic.py +++ b/worlds/stardew_valley/logic/special_order_logic.py @@ -3,6 +3,7 @@ from .base_logic import BaseLogicMixin, BaseLogic from ..content.vanilla.ginger_island import ginger_island_content_pack from ..content.vanilla.qi_board import qi_board_content_pack +from ..options import SpecialOrderLocations from ..stardew_rule import StardewRule, Has, false_ from ..strings.animal_product_names import AnimalProduct from ..strings.ap_names.transport_names import Transportation @@ -10,6 +11,7 @@ from ..strings.crop_names import Vegetable, Fruit from ..strings.fertilizer_names import Fertilizer from ..strings.fish_names import Fish +from ..strings.food_names import Beverage, Meal from ..strings.forageable_names import Forageable from ..strings.machine_names import Machine from ..strings.material_names import Material @@ -50,8 +52,8 @@ def initialize_rules(self): SpecialOrder.robins_resource_rush: self.logic.relationship.can_meet(NPC.robin) & self.logic.ability.can_chop_perfectly() & self.logic.has(Fertilizer.tree) & self.logic.ability.can_mine_perfectly(), SpecialOrder.juicy_bugs_wanted: self.logic.has(Loot.bug_meat), - SpecialOrder.a_curious_substance: self.logic.region.can_reach(Region.wizard_tower), - SpecialOrder.prismatic_jelly: self.logic.region.can_reach(Region.wizard_tower), + SpecialOrder.a_curious_substance: self.logic.region.can_reach_all(*(Region.wizard_tower, Region.mines_floor_55,)), + SpecialOrder.prismatic_jelly: self.logic.region.can_reach_all(*(Region.wizard_tower, Region.mines_floor_25,)), }) @@ -80,8 +82,9 @@ def initialize_rules(self): self.logic.ability.can_mine_perfectly_in_the_skull_cavern(), SpecialOrder.qis_hungry_challenge: self.logic.ability.can_mine_perfectly_in_the_skull_cavern(), SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.shipping.can_use_shipping_bin & - (self.logic.money.can_spend_at(Region.saloon, 205000) | self.logic.money.can_spend_at(Region.pierre_store, 170000)), - SpecialOrder.qis_kindness: self.logic.relationship.can_give_loved_gifts_to_everyone(), + (self.logic.money.can_spend_at(Region.saloon, 205000) & self.logic.cooking.can_cook(Beverage.triple_shot_espresso)) & + (self.logic.money.can_spend_at(Region.pierre_store, 170000) & self.logic.cooking.can_cook(Meal.bread)), + SpecialOrder.qis_kindness: self.logic.gifts.can_give_loved_gifts_to_everyone(), SpecialOrder.extended_family: self.logic.ability.can_fish_perfectly() & self.logic.has(Fish.angler) & self.logic.has(Fish.glacierfish) & self.logic.has(Fish.crimsonfish) & self.logic.has(Fish.mutant_carp) & self.logic.has(Fish.legend), SpecialOrder.danger_in_the_deep: self.logic.ability.can_mine_perfectly() & self.logic.mine.has_mine_elevator_to_floor(120), @@ -103,3 +106,10 @@ def can_complete_special_order(self, special_order: str) -> StardewRule: def has_island_transport(self) -> StardewRule: return self.logic.received(Transportation.island_obelisk) | self.logic.received(Transportation.boat_repair) + + def can_get_radioactive_ore(self) -> StardewRule: + if not self.options.special_order_locations & SpecialOrderLocations.value_qi: + return self.logic.false_ + + return self.logic.ability.can_mine_perfectly() & self.logic.region.can_reach(Region.qi_walnut_room) &\ + self.logic.region.can_reach_all(*(Region.mines_floor_100, Region.skull_cavern_100)) diff --git a/worlds/stardew_valley/logic/tailoring_logic.py b/worlds/stardew_valley/logic/tailoring_logic.py new file mode 100644 index 000000000000..00290f9d5830 --- /dev/null +++ b/worlds/stardew_valley/logic/tailoring_logic.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass +from typing import Tuple + +from .base_logic import BaseLogicMixin, BaseLogic +from ..data.game_item import Source +from ..data.shirt_data import Shirt +from ..stardew_rule import StardewRule +from ..strings.artisan_good_names import ArtisanGood +from ..strings.machine_names import Machine +from ..strings.region_names import Region +from ..strings.villager_names import NPC + + +@dataclass(frozen=True, kw_only=True) +class TailoringSource(Source): + tailoring_items: Tuple[str, ...] + + +class TailoringLogicMixin(BaseLogicMixin): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.tailoring = TailoringLogic(*args, **kwargs) + + +class TailoringLogic(BaseLogic): + + def can_tailor_shirt(self, shirt: Shirt) -> StardewRule: + return self.can_tailor(*shirt.required_items) + + def can_tailor(self, *items: str) -> StardewRule: + return self.has_tailoring() & self.logic.has(ArtisanGood.cloth) & self.logic.has_any(*items) + + def has_tailoring(self) -> StardewRule: + sewing_machine_rule = self.logic.region.can_reach(Region.haley_house) | self.logic.has(Machine.sewing_machine) + return sewing_machine_rule & self.logic.relationship.can_meet(NPC.emily) diff --git a/worlds/stardew_valley/logic/time_logic.py b/worlds/stardew_valley/logic/time_logic.py index d26723d2a58b..c42570b74823 100644 --- a/worlds/stardew_valley/logic/time_logic.py +++ b/worlds/stardew_valley/logic/time_logic.py @@ -1,14 +1,12 @@ from functools import cached_property -from typing import Union from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin from ..stardew_rule import StardewRule, HasProgressionPercent ONE_YEAR = 4 MAX_MONTHS = 3 * ONE_YEAR -PERCENT_REQUIRED_FOR_MAX_MONTHS = 48 +PERCENT_REQUIRED_FOR_MAX_MONTHS = 64 MONTH_COEFFICIENT = PERCENT_REQUIRED_FOR_MAX_MONTHS // MAX_MONTHS MIN_ITEMS = 10 diff --git a/worlds/stardew_valley/logic/tool_logic.py b/worlds/stardew_valley/logic/tool_logic.py index dba8bb29804c..2ec8965e2921 100644 --- a/worlds/stardew_valley/logic/tool_logic.py +++ b/worlds/stardew_valley/logic/tool_logic.py @@ -1,23 +1,28 @@ -from typing import Union, Iterable, Tuple +from typing import Union, Iterable from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from ..stardew_rule import StardewRule, True_, False_ +from ..stardew_rule import StardewRule, False_ from ..strings.ap_names.skill_level_names import ModSkillLevel from ..strings.region_names import Region, LogicRegion +from ..strings.skill_names import Skill from ..strings.spells import MagicSpell -from ..strings.tool_names import ToolMaterial, Tool, APTool +from ..strings.tool_names import ToolMaterial, Tool, FishingRod fishing_rod_prices = { - 3: 1800, - 4: 7500, + FishingRod.training: 25, + FishingRod.bamboo: 500, + FishingRod.fiberglass: 1800, + FishingRod.iridium: 7500, + FishingRod.advanced_iridium: 25000, } tool_materials = { - ToolMaterial.copper: 1, - ToolMaterial.iron: 2, - ToolMaterial.gold: 3, - ToolMaterial.iridium: 4 + ToolMaterial.basic: 1, + ToolMaterial.copper: 2, + ToolMaterial.iron: 3, + ToolMaterial.gold: 4, + ToolMaterial.iridium: 5 } tool_upgrade_prices = { @@ -36,41 +41,36 @@ def __init__(self, *args, **kwargs): class ToolLogic(BaseLogic): - def has_all_tools(self, tools: Iterable[Tuple[str, str]]): + def has_all_tools(self, tools: Iterable[tuple[str, str]]): return self.logic.and_(*(self.logic.tool.has_tool(tool, material) for tool, material in tools)) - # Should be cached - def has_tool(self, tool: str, material: str = ToolMaterial.basic) -> StardewRule: + def has_tool_generic(self, tool: str, material: str) -> StardewRule: + """I hope you know what you're doing...""" if tool == Tool.fishing_rod: - return self.logic.tool.has_fishing_rod(tool_materials[material]) - - if tool == Tool.pan and material == ToolMaterial.basic: - material = ToolMaterial.copper # The first Pan is the copper one, so the basic one does not exist + return self.has_fishing_rod(material) + if tool == Tool.scythe: + return self.has_scythe(material) + if tool == Tool.pan: + return self.has_pan(material) + return self.has_tool(tool, material) - if material == ToolMaterial.basic or tool == Tool.scythe: - return True_() + # Should be cached + def has_tool(self, tool: str, material: str = ToolMaterial.basic) -> StardewRule: + assert tool != Tool.fishing_rod, "Use has_fishing_rod instead of has_tool for fishing rods." + assert tool != Tool.scythe, "Use has_scythe instead of has_tool for scythes." + assert tool != Tool.pan, "Use has_pan instead of has_tool for pans." if self.content.features.tool_progression.is_progressive: - return self.logic.received(f"Progressive {tool}", tool_materials[material]) + return self.logic.tool._has_progressive_tool(tool, tool_materials[material]) - can_upgrade_rule = self.logic.tool._can_purchase_upgrade(material) - if tool == Tool.pan: - has_base_pan = self.logic.received("Glittering Boulder Removed") & self.logic.region.can_reach(Region.mountain) - if material == ToolMaterial.copper: - return has_base_pan - return has_base_pan & can_upgrade_rule + if material == ToolMaterial.basic: + return self.logic.true_ - return can_upgrade_rule + return self.logic.tool._can_purchase_upgrade(material) @cache_self1 def can_mine_using(self, material: str) -> StardewRule: - if material == ToolMaterial.basic: - return self.logic.true_ - - if self.content.features.tool_progression.is_progressive: - return self.logic.received(APTool.pickaxe, tool_materials[material]) - else: - return self.logic.tool._can_purchase_upgrade(material) + return self.logic.tool.has_tool(Tool.pickaxe, material) @cache_self1 def _can_purchase_upgrade(self, material: str) -> StardewRule: @@ -80,17 +80,73 @@ def can_use_tool_at(self, tool: str, material: str, region: str) -> StardewRule: return self.has_tool(tool, material) & self.logic.region.can_reach(region) @cache_self1 - def has_fishing_rod(self, level: int) -> StardewRule: - assert 1 <= level <= 4, "Fishing rod 0 isn't real, it can't hurt you. Training is 1, Bamboo is 2, Fiberglass is 3 and Iridium is 4." + def has_pan(self, material: str = ToolMaterial.copper) -> StardewRule: + assert material != ToolMaterial.basic, "The basic pan does not exist." + + if self.content.features.tool_progression.is_progressive: + # The is no basic tier for the pan, so copper is level 1 instead of 2 + level = tool_materials[material] - 1 + return self.logic.tool._has_progressive_tool(Tool.pan, level) + + pan_cutscene_rule = self.logic.received("Glittering Boulder Removed") & self.logic.region.can_reach(Region.mountain) + if material == ToolMaterial.copper: + return pan_cutscene_rule + return pan_cutscene_rule & self.logic.tool._can_purchase_upgrade(material) + + @cache_self1 + def has_scythe(self, material: str = ToolMaterial.basic) -> StardewRule: if self.content.features.tool_progression.is_progressive: - return self.logic.received(APTool.fishing_rod, level) + if material == ToolMaterial.basic: + return self._has_progressive_tool(Tool.scythe, 1) + if material == ToolMaterial.gold: + return self._has_progressive_tool(Tool.scythe, 2) + if material == ToolMaterial.iridium: + return self._has_progressive_tool(Tool.scythe, 3) + raise ValueError(f"Scythe material [{material}] is not valid.") + + if material == ToolMaterial.basic: + return self.logic.true_ + if material == ToolMaterial.gold: + return self.logic.tool._has_progressive_tool(Tool.scythe, 1) + if material == ToolMaterial.iridium: + return self.logic.skill.has_mastery(Skill.farming) - if level <= 2: - # We assume you always have access to the Bamboo pole, because mod side there is a builtin way to get it back. - return self.logic.region.can_reach(Region.beach) + return self.has_tool(Tool.scythe, material) - return self.logic.money.can_spend_at(Region.fish_shop, fishing_rod_prices[level]) + @cache_self1 + def has_fishing_rod(self, material: str = FishingRod.training) -> StardewRule: + level = FishingRod.material_to_tier[material] + tool_progression = self.content.features.tool_progression + + rebuy_rule = self.logic.money.can_spend_at(Region.fish_shop, fishing_rod_prices[material]) + + if tool_progression.is_progressive: + return self.logic.tool._has_progressive_tool(Tool.fishing_rod, level) & rebuy_rule + + if material == FishingRod.bamboo: + return self.logic.region.can_reach(Region.beach) & rebuy_rule + if material == FishingRod.fiberglass: + return self.logic.skill.has_level(Skill.fishing, 2) & rebuy_rule + if material == FishingRod.iridium: + return self.logic.skill.has_level(Skill.fishing, 6) & rebuy_rule + if material == FishingRod.advanced_iridium: + return self.logic.skill.has_mastery(Skill.fishing) & rebuy_rule + return rebuy_rule + + def _has_progressive_tool(self, tool: str, amount: int) -> StardewRule: + tool_progression = self.content.features.tool_progression + amount -= tool_progression.starting_tools[tool] + + # Meaning you started with the tool + if amount <= 0: + return self.logic.true_ + + return self.logic.received(tool_progression.to_progressive_item_name(tool), amount) + + @cache_self1 + def _can_purchase_upgrade(self, material: str) -> StardewRule: + return self.logic.region.can_reach(LogicRegion.blacksmith_upgrade(material)) # Should be cached def can_forage(self, season: Union[str, Iterable[str]], region: str = Region.forest, need_hoe: bool = False) -> StardewRule: @@ -105,7 +161,7 @@ def can_forage(self, season: Union[str, Iterable[str]], region: str = Region.for return season_rule & region_rule @cache_self1 - def can_water(self, level: int) -> StardewRule: + def can_water(self, level: int = 1) -> StardewRule: tool_rule = self.logic.tool.has_tool(Tool.watering_can, ToolMaterial.tiers[level]) spell_rule = self.logic.received(MagicSpell.water) & self.logic.magic.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, level) return tool_rule | spell_rule diff --git a/worlds/stardew_valley/logic/traveling_merchant_logic.py b/worlds/stardew_valley/logic/traveling_merchant_logic.py index 743ff9949bec..6c5566a9a05d 100644 --- a/worlds/stardew_valley/logic/traveling_merchant_logic.py +++ b/worlds/stardew_valley/logic/traveling_merchant_logic.py @@ -1,7 +1,4 @@ -from typing import Union - from .base_logic import BaseLogic, BaseLogicMixin -from .received_logic import ReceivedLogicMixin from ..stardew_rule import True_ from ..strings.calendar_names import Weekday diff --git a/worlds/stardew_valley/logic/wallet_logic.py b/worlds/stardew_valley/logic/wallet_logic.py index eb7afb9af300..e73b99acb2f8 100644 --- a/worlds/stardew_valley/logic/wallet_logic.py +++ b/worlds/stardew_valley/logic/wallet_logic.py @@ -1,5 +1,4 @@ from .base_logic import BaseLogic, BaseLogicMixin -from .received_logic import ReceivedLogicMixin from ..stardew_rule import StardewRule from ..strings.wallet_item_names import Wallet @@ -17,3 +16,10 @@ def can_speak_dwarf(self) -> StardewRule: def has_rusty_key(self) -> StardewRule: return self.logic.received(Wallet.rusty_key) + + # These could be tested against gender if gender ever becomes a yaml option + def has_mens_locker_key(self) -> StardewRule: + return self.logic.received(Wallet.mens_locker_key) + + def has_womens_locker_key(self) -> StardewRule: + return self.logic.received(Wallet.womens_locker_key) diff --git a/worlds/stardew_valley/logic/walnut_logic.py b/worlds/stardew_valley/logic/walnut_logic.py index fb83b6590717..3b704c197fd1 100644 --- a/worlds/stardew_valley/logic/walnut_logic.py +++ b/worlds/stardew_valley/logic/walnut_logic.py @@ -1,13 +1,14 @@ from functools import cached_property from .base_logic import BaseLogic, BaseLogicMixin -from ..options import ExcludeGingerIsland, Walnutsanity -from ..stardew_rule import StardewRule, False_, True_ +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..options import Walnutsanity +from ..stardew_rule import StardewRule from ..strings.ap_names.ap_option_names import WalnutsanityOptionName from ..strings.ap_names.event_names import Event from ..strings.craftable_names import Furniture from ..strings.crop_names import Fruit -from ..strings.metal_names import Mineral, Fossil +from ..strings.metal_names import Fossil from ..strings.region_names import Region from ..strings.seed_names import Seed @@ -21,10 +22,10 @@ def __init__(self, *args, **kwargs): class WalnutLogic(BaseLogic): def has_walnut(self, number: int) -> StardewRule: - if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: - return False_() + if not self.content.is_enabled(ginger_island_content_pack): + return self.logic.false_ if number <= 0: - return True_() + return self.logic.true_ if self.options.walnutsanity == Walnutsanity.preset_none: return self.can_get_walnuts(number) @@ -95,8 +96,8 @@ def can_get_walnuts(self, number: int) -> StardewRule: return self.logic.and_(*reach_walnut_regions) if number <= 50: return reach_entire_island - gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz) - return reach_entire_island & self.logic.has(Fruit.banana) & self.logic.has_all(*gems) & \ + + return reach_entire_island & self.logic.has(Fruit.banana) & self.logic.museum.has_all_gems() & \ self.logic.ability.can_mine_perfectly() & self.logic.ability.can_fish_perfectly() & \ self.logic.has(Furniture.flute_block) & self.logic.has(Seed.melon) & self.logic.has(Seed.wheat) & \ self.logic.has(Seed.garlic) & self.can_complete_field_office() diff --git a/worlds/stardew_valley/mods/logic/deepwoods_logic.py b/worlds/stardew_valley/mods/logic/deepwoods_logic.py index 17db3c0a6fe5..6d56f4185dbb 100644 --- a/worlds/stardew_valley/mods/logic/deepwoods_logic.py +++ b/worlds/stardew_valley/mods/logic/deepwoods_logic.py @@ -1,4 +1,3 @@ -from ..mod_data import ModNames from ...logic.base_logic import BaseLogicMixin, BaseLogic from ...options import ElevatorProgression from ...stardew_rule import StardewRule, True_, true_ @@ -55,9 +54,9 @@ def can_pull_sword(self) -> StardewRule: rules = [self.logic.received(DeepWoodsItem.pendant_depths) & self.logic.received(DeepWoodsItem.pendant_community) & self.logic.received(DeepWoodsItem.pendant_elder), self.logic.skill.has_total_level(40)] - if ModNames.luck_skill in self.options.mods: + if ModSkill.luck in self.content.skills: rules.append(self.logic.skill.has_level(ModSkill.luck, 7)) else: - rules.append( - self.logic.has(Meal.magic_rock_candy)) # You need more luck than this, but it'll push the logic down a ways; you can get the rest there. + # You need more luck than this, but it'll push the logic down a ways; you can get the rest there. + rules.append(self.logic.has(Meal.magic_rock_candy)) return self.logic.and_(*rules) diff --git a/worlds/stardew_valley/mods/logic/elevator_logic.py b/worlds/stardew_valley/mods/logic/elevator_logic.py index 8e154492e4c8..9060246caf6d 100644 --- a/worlds/stardew_valley/mods/logic/elevator_logic.py +++ b/worlds/stardew_valley/mods/logic/elevator_logic.py @@ -12,6 +12,6 @@ def __init__(self, *args, **kwargs): class ModElevatorLogic(BaseLogic): def has_skull_cavern_elevator_to_floor(self, floor: int) -> StardewRule: - if self.options.elevator_progression != ElevatorProgression.option_vanilla and ModNames.skull_cavern_elevator in self.options.mods: + if self.options.elevator_progression != ElevatorProgression.option_vanilla and self.content.is_enabled(ModNames.skull_cavern_elevator): return self.logic.received("Progressive Skull Cavern Elevator", floor // 25) return True_() diff --git a/worlds/stardew_valley/mods/logic/item_logic.py b/worlds/stardew_valley/mods/logic/item_logic.py index 7394d82ba138..ec15d8c98133 100644 --- a/worlds/stardew_valley/mods/logic/item_logic.py +++ b/worlds/stardew_valley/mods/logic/item_logic.py @@ -27,14 +27,14 @@ class ModItemLogic(BaseLogic): def get_modded_item_rules(self) -> Dict[str, StardewRule]: items = dict() - if ModNames.boarding_house in self.options.mods: + if self.content.is_enabled(ModNames.boarding_house): items.update(self.get_boarding_house_item_rules()) return items def modify_vanilla_item_rules_with_mod_additions(self, item_rule: Dict[str, StardewRule]): - if ModNames.sve in self.options.mods: + if self.content.is_enabled(ModNames.sve): item_rule.update(self.get_modified_item_rules_for_sve(item_rule)) - if ModNames.deepwoods in self.options.mods: + if self.content.is_enabled(ModNames.deepwoods): item_rule.update(self.get_modified_item_rules_for_deep_woods(item_rule)) return item_rule @@ -69,63 +69,29 @@ def get_modified_item_rules_for_deep_woods(self, items: Dict[str, StardewRule]): return options_to_update def get_boarding_house_item_rules(self): + can_reach_any_boarding_house_region = self.logic.region.can_reach_any(BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, + BoardingHouseRegion.lost_valley_house_2) + can_fight_at_good_level_in_boarding_house = can_reach_any_boarding_house_region & self.logic.combat.can_fight_at_level(Performance.good) + can_fight_at_great_level_in_boarding_house = can_reach_any_boarding_house_region & self.logic.combat.can_fight_at_level(Performance.great) return { # Mob Drops from lost valley enemies - ModArtisanGood.pterodactyl_egg: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_claw: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_ribs: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_vertebra: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_skull: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_phalange: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_l_wing_bone: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.pterodactyl_r_wing_bone: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_skull: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_tooth: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_femur: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_pelvis: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_ribs: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_vertebra: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.dinosaur_claw: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.good), - ModFossil.neanderthal_skull: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.great), - ModFossil.neanderthal_ribs: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.great), - ModFossil.neanderthal_pelvis: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.great), - ModFossil.neanderthal_limb_bones: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, - BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( - Performance.great), + ModArtisanGood.pterodactyl_egg: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_claw: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_ribs: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_vertebra: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_skull: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_phalange: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_l_wing_bone: can_fight_at_good_level_in_boarding_house, + ModFossil.pterodactyl_r_wing_bone: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_skull: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_tooth: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_femur: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_pelvis: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_ribs: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_vertebra: can_fight_at_good_level_in_boarding_house, + ModFossil.dinosaur_claw: can_fight_at_good_level_in_boarding_house, + ModFossil.neanderthal_skull: can_fight_at_great_level_in_boarding_house, + ModFossil.neanderthal_ribs: can_fight_at_great_level_in_boarding_house, + ModFossil.neanderthal_pelvis: can_fight_at_great_level_in_boarding_house, + ModFossil.neanderthal_limb_bones: can_fight_at_great_level_in_boarding_house, } diff --git a/worlds/stardew_valley/mods/logic/magic_logic.py b/worlds/stardew_valley/mods/logic/magic_logic.py index 2e3a0e1f97b2..a8135407f443 100644 --- a/worlds/stardew_valley/mods/logic/magic_logic.py +++ b/worlds/stardew_valley/mods/logic/magic_logic.py @@ -1,6 +1,6 @@ from ...logic.base_logic import BaseLogicMixin, BaseLogic from ...mods.mod_data import ModNames -from ...stardew_rule import StardewRule, False_ +from ...stardew_rule import StardewRule from ...strings.ap_names.skill_level_names import ModSkillLevel from ...strings.region_names import MagicRegion from ...strings.spells import MagicSpell, all_spells @@ -15,19 +15,21 @@ def __init__(self, *args, **kwargs): # TODO add logic.mods.magic for altar class MagicLogic(BaseLogic): def can_use_clear_debris_instead_of_tool_level(self, level: int) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + return self.logic.received(MagicSpell.clear_debris) & self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, level) def can_use_altar(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() - spell_rule = False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + return self.logic.region.can_reach(MagicRegion.altar) & self.logic.received_any(*all_spells) def has_any_spell(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + return self.can_use_altar() def has_attack_spell_count(self, count: int) -> StardewRule: @@ -42,37 +44,42 @@ def has_support_spell_count(self, count: int) -> StardewRule: return self.logic.count(count, *support_spell_rule) def has_decent_spells(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 2) magic_attack_options_rule = self.has_attack_spell_count(1) return magic_resource_rule & magic_attack_options_rule def has_good_spells(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 4) magic_attack_options_rule = self.has_attack_spell_count(2) magic_support_options_rule = self.has_support_spell_count(1) return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule def has_great_spells(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 6) magic_attack_options_rule = self.has_attack_spell_count(3) magic_support_options_rule = self.has_support_spell_count(1) return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule def has_amazing_spells(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 8) magic_attack_options_rule = self.has_attack_spell_count(4) magic_support_options_rule = self.has_support_spell_count(2) return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule def can_blink(self) -> StardewRule: - if ModNames.magic not in self.options.mods: - return False_() + if not self.content.is_enabled(ModNames.magic): + return self.logic.false_ + return self.logic.received(MagicSpell.blink) & self.can_use_altar() diff --git a/worlds/stardew_valley/mods/logic/mod_skills_levels.py b/worlds/stardew_valley/mods/logic/mod_skills_levels.py deleted file mode 100644 index 32b3368a8c8b..000000000000 --- a/worlds/stardew_valley/mods/logic/mod_skills_levels.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Tuple - -from ...mods.mod_data import ModNames -from ...options import Mods -from ...strings.ap_names.mods.mod_items import SkillLevel - - -def get_mod_skill_levels(mods: Mods) -> Tuple[str]: - skills_items = [] - if ModNames.luck_skill in mods: - skills_items.append(SkillLevel.luck) - if ModNames.socializing_skill in mods: - skills_items.append(SkillLevel.socializing) - if ModNames.magic in mods: - skills_items.append(SkillLevel.magic) - if ModNames.archaeology in mods: - skills_items.append(SkillLevel.archaeology) - if ModNames.binning_skill in mods: - skills_items.append(SkillLevel.binning) - if ModNames.cooking_skill in mods: - skills_items.append(SkillLevel.cooking) - return tuple(skills_items) diff --git a/worlds/stardew_valley/mods/logic/quests_logic.py b/worlds/stardew_valley/mods/logic/quests_logic.py index 8b1eca7fc213..9f14d97d7127 100644 --- a/worlds/stardew_valley/mods/logic/quests_logic.py +++ b/worlds/stardew_valley/mods/logic/quests_logic.py @@ -39,7 +39,7 @@ def get_modded_quest_rules(self) -> Dict[str, StardewRule]: return quests def _get_juna_quest_rules(self): - if ModNames.juna not in self.options.mods: + if not self.content.is_enabled(ModNames.juna): return {} return { @@ -48,7 +48,7 @@ def _get_juna_quest_rules(self): } def _get_mr_ginger_quest_rules(self): - if ModNames.ginger not in self.options.mods: + if not self.content.is_enabled(ModNames.ginger): return {} return { @@ -56,7 +56,7 @@ def _get_mr_ginger_quest_rules(self): } def _get_ayeisha_quest_rules(self): - if ModNames.ayeisha not in self.options.mods: + if not self.content.is_enabled(ModNames.ayeisha): return {} return { @@ -65,7 +65,7 @@ def _get_ayeisha_quest_rules(self): } def _get_sve_quest_rules(self): - if ModNames.sve not in self.options.mods: + if not self.content.is_enabled(ModNames.sve): return {} return { @@ -89,9 +89,8 @@ def has_completed_aurora_vineyard_bundle(self): return self.logic.received(SVEQuestItem.aurora_vineyard_reclamation) return self.logic.quest.can_complete_quest(ModQuest.AuroraVineyard) - def _get_distant_lands_quest_rules(self): - if ModNames.distant_lands not in self.options.mods: + if not self.content.is_enabled(ModNames.distant_lands): return {} return { @@ -110,7 +109,7 @@ def _get_distant_lands_quest_rules(self): } def _get_boarding_house_quest_rules(self): - if ModNames.boarding_house not in self.options.mods: + if not self.content.is_enabled(ModNames.boarding_house): return {} return { @@ -118,7 +117,7 @@ def _get_boarding_house_quest_rules(self): } def _get_hat_mouse_quest_rules(self): - if ModNames.lacey not in self.options.mods: + if not self.content.is_enabled(ModNames.lacey): return {} return { diff --git a/worlds/stardew_valley/mods/logic/skills_logic.py b/worlds/stardew_valley/mods/logic/skills_logic.py index d57e6c59313a..24e7c99b73ef 100644 --- a/worlds/stardew_valley/mods/logic/skills_logic.py +++ b/worlds/stardew_valley/mods/logic/skills_logic.py @@ -1,6 +1,5 @@ from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...mods.mod_data import ModNames -from ...stardew_rule import StardewRule, False_, True_, And +from ...stardew_rule import StardewRule, True_, And from ...strings.building_names import Building from ...strings.craftable_names import ModCraftable, ModMachine from ...strings.geode_names import Geode @@ -28,19 +27,23 @@ def has_mod_level(self, skill: str, level: int) -> StardewRule: return self.can_earn_mod_skill_level(skill, level) def can_earn_mod_skill_level(self, skill: str, level: int) -> StardewRule: - if ModNames.luck_skill in self.options.mods and skill == ModSkill.luck: + if not skill in self.content.skills: + return self.logic.false_ + + if skill == ModSkill.luck: return self.can_earn_luck_skill_level(level) - if ModNames.magic in self.options.mods and skill == ModSkill.magic: + if skill == ModSkill.magic: return self.can_earn_magic_skill_level(level) - if ModNames.socializing_skill in self.options.mods and skill == ModSkill.socializing: + if skill == ModSkill.socializing: return self.can_earn_socializing_skill_level(level) - if ModNames.archaeology in self.options.mods and skill == ModSkill.archaeology: + if skill == ModSkill.archaeology: return self.can_earn_archaeology_skill_level(level) - if ModNames.cooking_skill in self.options.mods and skill == ModSkill.cooking: + if skill == ModSkill.cooking: return self.can_earn_cooking_skill_level(level) - if ModNames.binning_skill in self.options.mods and skill == ModSkill.binning: + if skill == ModSkill.binning: return self.can_earn_binning_skill_level(level) - return False_() + + return self.logic.false_ def can_earn_luck_skill_level(self, level: int) -> StardewRule: if level >= 6: @@ -71,17 +74,17 @@ def can_earn_archaeology_skill_level(self, level: int) -> StardewRule: shifter_rule = True_() preservation_rule = True_() if self.content.features.skill_progression.is_progressive: - shifter_rule = self.logic.has(ModCraftable.water_shifter) + shifter_rule = self.logic.has(ModCraftable.water_sifter) preservation_rule = self.logic.has(ModMachine.hardwood_preservation_chamber) - if level >= 8: - tool_rule = self.logic.tool.has_tool(Tool.pan, ToolMaterial.iridium) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.gold) + if level > 8: + tool_rule = self.logic.tool.has_pan(ToolMaterial.iridium) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.gold) return tool_rule & shifter_rule & preservation_rule - if level >= 5: - tool_rule = self.logic.tool.has_tool(Tool.pan, ToolMaterial.gold) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iron) - return tool_rule & shifter_rule + if level > 6: + tool_rule = self.logic.tool.has_pan(ToolMaterial.gold) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iron) + return tool_rule & preservation_rule if level >= 3: - return self.logic.tool.has_tool(Tool.pan, ToolMaterial.iron) | self.logic.tool.has_tool(Tool.hoe, ToolMaterial.copper) - return self.logic.tool.has_tool(Tool.pan, ToolMaterial.copper) | self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic) + return self.logic.tool.has_pan(ToolMaterial.iron) | self.logic.tool.has_tool(Tool.hoe, ToolMaterial.copper) + return self.logic.tool.has_pan() | self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic) def can_earn_cooking_skill_level(self, level: int) -> StardewRule: if level >= 6: diff --git a/worlds/stardew_valley/mods/logic/special_orders_logic.py b/worlds/stardew_valley/mods/logic/special_orders_logic.py index f697661419a9..ba5d29319175 100644 --- a/worlds/stardew_valley/mods/logic/special_orders_logic.py +++ b/worlds/stardew_valley/mods/logic/special_orders_logic.py @@ -25,7 +25,7 @@ def __init__(self, *args, **kwargs): class ModSpecialOrderLogic(BaseLogic): def get_modded_special_orders_rules(self): special_orders = {} - if ModNames.juna in self.options.mods: + if self.content.is_enabled(ModNames.juna): special_orders.update({ ModSpecialOrder.junas_monster_mash: self.logic.relationship.has_hearts(ModNPC.juna, 4) & self.registry.special_order_rules[SpecialOrder.a_curious_substance] & @@ -34,7 +34,7 @@ def get_modded_special_orders_rules(self): self.logic.has("Energy Tonic") & self.logic.has(Material.sap) & self.logic.has(Loot.bug_meat) & self.logic.has(Edible.oil_of_garlic) & self.logic.has(Meal.strange_bun) }) - if ModNames.sve in self.options.mods: + if self.content.is_enabled(ModNames.sve): special_orders.update({ ModSpecialOrder.andys_cellar: self.logic.has(Material.stone) & self.logic.has(Material.wood) & self.logic.has(Material.hardwood) & self.logic.has(MetalBar.iron) & self.logic.received(CommunityUpgrade.movie_theater, 1) & @@ -52,12 +52,12 @@ def get_modded_special_orders_rules(self): self.logic.region.can_reach(SVERegion.susans_house) # quest requires you make the fertilizer }) - if ModNames.jasper in self.options.mods: + if self.content.is_enabled(ModNames.jasper): special_orders.update({ ModSpecialOrder.dwarf_scroll: self.logic.has_all(*(Artifact.dwarf_scroll_i, Artifact.dwarf_scroll_ii, Artifact.dwarf_scroll_iii, Artifact.dwarf_scroll_iv,)), ModSpecialOrder.geode_order: self.logic.has_all(*(Geode.geode, Geode.frozen, Geode.magma, Geode.omni,)) & - self.logic.relationship.has_hearts(ModNPC.jasper, 8) + self.logic.relationship.has_hearts(ModNPC.jasper, 8) & self.logic.special_order.can_complete_special_order(SpecialOrder.fragments_of_the_past) }) return special_orders diff --git a/worlds/stardew_valley/mods/logic/sve_logic.py b/worlds/stardew_valley/mods/logic/sve_logic.py index 03f1737c5919..05839941bb3b 100644 --- a/worlds/stardew_valley/mods/logic/sve_logic.py +++ b/worlds/stardew_valley/mods/logic/sve_logic.py @@ -2,7 +2,7 @@ from ...strings.ap_names.mods.mod_items import SVELocation, SVERunes, SVEQuestItem from ...strings.quest_names import Quest, ModQuest from ...strings.region_names import Region, SVERegion -from ...strings.tool_names import Tool, ToolMaterial +from ...strings.tool_names import Tool from ...strings.wallet_item_names import Wallet @@ -45,8 +45,8 @@ def has_bear_knowledge(self): return self.logic.quest.can_complete_quest(Quest.strange_note) def can_buy_bear_recipe(self): - access_rule = (self.logic.quest.can_complete_quest(Quest.strange_note) & self.logic.tool.has_tool(Tool.axe, ToolMaterial.basic) & - self.logic.tool.has_tool(Tool.pickaxe, ToolMaterial.basic)) - forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods, Region.mountain)) + access_rule = (self.logic.quest.can_complete_quest(Quest.strange_note) & self.logic.tool.has_tool(Tool.axe) & + self.logic.tool.has_tool(Tool.pickaxe)) + forage_rule = self.logic.region.can_reach_any(Region.forest, Region.backwoods, Region.mountain) knowledge_rule = self.has_bear_knowledge() return access_rule & forage_rule & knowledge_rule diff --git a/worlds/stardew_valley/mods/mod_data.py b/worlds/stardew_valley/mods/mod_data.py index 54408fb2c571..8ed97e033ebe 100644 --- a/worlds/stardew_valley/mods/mod_data.py +++ b/worlds/stardew_valley/mods/mod_data.py @@ -1,3 +1,6 @@ +from typing import Iterable + + class ModNames: vanilla = None deepwoods = "DeepWoods" @@ -26,3 +29,22 @@ class ModNames: distant_lands = "Distant Lands - Witch Swamp Overhaul" lacey = "Hat Mouse Lacey" boarding_house = "Boarding House and Bus Stop Extension" + + +invalid_mod_combinations = [ + # [ModNames.sve, ModNames.distant_lands] # This is going to become banned after Reptar's SVE update. For now, it's fine. +] + + +def mod_combination_is_valid(mods: Iterable[str]): + for mod_combination in invalid_mod_combinations: + if all([mod in mods for mod in mod_combination]): + return False + return True + + +def get_invalid_mod_combination(mods: Iterable[str]): + for mod_combination in invalid_mod_combinations: + if all([mod in mods for mod in mod_combination]): + return mod_combination + return None diff --git a/worlds/stardew_valley/mods/region_data.py b/worlds/stardew_valley/mods/region_data.py index 5dc4a3dff28b..3ba07858911f 100644 --- a/worlds/stardew_valley/mods/region_data.py +++ b/worlds/stardew_valley/mods/region_data.py @@ -169,7 +169,7 @@ RegionData(SVERegion.willy_bedroom), RegionData(Region.mountain, (SVEEntrance.mountain_to_guild_summit,)), # These entrances are removed from the mountain region when SVE is enabled - RegionData(Region.mountain, (Entrance.mountain_to_adventurer_guild, Entrance.mountain_to_the_mines), flag=MergeFlag.REMOVE_EXITS), + RegionData(Region.outside_adventure_guild, (Entrance.mountain_to_adventurer_guild, Entrance.mountain_to_the_mines), flag=MergeFlag.REMOVE_EXITS), RegionData(SVERegion.guild_summit, (SVEEntrance.guild_to_interior, SVEEntrance.guild_to_mines)), RegionData(Region.railroad, (SVEEntrance.to_susan_house, SVEEntrance.enter_summit, SVEEntrance.railroad_to_grampleton_station)), RegionData(SVERegion.grampleton_station, (SVEEntrance.grampleton_station_to_grampleton_suburbs,)), diff --git a/worlds/stardew_valley/options/__init__.py b/worlds/stardew_valley/options/__init__.py index 12c0d7c647ff..b4fe27a72cc7 100644 --- a/worlds/stardew_valley/options/__init__.py +++ b/worlds/stardew_valley/options/__init__.py @@ -1,6 +1,13 @@ -from .options import StardewValleyOption, Goal, FarmType, StartingMoney, ProfitMargin, BundleRandomization, BundlePrice, EntranceRandomization, \ - SeasonRandomization, Cropsanity, BackpackProgression, ToolProgression, ElevatorProgression, SkillProgression, BuildingProgression, FestivalLocations, \ - ArcadeMachineLocations, SpecialOrderLocations, QuestLocations, Fishsanity, Museumsanity, Monstersanity, Shipsanity, Cooksanity, Chefsanity, Craftsanity, \ - Friendsanity, FriendsanityHeartSize, Booksanity, Walnutsanity, NumberOfMovementBuffs, EnabledFillerBuffs, ExcludeGingerIsland, TrapDifficulty, \ - MultipleDaySleepEnabled, MultipleDaySleepCost, ExperienceMultiplier, FriendshipMultiplier, DebrisMultiplier, QuickStart, Gifting, Mods, BundlePlando, \ - StardewValleyOptions, enabled_mods, disabled_mods, all_mods, TrapDistribution, TrapItems, StardewValleyOptions +from .options import (StardewValleyOption, Goal, FarmType, StartingMoney, ProfitMargin, + BundleRandomization, BundlePrice, BundlePerRoom, EntranceRandomization, StartWithout, + SeasonRandomization, Cropsanity, BackpackProgression, ToolProgression, + ElevatorProgression, SkillProgression, BuildingProgression, FestivalLocations, + ArcadeMachineLocations, SpecialOrderLocations, QuestLocations, Fishsanity, + Museumsanity, Monstersanity, Shipsanity, Cooksanity, Chefsanity, Craftsanity, + Friendsanity, FriendsanityHeartSize, Eatsanity, Booksanity, Walnutsanity, Moviesanity, + Secretsanity, Hatsanity, IncludeEndgameLocations, NumberOfMovementBuffs, EnabledFillerBuffs, ExcludeGingerIsland, + TrapDifficulty, MultipleDaySleepEnabled, MultipleDaySleepCost, ExperienceMultiplier, + FriendshipMultiplier, DebrisMultiplier, BackpackSize, QuickStart, Gifting, Mods, + BundlePlando, BundleWhitelist, BundleBlacklist, AllowedFillerItems, + enabled_mods, enabled_mods_except_invalid_combinations, all_mods_except_invalid_combinations, disabled_mods, all_mods, + TrapDistribution, TrapItems, StardewValleyOptions) diff --git a/worlds/stardew_valley/options/forced_options.py b/worlds/stardew_valley/options/forced_options.py index 7429f3cbfc65..d773cf1ff378 100644 --- a/worlds/stardew_valley/options/forced_options.py +++ b/worlds/stardew_valley/options/forced_options.py @@ -2,17 +2,141 @@ import Options as ap_options from . import options +from .jojapocalypse_options import Jojapocalypse, JojaAreYouSure +from ..mods.mod_data import mod_combination_is_valid, get_invalid_mod_combination +from ..options.settings import StardewSettings +from ..strings.ap_names.ap_option_names import EatsanityOptionName, HatsanityOptionName logger = logging.getLogger(__name__) +def prevent_illegal_mod_combinations(world_options: options.StardewValleyOptions, player: int, player_name: str): + if not mod_combination_is_valid(world_options.mods): + invalid_mod_combination = get_invalid_mod_combination(world_options.mods) + message = f"The combination of mods '{invalid_mod_combination}' cannot be used together. Generation Aborted." + logger.error(message) + raise ap_options.OptionError(message) + + +def force_change_options_if_banned(world_options: options.StardewValleyOptions, settings: StardewSettings, player: int, player_name: str) -> None: + message_template = f"for Player {player} [{player_name}] disallowed from host.yaml." + if not settings.allow_allsanity and world_options.goal == options.Goal.option_allsanity: + message = f"Allsanity Goal {message_template} Generation Aborted." + logger.error(message) + raise ap_options.OptionError(message) + if not settings.allow_perfection and world_options.goal == options.Goal.option_perfection: + message = f"Perfection Goal {message_template} Generation Aborted." + logger.error(message) + raise ap_options.OptionError(message) + if not settings.allow_max_bundles and world_options.bundle_price == options.BundlePrice.option_maximum: + world_options.bundle_price.value = options.BundlePrice.option_very_expensive + message = f"Max Bundles Price {message_template} Replaced with 'Very Expensive'" + logger.warning(message) + if not settings.allow_chaos_er and world_options.entrance_randomization == options.EntranceRandomization.option_chaos: + world_options.entrance_randomization.value = options.EntranceRandomization.option_buildings + message = f"Chaos Entrance Randomization {message_template} Replaced with 'Buildings'" + logger.warning(message) + if not settings.allow_shipsanity_everything and world_options.shipsanity == options.Shipsanity.option_everything: + world_options.shipsanity.value = options.Shipsanity.option_full_shipment_with_fish + message = f"Shipsanity Everything {message_template} Replaced with 'Full Shipment With Fish'" + logger.warning(message) + if not settings.allow_hatsanity_perfection: + removed_one = False + if HatsanityOptionName.near_perfection in world_options.hatsanity.value: + world_options.hatsanity.value.remove(HatsanityOptionName.near_perfection) + removed_one = True + if HatsanityOptionName.post_perfection in world_options.hatsanity.value: + world_options.hatsanity.value.remove(HatsanityOptionName.post_perfection) + removed_one = True + if removed_one and HatsanityOptionName.difficult not in world_options.hatsanity.value: + world_options.hatsanity.value.add(HatsanityOptionName.difficult) + message = f"Hatsanity Near or Post Perfection {message_template} Hatsanity setting reduced." + logger.warning(message) + if not settings.allow_custom_logic: + world_options.custom_logic.value = options.CustomLogic.preset_none + message = f"Custom Logic {message_template} All flags toggled off." + logger.warning(message) + if not settings.allow_jojapocalypse and world_options.jojapocalypse >= options.Jojapocalypse.option_allowed: + world_options.jojapocalypse.value = options.Jojapocalypse.option_disabled + message = f"Jojapocalypse {message_template} Disabled." + logger.warning(message) + # if not settings.allow_sve and ModNames.sve in options.Mods: + # world_options.mods.value.remove(ModNames.sve) + # message = f"Stardew Valley Expanded {message_template} Removed from Mods." + # logger.warning(message) + prevent_illegal_mod_combinations(world_options, player, player_name) + + def force_change_options_if_incompatible(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None: + force_no_jojapocalypse_without_being_sure(world_options, player, player_name) + force_eatsanity_no_enzymes_if_no_other_eatsanity(world_options, player, player_name) + force_hatsanity_when_goal_is_mad_hatter(world_options, player, player_name) + force_eatsanity_when_goal_is_ultimate_foodie(world_options, player, player_name) force_ginger_island_inclusion_when_goal_is_ginger_island_related(world_options, player, player_name) force_walnutsanity_deactivation_when_ginger_island_is_excluded(world_options, player, player_name) force_qi_special_orders_deactivation_when_ginger_island_is_excluded(world_options, player, player_name) force_accessibility_to_full_when_goal_requires_all_locations(player, player_name, world_options) +def force_no_jojapocalypse_without_being_sure(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None: + has_jojapocalypse = world_options.jojapocalypse.value >= Jojapocalypse.option_allowed + is_sure = world_options.joja_are_you_sure == JojaAreYouSure.option_true + + if has_jojapocalypse and not is_sure: + world_options.jojapocalypse.value = Jojapocalypse.option_disabled + logger.warning(f"Jojapocalypse requires affirmative consent to be enabled. " + f"Jojapocalypse option forced to '{world_options.jojapocalypse}' for player {player} ({player_name})") + + start_price = world_options.joja_start_price.value + end_price = world_options.joja_end_price.value + if end_price <= start_price: + end_price = start_price + 1 + world_options.joja_end_price.value = end_price + logger.warning(f"Jojapocalypse End price must be higher than the start price. " + f"Jojapocalypse End Price forced to '{world_options.joja_end_price}' for player {player} ({player_name})") + + +def force_eatsanity_no_enzymes_if_no_other_eatsanity(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None: + has_eatsanity_enzymes = EatsanityOptionName.lock_effects in world_options.eatsanity + valid_eatsanity_locations_for_enzymes = [EatsanityOptionName.crops, EatsanityOptionName.fish, EatsanityOptionName.cooking] + has_enough_eatsanity_locations_for_enzymes = any([option in world_options.eatsanity for option in valid_eatsanity_locations_for_enzymes]) + + if has_eatsanity_enzymes and not has_enough_eatsanity_locations_for_enzymes: + world_options.eatsanity.value.remove(EatsanityOptionName.lock_effects) + logger.warning(f"Eatsanity 'Lock Effects' requires more eatsanity locations to be active. " + f"Eatsanity option forced to '{world_options.eatsanity}' for player {player} ({player_name})") + + +def force_hatsanity_when_goal_is_mad_hatter(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None: + if world_options.exclude_ginger_island.value and HatsanityOptionName.post_perfection in world_options.hatsanity: + world_options.hatsanity.value.remove(HatsanityOptionName.post_perfection) + logger.warning(f"Hatsanity '{HatsanityOptionName.post_perfection}' requires ginger island. " + f"'{HatsanityOptionName.post_perfection}' force-removed from Hatsanity") + + goal_is_mad_hatter = world_options.goal == options.Goal.option_mad_hatter + hatsanity_is_disabled = world_options.hatsanity == options.Hatsanity.preset_none + + if goal_is_mad_hatter and hatsanity_is_disabled: + world_options.hatsanity.value = options.Hatsanity.preset_simple + goal_name = world_options.goal.current_option_name + logger.warning(f"Goal '{goal_name}' requires Hatsanity. " + f"Hatsanity option forced to 'Easy+Tailoring' for player {player} ({player_name})") + + +def force_eatsanity_when_goal_is_ultimate_foodie(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None: + goal_is_foodie = world_options.goal == options.Goal.option_ultimate_foodie + eatsanity_options_that_dont_add_locations = [EatsanityOptionName.lock_effects, EatsanityOptionName.poisonous] + eatsanity_options_that_add_locations = options.Eatsanity.preset_all.difference(eatsanity_options_that_dont_add_locations) + eatsanity_relevant_values = world_options.eatsanity.value.difference(eatsanity_options_that_dont_add_locations) + eatsanity_is_disabled = len(eatsanity_relevant_values) <= 0 + + if goal_is_foodie and eatsanity_is_disabled: + world_options.eatsanity.value = world_options.eatsanity.value.union(eatsanity_options_that_add_locations) + goal_name = world_options.goal.current_option_name + logger.warning(f"Goal '{goal_name}' requires Eatsanity. " + f"Eatsanity option forced to 'All' for player {player} ({player_name})") + + def force_ginger_island_inclusion_when_goal_is_ginger_island_related(world_options: options.StardewValleyOptions, player: int, player_name: str) -> None: goal_is_walnut_hunter = world_options.goal == options.Goal.option_greatest_walnut_hunter goal_is_perfection = world_options.goal == options.Goal.option_perfection diff --git a/worlds/stardew_valley/options/jojapocalypse_options.py b/worlds/stardew_valley/options/jojapocalypse_options.py new file mode 100644 index 000000000000..b6ec8edba558 --- /dev/null +++ b/worlds/stardew_valley/options/jojapocalypse_options.py @@ -0,0 +1,73 @@ +from Options import Choice, Range, Toggle + + +class Jojapocalypse(Choice): + """Joja Co opens a new Archipelago branch, selling you any and all location checks you might want or need, in exchange for money. + But are you ready to pay the price... + Disabled: Joja does not sell location checks + Allowed: Joja sells location checks, that you can buy if you want + Forced: The only way to obtain location checks is through Joja + """ + internal_name = "jojapocalypse" + display_name = "Jojapocalypse" + default = 0 + option_disabled = 0 + option_allowed = 1 + option_forced = 2 + + +class JojaStartPrice(Range): + """The price of Jojapocalypse items at the very beginning of the game. This price will increase with each Jojapocalypse purchase + """ + internal_name = "joja_start_price" + display_name = "Jojapocalypse Start Price" + default = 100 + range_start = 1 + range_end = 10000 + + +class JojaEndPrice(Range): + """The price of the very last Jojapocalypse item you will buy. An individual location check will never go above this. + Consider your number of checks before choosing a price, as the total amount of money you spend will highly scale with it. + """ + internal_name = "joja_end_price" + display_name = "Jojapocalypse End Price" + default = 10000 + range_start = 0 + range_end = 100_000 + + +class JojaPricingPattern(Choice): + """Chooses the pricing strategy of Jojapocalypse sold location checks. Prices will always increase, but you can pick the increase pattern. + """ + internal_name = "joja_pricing_pattern" + display_name = "Jojapocalypse Pricing Pattern" + default = 1 + option_linear = 0 + option_exponential = 1 + + +class JojaPurchasesForMembership(Range): + """After buying this number of location checks for Joja, you will earn your very own Joja Membership! + The world will change accordingly. + If you are on "Allowed", receiving your first Movie Theater before earning your membership will close down Joja permanently. + If you obtain your membership, the movie theater will instead replace the Community Center, and some location checks will become only obtainable through Joja. + If you are on "Forced", You will start as a Joja Member. + """ + internal_name = "joja_purchases_for_membership" + display_name = "Purchases For Joja Membership" + default = 10 + range_start = 1 + range_end = 100 + + +class JojaAreYouSure(Toggle): + """Jojapocalypse will be an extremely unfulfilling experience. + The main driving directive behind its Design is to make the player feel bad and guilty about picking Joja. + Most of the things you buy will come with heavy, painful consequences, and they are explicitly done with the intent of being unfun and annoying. + This game mode is not supposed to be fun. You will have a bad time. + Are you really sure you want to do this? + """ + internal_name = "joja_are_you_sure" + display_name = "Are you sure?" + default = Toggle.option_false diff --git a/worlds/stardew_valley/options/option_groups.py b/worlds/stardew_valley/options/option_groups.py index 4ae1fc3c4d7e..ddb54f7c5de8 100644 --- a/worlds/stardew_valley/options/option_groups.py +++ b/worlds/stardew_valley/options/option_groups.py @@ -15,6 +15,7 @@ options.FarmType, options.BundleRandomization, options.BundlePrice, + options.BundlePerRoom, options.EntranceRandomization, options.ExcludeGingerIsland, ]), @@ -26,6 +27,7 @@ options.ElevatorProgression, options.SkillProgression, options.BuildingProgression, + options.StartWithout, ]), OptionGroup("Extra Shuffling", [ options.FestivalLocations, @@ -35,23 +37,32 @@ options.Fishsanity, options.Museumsanity, options.Friendsanity, - options.FriendsanityHeartSize, options.Monstersanity, - options.Shipsanity, - options.Cooksanity, options.Chefsanity, - options.Craftsanity, options.Booksanity, options.Walnutsanity, + options.Moviesanity, + ]), + OptionGroup("Extreme Options", [ + options.Shipsanity, + options.Cooksanity, + options.Craftsanity, + options.Eatsanity, + options.Secretsanity, + options.Hatsanity, + options.IncludeEndgameLocations, ]), - OptionGroup("Multipliers and Buffs", [ + OptionGroup("Multipliers, Buffs and extra customization", [ options.StartingMoney, options.ProfitMargin, options.ExperienceMultiplier, options.FriendshipMultiplier, + options.FriendsanityHeartSize, options.DebrisMultiplier, + options.BackpackSize, options.NumberOfMovementBuffs, options.EnabledFillerBuffs, + options.AllowedFillerItems, options.TrapDifficulty, options.TrapDistribution, options.MultipleDaySleepEnabled, @@ -62,8 +73,18 @@ options.Gifting, ap_options.DeathLink, options.Mods, - options.BundlePlando, + options.BundleWhitelist, + options.BundleBlacklist, + options.CustomLogic, ap_options.ProgressionBalancing, ap_options.Accessibility, ]), + OptionGroup("Jojapocalypse", [ + options.Jojapocalypse, + options.JojaStartPrice, + options.JojaEndPrice, + options.JojaPricingPattern, + options.JojaPurchasesForMembership, + options.JojaAreYouSure, + ]), ] diff --git a/worlds/stardew_valley/options/options.py b/worlds/stardew_valley/options/options.py index f81cdaac813b..76d8e4cb954e 100644 --- a/worlds/stardew_valley/options/options.py +++ b/worlds/stardew_valley/options/options.py @@ -3,11 +3,14 @@ from dataclasses import dataclass from typing import Protocol, ClassVar -from Options import Range, NamedRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink, OptionList, Visibility, Removed, OptionCounter -from ..items import items_by_group, Group -from ..mods.mod_data import ModNames -from ..strings.ap_names.ap_option_names import BuffOptionName, WalnutsanityOptionName -from ..strings.bundle_names import all_cc_bundle_names +from Options import Range, NamedRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink, OptionList, \ + Visibility, Removed, OptionCounter +from .jojapocalypse_options import Jojapocalypse, JojaStartPrice, JojaEndPrice, JojaPricingPattern, JojaPurchasesForMembership, JojaAreYouSure +from ..mods.mod_data import ModNames, invalid_mod_combinations +from ..strings.ap_names.ap_option_names import BuffOptionName, WalnutsanityOptionName, SecretsanityOptionName, EatsanityOptionName, ChefsanityOptionName, \ + StartWithoutOptionName, HatsanityOptionName, AllowedFillerOptionName, CustomLogicOptionName +from ..strings.bundle_names import all_cc_bundle_names, MemeBundleName +from ..strings.trap_names import all_traps class StardewValleyOption(Protocol): @@ -30,6 +33,8 @@ class Goal(Choice): Craft Master: Craft every item Legend: Earn 10 000 000g Mystery of the Stardrops: Find every stardrop + Mad Hatter: Complete all your hatsanity locations. If hatsanity is disabled, will enable it on "Easy+Tailoring" + Ultimate Foodie: Eat all items in the game. Adapts to Eatsanity Allsanity: Complete every check in your slot Perfection: Attain Perfection """ @@ -50,6 +55,8 @@ class Goal(Choice): option_craft_master = 11 option_legend = 12 option_mystery_of_the_stardrops = 13 + option_mad_hatter = 20 + option_ultimate_foodie = 21 # option_junimo_kart = # option_prairie_king = # option_fector_challenge = @@ -122,18 +129,20 @@ class ProfitMargin(NamedRange): class BundleRandomization(Choice): """What items are needed for the community center bundles? - Vanilla: Standard bundles from the vanilla game - Thematic: Every bundle will require random items compatible with their original theme - Remixed: Picks bundles at random from thematic, vanilla remixed and new custom ones + Vanilla: Standard bundles from the vanilla game. + Thematic: Every bundle will require random items compatible with their original theme. + Remixed: Picks bundles at random from thematic, vanilla remixed and new custom ones. Remixed Anywhere: Remixed, but bundles are not locked to specific rooms. - Shuffled: Every bundle will require random items and follow no particular structure""" + Shuffled: Every bundle will require random items and follow no particular structure. + Meme: A set of entirely custom bundles are generated purely based on jokes, references, and trolling. Funny but not balanced at all. Not for the faint of heart.""" internal_name = "bundle_randomization" display_name = "Bundle Randomization" option_vanilla = 0 option_thematic = 1 option_remixed = 3 - option_remixed_anywhere = 4 + option_remixed_anywhere = 5 option_shuffled = 6 + option_meme = 10 default = option_remixed @@ -158,6 +167,30 @@ class BundlePrice(Choice): option_maximum = 8 +class BundlePerRoom(Choice): + """How many bundles are in each room of the community center? + Rooms that already have the max cannot increase further + 2 Fewer: Every room will have 2 fewer bundles + 1 Fewer: Every room will have 1 fewer bundle + Normal: Every room will have its usual number of bundles + 1 Extra: Every room will have 1 extra bundle + 2 Extra: Every room will have 2 extra bundles + 3 Extra: Every room will have 3 extra bundles + 4 Extra: Every room will have 4 extra bundles""" + internal_name = "bundle_per_room" + display_name = "Bundle Per Room" + default = 0 + # option_minimum = -8 # I don't think users need this, keeping my options open + option_two_fewer = -2 + option_one_fewer = -1 + option_normal = 0 + option_one_extra = 1 + option_two_extra = 2 + option_three_extra = 3 + option_four_extra = 4 + # option_maximum = 8 # I don't think users need this, keeping my options open + + class EntranceRandomization(Choice): """Should area entrances be randomized? Disabled: No entrance randomization is done @@ -188,6 +221,36 @@ class EntranceRandomization(Choice): # option_chaos_one_way = 8 +class StartWithout(OptionSet): + """ Items that, in vanilla, you generally start with (or get very quickly), but in Archipelago, you would rather start without them. + If the relevant item is not randomized, this option will do nothing. + Tools: Start without an Axe, Pickaxe, Hoe, Watering can and Scythe + Backpack: Start with 4 backpack slots, instead of 12, if your backpack size allows it + Landslide: Start without the landslide that leads to the mines + Community Center: Start without the key to the Community Center, and the Forest Magic to allow reading the bundles + Buildings: Start without the Shipping Bin and Pet Bowl + """ + internal_name = "start_without" + display_name = "Start Without" + valid_keys = frozenset({ + StartWithoutOptionName.tools, StartWithoutOptionName.backpack, + StartWithoutOptionName.landslide, StartWithoutOptionName.community_center, + StartWithoutOptionName.buildings, + }) + preset_none = frozenset() + preset_easy = frozenset({StartWithoutOptionName.landslide, StartWithoutOptionName.community_center}) + preset_all = valid_keys + default = preset_none + + def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, OptionSet): + return set(self.value) == other.value + if isinstance(other, OptionList): + return set(self.value) == set(other.value) + else: + return typing.cast(bool, self.value == other) + + class SeasonRandomization(Choice): """Should seasons be randomized? Disabled: Start in Spring with all seasons unlocked. @@ -232,6 +295,26 @@ class BackpackProgression(Choice): option_early_progressive = 2 +class BackpackSize(Choice): + """Customize the granularity of the backpack upgrades + This works with vanilla and progressive backpack. + Default size is 12, which means you start with one backpack (12 slots), and get 2 more upgrades up to 36 slots. + If you pick 4, then you start with 3 backpacks (12 slots), and get 6 more upgrades up to 36 slots. + If you picked "Start Without Backpack", you will only be provided start upgrades up to 4 slots, instead of up to 12""" + internal_name = "backpack_size" + option_1 = 1 + option_2 = 2 + option_3 = 3 + option_4 = 4 + option_6 = 6 + option_12 = 12 + default = option_12 + display_name = "Backpack Size" + + def count_per_tier(self) -> int: + return 12 // self.value + + class ToolProgression(Choice): """Shuffle the tool upgrades? Vanilla: Clint will upgrade your tools with metal bars. @@ -241,12 +324,12 @@ class ToolProgression(Choice): internal_name = "tool_progression" display_name = "Tool Progression" default = 1 - option_vanilla = 0b000 # 0 - option_progressive = 0b001 # 1 - option_vanilla_cheap = 0b010 # 2 - option_vanilla_very_cheap = 0b100 # 4 - option_progressive_cheap = 0b011 # 3 - option_progressive_very_cheap = 0b101 # 5 + option_vanilla = 0b0000 # 0 + option_progressive = 0b0001 # 1 + option_vanilla_cheap = 0b0010 # 2 + option_vanilla_very_cheap = 0b0100 # 4 + option_progressive_cheap = 0b0011 # 3 + option_progressive_very_cheap = 0b0101 # 5 @property def is_vanilla(self): @@ -278,7 +361,7 @@ class SkillProgression(Choice): With Masteries: Skill levels are unlocked randomly, and earning xp sends checks. Masteries are included""" internal_name = "skill_progression" display_name = "Skill Progression" - default = 2 + default = 1 option_vanilla = 0 option_progressive = 1 option_progressive_with_masteries = 2 @@ -461,7 +544,7 @@ class Shipsanity(Choice): """Locations for shipping items? None: There are no checks for shipping items Crops: Every crop and forageable being shipped is a check - Fish: Every fish being shipped is a check except legendaries + Fish: Every fish being shipped is a check Full Shipment: Every item in the Collections page is a check Full Shipment With Fish: Every item in the Collections page and every fish is a check Everything: Every item in the game that can be shipped is a check @@ -471,15 +554,12 @@ class Shipsanity(Choice): default = 0 option_none = 0 option_crops = 1 - # option_quality_crops = 2 option_fish = 3 - # option_quality_fish = 4 + option_crops_and_fish = 4 option_full_shipment = 5 - # option_quality_full_shipment = 6 option_full_shipment_with_fish = 7 - # option_quality_full_shipment_with_fish = 8 option_everything = 9 - # option_quality_everything = 10 + alias_all = option_everything class Cooksanity(Choice): @@ -496,38 +576,31 @@ class Cooksanity(Choice): option_all = 2 -class Chefsanity(NamedRange): - """Locations for learning cooking recipes? - Vanilla: All cooking recipes are learned normally - Queen of Sauce: Every Queen of Sauce episode is a check, all Queen of Sauce recipes are items +class Chefsanity(OptionSet): + """Locations for learning cooking recipes? Omitted categories are learned normally + Queen of Sauce: Every Queen of Sauce episode is a check Purchases: Every purchasable recipe is a check Friendship: Recipes obtained from friendship are checks Skills: Recipes obtained from skills are checks - All: Learning every cooking recipe is a check """ internal_name = "chefsanity" display_name = "Chefsanity" - default = 0 - range_start = 0 - range_end = 15 - option_none = 0b0000 # 0 - option_queen_of_sauce = 0b0001 # 1 - option_purchases = 0b0010 # 2 - option_qos_and_purchases = 0b0011 # 3 - option_skills = 0b0100 # 4 - option_friendship = 0b1000 # 8 - option_all = 0b1111 # 15 + valid_keys = frozenset({ + ChefsanityOptionName.queen_of_sauce, ChefsanityOptionName.purchases, + ChefsanityOptionName.skills, ChefsanityOptionName.friendship, + }) + preset_none = frozenset() + preset_all = valid_keys + default = preset_none - special_range_names = { - "none": 0b0000, # 0 - "queen_of_sauce": 0b0001, # 1 - "purchases": 0b0010, # 2 - "qos_and_purchases": 0b0011, # 3 - "skills": 0b0100, # 4 - "friendship": 0b1000, # 8 - "all": 0b1111, # 15 - } + def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, OptionSet): + return set(self.value) == other.value + if isinstance(other, OptionList): + return set(self.value) == set(other.value) + else: + return typing.cast(bool, self.value == other) class Craftsanity(Choice): @@ -573,6 +646,36 @@ class FriendsanityHeartSize(Range): # step = 1 +class Eatsanity(OptionSet): + """Locations for eating various items? + Cooking: Includes all cooked and crafted edible items + Crops: Includes all crops and forageable edible items + Fish: Includes all fish and fished edible items + Artisan: Includes all edible items produced by machines and animals + Shop: Includes all edible items purchased primarily in shops + Poisonous: Includes items that cause negative effects when consumed + Lock Effects: Whether each positive effect from edible items is unlocked through an archipelago "Enzyme" item. Requires some eatsanity locations to be enabled. + """ + internal_name = "eatsanity" + display_name = "Eatsanity" + valid_keys = frozenset({ + EatsanityOptionName.cooking, EatsanityOptionName.crops, EatsanityOptionName.fish, + EatsanityOptionName.artisan, EatsanityOptionName.shop, + EatsanityOptionName.poisonous, EatsanityOptionName.lock_effects, + }) + preset_none = frozenset() + preset_all = valid_keys + default = preset_none + + def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, OptionSet): + return set(self.value) == other.value + if isinstance(other, OptionList): + return set(self.value) == set(other.value) + else: + return typing.cast(bool, self.value == other) + + class Booksanity(Choice): """Shuffle Books? None: All books behave like vanilla @@ -615,6 +718,100 @@ def __eq__(self, other: typing.Any) -> bool: return typing.cast(bool, self.value == other) +class Moviesanity(Choice): + """Add checks for watching movies? + None: No movie checks + One: There is a check for watching a movie, regardless of which + All Movies: Watching all individual movies are checks + All Movies Loved: Watching all individual movies are checks, but you have to invite someone who loves it + All Movies With Loved Snacks: Watching movies only counts if you invite someone who loves it and buy them a loved snack + All Movies And All Snacks: Watch all movies with someone who loves them, and purchase all the snacks once + All Movies And All Loved Snacks: Watch all movies with someone who loves them, and purchase all the snacks for someone who loves them + """ + internal_name = "moviesanity" + display_name = "Moviesanity" + default = 1 + option_none = 0 + option_one = 1 + option_all_movies = 2 + option_all_movies_loved = 3 + option_all_movies_with_loved_snack = 4 + option_all_movies_and_all_snacks = 5 + option_all_movies_and_all_loved_snacks = 6 + + +class Secretsanity(OptionSet): + """Add checks for the various secrets and easter eggs present in Stardew Valley. Some of them can be very obscure. If you enable this setting, you should expect to need the wiki a lot. + Easy: Secrets that can be obtained quickly and easily, if you know what to do + Difficult: Includes secrets that require a lot of grinding or a lot of luck. Not for the faint of heart. Enabling this will also modify some secrets from the other categories to require their harder variation, if there is one. + Fishing: Various special items and furniture that can be fished up in specific places + Secret Notes: Complete tasks described in the various secret notes, when applicable + """ + internal_name = "secretsanity" + display_name = "Secretsanity" + valid_keys = frozenset({ + SecretsanityOptionName.easy, SecretsanityOptionName.difficult, SecretsanityOptionName.fishing, SecretsanityOptionName.secret_notes, + }) + preset_none = frozenset() + preset_simple = frozenset({ + SecretsanityOptionName.easy, SecretsanityOptionName.fishing, SecretsanityOptionName.secret_notes, + }) + preset_all = valid_keys + default = preset_none + + def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, OptionSet): + return set(self.value) == other.value + if isinstance(other, OptionList): + return set(self.value) == set(other.value) + else: + return typing.cast(bool, self.value == other) + + +class Hatsanity(OptionSet): + """Add checks for wearing hats? + Tailoring: Locations for wearing the hats created through tailoring + Easy: Locations for wearing the easily obtainable hats + Medium: Locations for wearing the hats that are obtainable through a task that requires a bit of effort + Difficult: Locations for wearing hats that are difficult to obtain + RNG: Locations for wearing hats that are extremely rng-dependent to obtain. Generally an unpleasant grind. + Near Perfection: Locations for wearing hats that are late game and generally obtained by doing the equivalent of a perfection task + Post Perfection: Locations for wearing all hats, including the hyper-late game ones that require more work than perfection itself + """ + internal_name = "hatsanity" + display_name = "Hatsanity" + + valid_keys = frozenset({ + HatsanityOptionName.tailoring, HatsanityOptionName.easy, HatsanityOptionName.medium, HatsanityOptionName.difficult, + HatsanityOptionName.rng, HatsanityOptionName.near_perfection, HatsanityOptionName.post_perfection + }) + preset_none = frozenset() + preset_simple = frozenset({ + HatsanityOptionName.tailoring, HatsanityOptionName.easy, + }) + preset_difficult = frozenset({ + HatsanityOptionName.tailoring, HatsanityOptionName.easy, HatsanityOptionName.medium, HatsanityOptionName.difficult, + }) + preset_all = valid_keys + default = preset_none + + def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, OptionSet): + return set(self.value) == other.value + if isinstance(other, OptionList): + return set(self.value) == set(other.value) + else: + return typing.cast(bool, self.value == other) + + +class IncludeEndgameLocations(Toggle): + """Whether to include, as locations, several very expensive things that are usually purchased during the end-game in vanilla. + Examples: Obelisks, Community Upgrades, Catalogues, etc""" + internal_name = "include_endgame_locations" + display_name = "Include Endgame Locations" + default = Toggle.option_false + + class NumberOfMovementBuffs(Range): """Number of movement speed buffs to the player that exist as items in the pool. Each movement speed buff is a +25% multiplier that stacks additively""" @@ -690,6 +887,9 @@ class TrapDifficulty(Choice): option_hell = 4 option_nightmare = 5 + def include_traps(self) -> bool: + return self.value > 0 + trap_default_weight = 100 @@ -707,18 +907,56 @@ class TrapDistribution(OptionCounter): visibility = Visibility.all ^ Visibility.simple_ui min = 0 max = 1000 - valid_keys = frozenset({ - trap_data.name - for trap_data in items_by_group[Group.TRAP] - if Group.DEPRECATED not in trap_data.groups - }) + valid_keys = frozenset(all_traps) default = { - trap_data.name: trap_default_weight - for trap_data in items_by_group[Group.TRAP] - if Group.DEPRECATED not in trap_data.groups + trap: trap_default_weight + for trap in all_traps } +class CustomLogic(OptionSet): + """Enable various customizations to the logic of the generator. + Some flags are inherently incompatible with each other, the harder flag takes priority. + Some of these toggles can, if the player is not careful, force them to reset days. + Chair Skips: Chair skips are considered in-logic + Easy Fishing: +2 Required fishing levels + Hard Fishing: -2 Required fishing levels + Extreme Fishing: -4 Required fishing levels + Easy Mining: +1 required pickaxes and +2 required mining levels + Hard Mining: -1 required pickaxes and -2 required mining levels + Extreme Mining: -2 required pickaxes and -4 required mining levels + Easy Combat: +1 required weapon and +2 required combat levels + Hard Combat: -1 required weapon and -2 required combat levels + Extreme Combat: -2 required weapon and -4 required combat levels + Deep Mining: x2 Mine depth expectations + Very Deep Mining: x4 Mine depth expectations + Ignore Birthdays: Villager birthdays are not considered in logic + Easy Money: x0.25 to earnings expectations + Hard Money: x2 to earnings expectations + Extreme Money: x8 to earnings expectations + Nightmare Money: x20 to earnings expectations + Bomb Hoeing: Hoeing ground is in logic without a hoe + Rain Watering: Watering crops is in logic without a watering can + Critical Free Samples: Free samples of items are considered in logic without a renewable source + """ + internal_name = "custom_logic" + display_name = "Custom Logic" + valid_keys = frozenset({ + CustomLogicOptionName.chair_skips, + CustomLogicOptionName.easy_fishing, CustomLogicOptionName.hard_fishing, CustomLogicOptionName.extreme_fishing, + CustomLogicOptionName.easy_mining, CustomLogicOptionName.hard_mining, CustomLogicOptionName.extreme_mining, + CustomLogicOptionName.easy_combat, CustomLogicOptionName.hard_combat, CustomLogicOptionName.extreme_combat, + CustomLogicOptionName.deep_mining, CustomLogicOptionName.very_deep_mining, + CustomLogicOptionName.ignore_birthdays, + CustomLogicOptionName.easy_money, CustomLogicOptionName.hard_money, CustomLogicOptionName.extreme_money, CustomLogicOptionName.nightmare_money, + CustomLogicOptionName.bomb_hoeing, CustomLogicOptionName.rain_watering, + CustomLogicOptionName.critical_free_samples, + }) + preset_none = frozenset() + preset_all = valid_keys + default = frozenset(preset_none) + + class MultipleDaySleepEnabled(Toggle): """Enable the ability to sleep automatically for multiple days straight?""" internal_name = "multiple_day_sleep_enabled" @@ -828,13 +1066,24 @@ class Gifting(Toggle): # These mods have been disabled because either they are not updated for the current supported version of Stardew Valley, # or we didn't find the time to validate that they work or fix compatibility issues if they do. # Once a mod is validated to be functional, it can simply be removed from this list +# SVE specifically is disabled because their main version is significantly ahead of ours, with breaking changes, and nobody is maintaining our integration. disabled_mods = {ModNames.deepwoods, ModNames.magic, ModNames.cooking_skill, ModNames.yoba, ModNames.eugene, ModNames.wellwick, ModNames.shiko, ModNames.delores, ModNames.riley, - ModNames.boarding_house} + ModNames.boarding_house, ModNames.sve} enabled_mods = all_mods.difference(disabled_mods) +all_mods_except_invalid_combinations = set(all_mods) +for mod_combination in invalid_mod_combinations: + priority_mod = mod_combination[0] + if priority_mod not in all_mods_except_invalid_combinations: + continue + for mod in mod_combination: + if mod == priority_mod: + continue + all_mods_except_invalid_combinations.remove(mod) +enabled_mods_except_invalid_combinations = all_mods_except_invalid_combinations.difference(disabled_mods) class Mods(OptionSet): @@ -849,14 +1098,88 @@ class Mods(OptionSet): valid_keys = all_mods -class BundlePlando(OptionSet): - """If using Remixed bundles, this guarantees some of them will show up in your community center. - If more bundles are specified than what fits in their parent room, that room will randomly pick from only the plando ones""" +class BundlePlando(Removed): + """Deprecated setting, replaced by BundleWhitelist and BundleBlacklist + """ internal_name = "bundle_plando" display_name = "Bundle Plando" + default = "" + visibility = Visibility.none + + def __init__(self, value: str): + if value: + raise Exception("Option bunde_plando was replaced by bundle_whitelist and bundle_blacklist, please update your options file") + super().__init__(value) + + +class BundleWhitelist(OptionSet): + """If using Remixed or Meme bundles, this guarantees some of them will show up in your community center. + If more bundles are specified than what fits in their parent room, that room will randomly pick from only the whitelist ones""" + internal_name = "bundle_whitelist" + display_name = "Bundle Whitelist" visibility = Visibility.template | Visibility.spoiler valid_keys = set(all_cc_bundle_names) + def prioritizes(self, bundle_name): + if self.in_plando(bundle_name): + return True + if bundle_name == MemeBundleName.scam: + return self.in_plando("Investment Bundle") + return False + + def in_plando(self, bundle_name) -> bool: + return bundle_name in self.value + + +class BundleBlacklist(OptionSet): + """If using Remixed or Meme bundles, this guarantees some of them will not show up in your community center. + If too many bundles are blacklisted for a given room, that room will pick all the non-blacklist bundles, then random blacklisted ones.""" + internal_name = "bundle_blacklist" + display_name = "Bundle Blacklist" + visibility = Visibility.template | Visibility.spoiler + valid_keys = set(all_cc_bundle_names) + + def allows(self, bundle_name): + if self.in_plando(bundle_name): + return False + if bundle_name == MemeBundleName.scam: + return not self.in_plando("Investment Bundle") + return True + + def in_plando(self, bundle_name) -> bool: + return bundle_name in self.value + + +class AllowedFillerItems(OptionSet): + """Types of filler items that can be generated for this slot. All allowed fillers have the same odds, and duplicates can roll + Farming Items: Items to help with farming. Fertilizers, sprinklers... + Fishing Items: Items to help with fishing. Baits, Bobbers... + Fruit Trees: Extra Fruit Trees + Food: Food that doesn't give buffs + Buff Food: Food that gives buffs + Consumables: Other consumables. Warp Totems, Staircases... + Machines: Various machines. Enables a lot of OoL + Storage: Storage items. Chests, Big Chests... + Quality Of Life: Items that are nice to have. Key to the town, Horse Flute... + Materials: Construction Materials. Wood, Stone... + Money: Packs of money + Currencies: Packs of other currencies. Walnuts, Qi Gems, Calico eggs... + Rings: Various rings + Hats: Various hats. + Decorations: Various decorations and furniture + """ + internal_name = "allowed_filler_items" + display_name = "Allowed Filler Items" + visibility = Visibility.template | Visibility.spoiler + valid_keys = frozenset({AllowedFillerOptionName.farming, AllowedFillerOptionName.fishing, AllowedFillerOptionName.fruit_trees, + AllowedFillerOptionName.food, AllowedFillerOptionName.buff_food, AllowedFillerOptionName.consumables, + AllowedFillerOptionName.machines, AllowedFillerOptionName.storage, AllowedFillerOptionName.quality_of_life, + AllowedFillerOptionName.materials, AllowedFillerOptionName.currencies, AllowedFillerOptionName.money, + AllowedFillerOptionName.hats, AllowedFillerOptionName.decorations, AllowedFillerOptionName.rings}) + preset_none = frozenset() + preset_all = valid_keys + default = preset_all + @dataclass class StardewValleyOptions(PerGameCommonOptions): @@ -864,10 +1187,13 @@ class StardewValleyOptions(PerGameCommonOptions): farm_type: FarmType bundle_randomization: BundleRandomization bundle_price: BundlePrice + bundle_per_room: BundlePerRoom entrance_randomization: EntranceRandomization + start_without: StartWithout season_randomization: SeasonRandomization cropsanity: Cropsanity backpack_progression: BackpackProgression + backpack_size: BackpackSize tool_progression: ToolProgression skill_progression: SkillProgression building_progression: BuildingProgression @@ -885,8 +1211,13 @@ class StardewValleyOptions(PerGameCommonOptions): craftsanity: Craftsanity friendsanity: Friendsanity friendsanity_heart_size: FriendsanityHeartSize + eatsanity: Eatsanity booksanity: Booksanity walnutsanity: Walnutsanity + moviesanity: Moviesanity + secretsanity: Secretsanity + hatsanity: Hatsanity + include_endgame_locations: IncludeEndgameLocations exclude_ginger_island: ExcludeGingerIsland quick_start: QuickStart starting_money: StartingMoney @@ -898,12 +1229,24 @@ class StardewValleyOptions(PerGameCommonOptions): enabled_filler_buffs: EnabledFillerBuffs trap_difficulty: TrapDifficulty trap_distribution: TrapDistribution + custom_logic: CustomLogic multiple_day_sleep_enabled: MultipleDaySleepEnabled multiple_day_sleep_cost: MultipleDaySleepCost gifting: Gifting mods: Mods - bundle_plando: BundlePlando + bundle_whitelist: BundleWhitelist + bundle_blacklist: BundleBlacklist + allowed_filler_items: AllowedFillerItems death_link: DeathLink + # Jojapocalypse + jojapocalypse: Jojapocalypse + joja_start_price: JojaStartPrice + joja_end_price: JojaEndPrice + joja_pricing_pattern: JojaPricingPattern + joja_purchases_for_membership: JojaPurchasesForMembership + joja_are_you_sure: JojaAreYouSure + # removed: - trap_items: TrapItems \ No newline at end of file + trap_items: TrapItems + bundle_plando: BundlePlando diff --git a/worlds/stardew_valley/options/presets.py b/worlds/stardew_valley/options/presets.py index a711fe08ff86..31bf2b5a0448 100644 --- a/worlds/stardew_valley/options/presets.py +++ b/worlds/stardew_valley/options/presets.py @@ -2,359 +2,475 @@ import Options as ap_options from . import options -from ..strings.ap_names.ap_option_names import WalnutsanityOptionName - # @formatter:off +from .jojapocalypse_options import Jojapocalypse, JojaStartPrice, JojaEndPrice, JojaPricingPattern, JojaPurchasesForMembership, JojaAreYouSure +from ..strings.ap_names.ap_option_names import EatsanityOptionName, ChefsanityOptionName, HatsanityOptionName + all_random_settings = { - "progression_balancing": "random", - "accessibility": "random", - options.Goal.internal_name: "random", - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: "random", - options.ProfitMargin.internal_name: "random", - options.BundleRandomization.internal_name: "random", - options.BundlePrice.internal_name: "random", - options.EntranceRandomization.internal_name: "random", - options.SeasonRandomization.internal_name: "random", - options.Cropsanity.internal_name: "random", - options.BackpackProgression.internal_name: "random", - options.ToolProgression.internal_name: "random", - options.ElevatorProgression.internal_name: "random", - options.SkillProgression.internal_name: "random", - options.BuildingProgression.internal_name: "random", - options.FestivalLocations.internal_name: "random", - options.ArcadeMachineLocations.internal_name: "random", - options.SpecialOrderLocations.internal_name: "random", - options.QuestLocations.internal_name: "random", - options.Fishsanity.internal_name: "random", - options.Museumsanity.internal_name: "random", - options.Monstersanity.internal_name: "random", - options.Shipsanity.internal_name: "random", - options.Cooksanity.internal_name: "random", - options.Chefsanity.internal_name: "random", - options.Craftsanity.internal_name: "random", - options.Friendsanity.internal_name: "random", - options.FriendsanityHeartSize.internal_name: "random", - options.Booksanity.internal_name: "random", - options.NumberOfMovementBuffs.internal_name: "random", - options.ExcludeGingerIsland.internal_name: "random", - options.TrapDifficulty.internal_name: "random", - options.MultipleDaySleepEnabled.internal_name: "random", - options.MultipleDaySleepCost.internal_name: "random", - options.ExperienceMultiplier.internal_name: "random", - options.FriendshipMultiplier.internal_name: "random", - options.DebrisMultiplier.internal_name: "random", - options.QuickStart.internal_name: "random", - options.Gifting.internal_name: "random", - "death_link": "random", + "progression_balancing": "random", + "accessibility": "random", + options.Goal.internal_name: "random", + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: "random", + options.ProfitMargin.internal_name: "random", + options.BundleRandomization.internal_name: "random", + options.BundlePrice.internal_name: "random", + options.BundlePerRoom.internal_name: "random", + options.EntranceRandomization.internal_name: "random", + options.SeasonRandomization.internal_name: "random", + options.Cropsanity.internal_name: "random", + options.BackpackProgression.internal_name: "random", + options.BackpackSize.internal_name: "random", + options.ToolProgression.internal_name: "random", + options.ElevatorProgression.internal_name: "random", + options.SkillProgression.internal_name: "random", + options.BuildingProgression.internal_name: "random", + options.FestivalLocations.internal_name: "random", + options.ArcadeMachineLocations.internal_name: "random", + options.SpecialOrderLocations.internal_name: "random", + options.QuestLocations.internal_name: "random", + options.Fishsanity.internal_name: "random", + options.Museumsanity.internal_name: "random", + options.Monstersanity.internal_name: "random", + options.Shipsanity.internal_name: "random", + options.Cooksanity.internal_name: "random", + options.Craftsanity.internal_name: "random", + options.Friendsanity.internal_name: "random", + options.FriendsanityHeartSize.internal_name: "random", + options.Booksanity.internal_name: "random", + options.Moviesanity.internal_name: "random", + options.IncludeEndgameLocations.internal_name: "random", + options.NumberOfMovementBuffs.internal_name: "random", + options.ExcludeGingerIsland.internal_name: "random", + options.TrapDifficulty.internal_name: "random", + options.MultipleDaySleepEnabled.internal_name: "random", + options.MultipleDaySleepCost.internal_name: "random", + options.ExperienceMultiplier.internal_name: "random", + options.FriendshipMultiplier.internal_name: "random", + options.DebrisMultiplier.internal_name: "random", + options.QuickStart.internal_name: "random", + options.Gifting.internal_name: "random", + "death_link": "random", + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } easy_settings = { - options.Goal.internal_name: options.Goal.option_community_center, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: "very rich", - options.ProfitMargin.internal_name: "double", - options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic, - options.BundlePrice.internal_name: options.BundlePrice.option_cheap, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized_not_winter, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, - options.ToolProgression.internal_name: options.ToolProgression.option_progressive_very_cheap, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_very_cheap, - options.FestivalLocations.internal_name: options.FestivalLocations.option_easy, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla_very_short, - options.QuestLocations.internal_name: "minimum", - options.Fishsanity.internal_name: options.Fishsanity.option_only_easy_fish, - options.Museumsanity.internal_name: options.Museumsanity.option_milestones, - options.Monstersanity.internal_name: options.Monstersanity.option_one_per_category, - options.Shipsanity.internal_name: options.Shipsanity.option_none, - options.Cooksanity.internal_name: options.Cooksanity.option_none, - options.Chefsanity.internal_name: options.Chefsanity.option_none, - options.Craftsanity.internal_name: options.Craftsanity.option_none, - options.Friendsanity.internal_name: options.Friendsanity.option_none, - options.FriendsanityHeartSize.internal_name: 4, - options.Booksanity.internal_name: options.Booksanity.option_none, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, - options.NumberOfMovementBuffs.internal_name: 8, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_easy, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, - options.MultipleDaySleepCost.internal_name: "free", - options.ExperienceMultiplier.internal_name: "triple", - options.FriendshipMultiplier.internal_name: "quadruple", - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_quarter, - options.QuickStart.internal_name: options.QuickStart.option_true, - options.Gifting.internal_name: options.Gifting.option_true, - "death_link": "false", + options.Goal.internal_name: options.Goal.option_community_center, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: "very rich", + options.ProfitMargin.internal_name: "double", + options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic, + options.BundlePrice.internal_name: options.BundlePrice.option_cheap, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_one_fewer, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, + options.StartWithout.internal_name: options.StartWithout.preset_none, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized_not_winter, + options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive_very_cheap, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_very_cheap, + options.FestivalLocations.internal_name: options.FestivalLocations.option_easy, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla_very_short, + options.QuestLocations.internal_name: "minimum", + options.Fishsanity.internal_name: options.Fishsanity.option_only_easy_fish, + options.Museumsanity.internal_name: options.Museumsanity.option_milestones, + options.Monstersanity.internal_name: options.Monstersanity.option_one_per_category, + options.Shipsanity.internal_name: options.Shipsanity.option_none, + options.Cooksanity.internal_name: options.Cooksanity.option_none, + options.Chefsanity.internal_name: options.Chefsanity.preset_none, + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_none, + options.FriendsanityHeartSize.internal_name: 4, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, + options.Booksanity.internal_name: options.Booksanity.option_none, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, + options.Moviesanity.internal_name: options.Moviesanity.option_none, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, + options.Hatsanity.internal_name: options.Hatsanity.preset_none, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, + options.NumberOfMovementBuffs.internal_name: 8, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_easy, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, + options.MultipleDaySleepCost.internal_name: "free", + options.ExperienceMultiplier.internal_name: "triple", + options.FriendshipMultiplier.internal_name: "quadruple", + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_quarter, + options.QuickStart.internal_name: options.QuickStart.option_true, + options.Gifting.internal_name: options.Gifting.option_true, + "death_link": "false", + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } medium_settings = { - options.Goal.internal_name: options.Goal.option_community_center, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: "rich", - options.ProfitMargin.internal_name: 150, - options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed, - options.BundlePrice.internal_name: options.BundlePrice.option_normal, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_non_progression, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, - options.ToolProgression.internal_name: options.ToolProgression.option_progressive_cheap, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_cheap, - options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_victories_easy, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_short, - options.QuestLocations.internal_name: "normal", - options.Fishsanity.internal_name: options.Fishsanity.option_exclude_legendaries, - options.Museumsanity.internal_name: options.Museumsanity.option_milestones, - options.Monstersanity.internal_name: options.Monstersanity.option_one_per_monster, - options.Shipsanity.internal_name: options.Shipsanity.option_none, - options.Cooksanity.internal_name: options.Cooksanity.option_none, - options.Chefsanity.internal_name: options.Chefsanity.option_queen_of_sauce, - options.Craftsanity.internal_name: options.Craftsanity.option_none, - options.Friendsanity.internal_name: options.Friendsanity.option_starting_npcs, - options.FriendsanityHeartSize.internal_name: 4, - options.Booksanity.internal_name: options.Booksanity.option_power_skill, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, - options.NumberOfMovementBuffs.internal_name: 6, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, - options.MultipleDaySleepCost.internal_name: "free", - options.ExperienceMultiplier.internal_name: "double", - options.FriendshipMultiplier.internal_name: "triple", - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_half, - options.QuickStart.internal_name: options.QuickStart.option_true, - options.Gifting.internal_name: options.Gifting.option_true, - "death_link": "false", + options.Goal.internal_name: options.Goal.option_community_center, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: "rich", + options.ProfitMargin.internal_name: 150, + options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed, + options.BundlePrice.internal_name: options.BundlePrice.option_normal, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_normal, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_non_progression, + options.StartWithout.internal_name: options.StartWithout.preset_easy, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive_cheap, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_cheap, + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_victories_easy, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_short, + options.QuestLocations.internal_name: "normal", + options.Fishsanity.internal_name: options.Fishsanity.option_exclude_legendaries, + options.Museumsanity.internal_name: options.Museumsanity.option_milestones, + options.Monstersanity.internal_name: options.Monstersanity.option_one_per_monster, + options.Shipsanity.internal_name: options.Shipsanity.option_none, + options.Cooksanity.internal_name: options.Cooksanity.option_none, + options.Chefsanity.internal_name: frozenset([ChefsanityOptionName.queen_of_sauce]), + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_starting_npcs, + options.FriendsanityHeartSize.internal_name: 4, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, + options.Booksanity.internal_name: options.Booksanity.option_power_skill, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, + options.Moviesanity.internal_name: options.Moviesanity.option_none, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, + options.Hatsanity.internal_name: options.Hatsanity.preset_none, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, + options.NumberOfMovementBuffs.internal_name: 6, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, + options.MultipleDaySleepCost.internal_name: "free", + options.ExperienceMultiplier.internal_name: "double", + options.FriendshipMultiplier.internal_name: "triple", + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_half, + options.QuickStart.internal_name: options.QuickStart.option_true, + options.Gifting.internal_name: options.Gifting.option_true, + "death_link": "false", + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } hard_settings = { - options.Goal.internal_name: options.Goal.option_grandpa_evaluation, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: "extra", - options.ProfitMargin.internal_name: "normal", - options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed, - options.BundlePrice.internal_name: options.BundlePrice.option_expensive, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings_without_house, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, - options.ToolProgression.internal_name: options.ToolProgression.option_progressive, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive_from_previous_floor, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, - options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi_short, - options.QuestLocations.internal_name: "lots", - options.Fishsanity.internal_name: options.Fishsanity.option_all, - options.Museumsanity.internal_name: options.Museumsanity.option_all, - options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, - options.Shipsanity.internal_name: options.Shipsanity.option_crops, - options.Cooksanity.internal_name: options.Cooksanity.option_queen_of_sauce, - options.Chefsanity.internal_name: options.Chefsanity.option_qos_and_purchases, - options.Craftsanity.internal_name: options.Craftsanity.option_none, - options.Friendsanity.internal_name: options.Friendsanity.option_all, - options.FriendsanityHeartSize.internal_name: 4, - options.Booksanity.internal_name: options.Booksanity.option_all, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, - options.NumberOfMovementBuffs.internal_name: 4, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_hard, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, - options.MultipleDaySleepCost.internal_name: "cheap", - options.ExperienceMultiplier.internal_name: "vanilla", - options.FriendshipMultiplier.internal_name: "double", - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_vanilla, - options.QuickStart.internal_name: options.QuickStart.option_true, - options.Gifting.internal_name: options.Gifting.option_true, - "death_link": "true", + options.Goal.internal_name: options.Goal.option_grandpa_evaluation, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: "extra", + options.ProfitMargin.internal_name: "normal", + options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed, + options.BundlePrice.internal_name: options.BundlePrice.option_expensive, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_one_extra, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings_without_house, + options.StartWithout.internal_name: options.StartWithout.preset_easy, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive_from_previous_floor, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi_short, + options.QuestLocations.internal_name: "lots", + options.Fishsanity.internal_name: options.Fishsanity.option_all, + options.Museumsanity.internal_name: options.Museumsanity.option_all, + options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, + options.Shipsanity.internal_name: options.Shipsanity.option_crops, + options.Cooksanity.internal_name: options.Cooksanity.option_queen_of_sauce, + options.Chefsanity.internal_name: frozenset([ChefsanityOptionName.queen_of_sauce, ChefsanityOptionName.purchases]), + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_all, + options.FriendsanityHeartSize.internal_name: 4, + options.Eatsanity.internal_name: frozenset([EatsanityOptionName.crops, EatsanityOptionName.fish, EatsanityOptionName.artisan]), + options.Booksanity.internal_name: options.Booksanity.option_all, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies, + options.Secretsanity.internal_name: options.Secretsanity.preset_simple, + options.Hatsanity.internal_name: frozenset([HatsanityOptionName.easy]), + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, + options.NumberOfMovementBuffs.internal_name: 4, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_hard, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, + options.MultipleDaySleepCost.internal_name: "cheap", + options.ExperienceMultiplier.internal_name: "vanilla", + options.FriendshipMultiplier.internal_name: "double", + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_vanilla, + options.QuickStart.internal_name: options.QuickStart.option_true, + options.Gifting.internal_name: options.Gifting.option_true, + "death_link": "true", + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } nightmare_settings = { - options.Goal.internal_name: options.Goal.option_community_center, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: "vanilla", - options.ProfitMargin.internal_name: "half", - options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled, - options.BundlePrice.internal_name: options.BundlePrice.option_very_expensive, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, - options.ToolProgression.internal_name: options.ToolProgression.option_progressive, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive_from_previous_floor, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, - options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, - options.QuestLocations.internal_name: "maximum", - options.Fishsanity.internal_name: options.Fishsanity.option_special, - options.Museumsanity.internal_name: options.Museumsanity.option_all, - options.Monstersanity.internal_name: options.Monstersanity.option_split_goals, - options.Shipsanity.internal_name: options.Shipsanity.option_full_shipment_with_fish, - options.Cooksanity.internal_name: options.Cooksanity.option_queen_of_sauce, - options.Chefsanity.internal_name: options.Chefsanity.option_qos_and_purchases, - options.Craftsanity.internal_name: options.Craftsanity.option_none, - options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, - options.FriendsanityHeartSize.internal_name: 4, - options.Booksanity.internal_name: options.Booksanity.option_all, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, - options.NumberOfMovementBuffs.internal_name: 2, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_none, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_hell, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, - options.MultipleDaySleepCost.internal_name: "expensive", - options.ExperienceMultiplier.internal_name: "half", - options.FriendshipMultiplier.internal_name: "vanilla", - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_vanilla, - options.QuickStart.internal_name: options.QuickStart.option_false, - options.Gifting.internal_name: options.Gifting.option_true, - "death_link": "true", + options.Goal.internal_name: options.Goal.option_community_center, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: "vanilla", + options.ProfitMargin.internal_name: "half", + options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled, + options.BundlePrice.internal_name: options.BundlePrice.option_very_expensive, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_extra, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings, + options.StartWithout.internal_name: options.StartWithout.preset_all, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive_from_previous_floor, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.QuestLocations.internal_name: "maximum", + options.Fishsanity.internal_name: options.Fishsanity.option_special, + options.Museumsanity.internal_name: options.Museumsanity.option_all, + options.Monstersanity.internal_name: options.Monstersanity.option_split_goals, + options.Shipsanity.internal_name: options.Shipsanity.option_full_shipment_with_fish, + options.Cooksanity.internal_name: options.Cooksanity.option_queen_of_sauce, + options.Chefsanity.internal_name: frozenset([ChefsanityOptionName.queen_of_sauce, ChefsanityOptionName.purchases]), + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 4, + options.Eatsanity.internal_name: options.Eatsanity.preset_all, + options.Booksanity.internal_name: options.Booksanity.option_all, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies_and_all_loved_snacks, + options.Secretsanity.internal_name: options.Secretsanity.preset_all, + options.Hatsanity.internal_name: options.Hatsanity.preset_difficult, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_true, + options.NumberOfMovementBuffs.internal_name: 2, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_none, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_hell, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, + options.MultipleDaySleepCost.internal_name: "expensive", + options.ExperienceMultiplier.internal_name: "half", + options.FriendshipMultiplier.internal_name: "vanilla", + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_vanilla, + options.QuickStart.internal_name: options.QuickStart.option_false, + options.Gifting.internal_name: options.Gifting.option_true, + "death_link": "true", + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } short_settings = { - options.Goal.internal_name: options.Goal.option_bottom_of_the_mines, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: "filthy rich", - options.ProfitMargin.internal_name: "quadruple", - options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed, - options.BundlePrice.internal_name: options.BundlePrice.option_minimum, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized_not_winter, - options.Cropsanity.internal_name: options.Cropsanity.option_disabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, - options.ToolProgression.internal_name: options.ToolProgression.option_progressive_very_cheap, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_very_cheap, - options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla_very_short, - options.QuestLocations.internal_name: "none", - options.Fishsanity.internal_name: options.Fishsanity.option_none, - options.Museumsanity.internal_name: options.Museumsanity.option_none, - options.Monstersanity.internal_name: options.Monstersanity.option_none, - options.Shipsanity.internal_name: options.Shipsanity.option_none, - options.Cooksanity.internal_name: options.Cooksanity.option_none, - options.Chefsanity.internal_name: options.Chefsanity.option_none, - options.Craftsanity.internal_name: options.Craftsanity.option_none, - options.Friendsanity.internal_name: options.Friendsanity.option_none, - options.FriendsanityHeartSize.internal_name: 4, - options.Booksanity.internal_name: options.Booksanity.option_none, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, - options.NumberOfMovementBuffs.internal_name: 10, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_easy, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, - options.MultipleDaySleepCost.internal_name: "free", - options.ExperienceMultiplier.internal_name: "quadruple", - options.FriendshipMultiplier.internal_name: 800, - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_none, - options.QuickStart.internal_name: options.QuickStart.option_true, - options.Gifting.internal_name: options.Gifting.option_true, - "death_link": "false", + options.Goal.internal_name: options.Goal.option_bottom_of_the_mines, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: "filthy rich", + options.ProfitMargin.internal_name: "quadruple", + options.BundleRandomization.internal_name: options.BundleRandomization.option_remixed, + options.BundlePrice.internal_name: options.BundlePrice.option_minimum, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_fewer, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, + options.StartWithout.internal_name: options.StartWithout.preset_none, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized_not_winter, + options.Cropsanity.internal_name: options.Cropsanity.option_disabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive_very_cheap, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_very_cheap, + options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla_very_short, + options.QuestLocations.internal_name: "none", + options.Fishsanity.internal_name: options.Fishsanity.option_none, + options.Museumsanity.internal_name: options.Museumsanity.option_none, + options.Monstersanity.internal_name: options.Monstersanity.option_none, + options.Shipsanity.internal_name: options.Shipsanity.option_none, + options.Cooksanity.internal_name: options.Cooksanity.option_none, + options.Chefsanity.internal_name: options.Chefsanity.preset_none, + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_none, + options.FriendsanityHeartSize.internal_name: 4, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, + options.Booksanity.internal_name: options.Booksanity.option_none, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, + options.Moviesanity.internal_name: options.Moviesanity.option_none, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, + options.Hatsanity.internal_name: options.Hatsanity.preset_none, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, + options.NumberOfMovementBuffs.internal_name: 10, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_easy, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.option_true, + options.MultipleDaySleepCost.internal_name: "free", + options.ExperienceMultiplier.internal_name: "quadruple", + options.FriendshipMultiplier.internal_name: 800, + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.option_none, + options.QuickStart.internal_name: options.QuickStart.option_true, + options.Gifting.internal_name: options.Gifting.option_true, + "death_link": "false", + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } minsanity_settings = { - options.Goal.internal_name: options.Goal.default, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: options.StartingMoney.default, - options.ProfitMargin.internal_name: options.ProfitMargin.default, - options.BundleRandomization.internal_name: options.BundleRandomization.default, - options.BundlePrice.internal_name: options.BundlePrice.default, - options.EntranceRandomization.internal_name: options.EntranceRandomization.default, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled, - options.Cropsanity.internal_name: options.Cropsanity.option_disabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla, - options.ToolProgression.internal_name: options.ToolProgression.option_vanilla, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, - options.SkillProgression.internal_name: options.SkillProgression.option_vanilla, - options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla, - options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla_very_short, - options.QuestLocations.internal_name: "none", - options.Fishsanity.internal_name: options.Fishsanity.option_none, - options.Museumsanity.internal_name: options.Museumsanity.option_none, - options.Monstersanity.internal_name: options.Monstersanity.option_none, - options.Shipsanity.internal_name: options.Shipsanity.option_none, - options.Cooksanity.internal_name: options.Cooksanity.option_none, - options.Chefsanity.internal_name: options.Chefsanity.option_none, - options.Craftsanity.internal_name: options.Craftsanity.option_none, - options.Friendsanity.internal_name: options.Friendsanity.option_none, - options.FriendsanityHeartSize.internal_name: options.FriendsanityHeartSize.default, - options.Booksanity.internal_name: options.Booksanity.option_none, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, - options.NumberOfMovementBuffs.internal_name: options.NumberOfMovementBuffs.default, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.TrapDifficulty.internal_name: options.TrapDifficulty.default, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.default, - options.MultipleDaySleepCost.internal_name: options.MultipleDaySleepCost.default, - options.ExperienceMultiplier.internal_name: options.ExperienceMultiplier.default, - options.FriendshipMultiplier.internal_name: options.FriendshipMultiplier.default, - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.default, - options.QuickStart.internal_name: options.QuickStart.default, - options.Gifting.internal_name: options.Gifting.default, - "death_link": ap_options.DeathLink.default, + options.Goal.internal_name: options.Goal.default, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: options.StartingMoney.default, + options.ProfitMargin.internal_name: options.ProfitMargin.default, + options.BundleRandomization.internal_name: options.BundleRandomization.default, + options.BundlePrice.internal_name: options.BundlePrice.default, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_fewer, + options.EntranceRandomization.internal_name: options.EntranceRandomization.default, + options.StartWithout.internal_name: options.StartWithout.preset_none, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled, + options.Cropsanity.internal_name: options.Cropsanity.option_disabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.ToolProgression.internal_name: options.ToolProgression.option_vanilla, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, + options.SkillProgression.internal_name: options.SkillProgression.option_vanilla, + options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla, + options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla_very_short, + options.QuestLocations.internal_name: "none", + options.Fishsanity.internal_name: options.Fishsanity.option_none, + options.Museumsanity.internal_name: options.Museumsanity.option_none, + options.Monstersanity.internal_name: options.Monstersanity.option_none, + options.Shipsanity.internal_name: options.Shipsanity.option_none, + options.Cooksanity.internal_name: options.Cooksanity.option_none, + options.Chefsanity.internal_name: options.Chefsanity.preset_none, + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_none, + options.FriendsanityHeartSize.internal_name: options.FriendsanityHeartSize.default, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, + options.Booksanity.internal_name: options.Booksanity.option_none, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, + options.Moviesanity.internal_name: options.Moviesanity.option_none, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, + options.Hatsanity.internal_name: options.Hatsanity.preset_none, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, + options.NumberOfMovementBuffs.internal_name: options.NumberOfMovementBuffs.default, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.TrapDifficulty.internal_name: options.TrapDifficulty.default, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.default, + options.MultipleDaySleepCost.internal_name: options.MultipleDaySleepCost.default, + options.ExperienceMultiplier.internal_name: options.ExperienceMultiplier.default, + options.FriendshipMultiplier.internal_name: options.FriendshipMultiplier.default, + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.default, + options.QuickStart.internal_name: options.QuickStart.default, + options.Gifting.internal_name: options.Gifting.default, + "death_link": ap_options.DeathLink.default, + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } allsanity_settings = { - options.Goal.internal_name: options.Goal.default, - options.FarmType.internal_name: "random", - options.StartingMoney.internal_name: options.StartingMoney.default, - options.ProfitMargin.internal_name: options.ProfitMargin.default, - options.BundleRandomization.internal_name: options.BundleRandomization.default, - options.BundlePrice.internal_name: options.BundlePrice.default, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings, - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, - options.ToolProgression.internal_name: options.ToolProgression.option_progressive, - options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, - options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, - options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, - options.QuestLocations.internal_name: "maximum", - options.Fishsanity.internal_name: options.Fishsanity.option_all, - options.Museumsanity.internal_name: options.Museumsanity.option_all, - options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, - options.Shipsanity.internal_name: options.Shipsanity.option_everything, - options.Cooksanity.internal_name: options.Cooksanity.option_all, - options.Chefsanity.internal_name: options.Chefsanity.option_all, - options.Craftsanity.internal_name: options.Craftsanity.option_all, - options.Friendsanity.internal_name: options.Friendsanity.option_all, - options.FriendsanityHeartSize.internal_name: 1, - options.Booksanity.internal_name: options.Booksanity.option_all, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, - options.NumberOfMovementBuffs.internal_name: 12, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, - options.TrapDifficulty.internal_name: options.TrapDifficulty.default, - options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.default, - options.MultipleDaySleepCost.internal_name: options.MultipleDaySleepCost.default, - options.ExperienceMultiplier.internal_name: options.ExperienceMultiplier.default, - options.FriendshipMultiplier.internal_name: options.FriendshipMultiplier.default, - options.DebrisMultiplier.internal_name: options.DebrisMultiplier.default, - options.QuickStart.internal_name: options.QuickStart.default, - options.Gifting.internal_name: options.Gifting.default, - "death_link": ap_options.DeathLink.default, + options.Goal.internal_name: options.Goal.default, + options.FarmType.internal_name: "random", + options.StartingMoney.internal_name: options.StartingMoney.default, + options.ProfitMargin.internal_name: options.ProfitMargin.default, + options.BundleRandomization.internal_name: options.BundleRandomization.default, + options.BundlePrice.internal_name: options.BundlePrice.default, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_extra, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings, + options.StartWithout.internal_name: options.StartWithout.preset_all, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_1, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.QuestLocations.internal_name: "maximum", + options.Fishsanity.internal_name: options.Fishsanity.option_all, + options.Museumsanity.internal_name: options.Museumsanity.option_all, + options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, + options.Shipsanity.internal_name: options.Shipsanity.option_everything, + options.Cooksanity.internal_name: options.Cooksanity.option_all, + options.Chefsanity.internal_name: options.Chefsanity.preset_all, + options.Craftsanity.internal_name: options.Craftsanity.option_all, + options.Friendsanity.internal_name: options.Friendsanity.option_all, + options.FriendsanityHeartSize.internal_name: 1, + options.Eatsanity.internal_name: options.Eatsanity.preset_all, + options.Booksanity.internal_name: options.Booksanity.option_all, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies_and_all_loved_snacks, + options.Secretsanity.internal_name: options.Secretsanity.preset_all, + options.Hatsanity.internal_name: options.Hatsanity.preset_all, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_true, + options.NumberOfMovementBuffs.internal_name: 12, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.TrapDifficulty.internal_name: options.TrapDifficulty.default, + options.MultipleDaySleepEnabled.internal_name: options.MultipleDaySleepEnabled.default, + options.MultipleDaySleepCost.internal_name: options.MultipleDaySleepCost.default, + options.ExperienceMultiplier.internal_name: options.ExperienceMultiplier.default, + options.FriendshipMultiplier.internal_name: options.FriendshipMultiplier.default, + options.DebrisMultiplier.internal_name: options.DebrisMultiplier.default, + options.QuickStart.internal_name: options.QuickStart.default, + options.Gifting.internal_name: options.Gifting.default, + "death_link": ap_options.DeathLink.default, + + options.Jojapocalypse.internal_name: Jojapocalypse.option_disabled, + options.JojaStartPrice.internal_name: JojaStartPrice.default, + options.JojaEndPrice.internal_name: JojaEndPrice.default, + options.JojaPricingPattern.internal_name: JojaPricingPattern.default, + options.JojaPurchasesForMembership.internal_name: JojaPurchasesForMembership.default, + options.JojaAreYouSure.internal_name: JojaAreYouSure.option_false, } # @formatter:on diff --git a/worlds/stardew_valley/options/settings.py b/worlds/stardew_valley/options/settings.py new file mode 100644 index 000000000000..6c5d0a621373 --- /dev/null +++ b/worlds/stardew_valley/options/settings.py @@ -0,0 +1,43 @@ +from typing import Union + +from settings import Group, Bool + + +class StardewSettings(Group): + + class AllowAllsanityGoal(Bool): + """Allow players to pick the goal 'Allsanity'. If disallowed, generation will fail.""" + + class AllowPerfectionGoal(Bool): + """Allow players to pick the goal 'Perfection'. If disallowed, generation will fail.""" + + class AllowMaxPriceBundles(Bool): + """Allow players to pick the option 'Bundle Price: Maximum'. If disallowed, it will be replaced with 'Very Expensive'""" + + class AllowChaosER(Bool): + """Allow players to pick the option 'Entrance Randomization: Chaos'. If disallowed, it will be replaced with 'Buildings'""" + + class AllowShipsanityEverything(Bool): + """Allow players to pick the option 'Shipsanity: Everything'. If disallowed, it will be replaced with 'Full Shipment With Fish'""" + + class AllowHatsanityNearOrPostPerfection(Bool): + """Allow players to pick the option 'Hatsanity: Near Perfection OR Post Perfection'. If disallowed, it will be replaced with 'Difficult'""" + + class AllowCustomLogic(Bool): + """Allow players to toggle on Custom logic flags. If disallowed, it will be disabled""" + + class AllowJojapocalypse(Bool): + """Allow players to enable Jojapocalypse. If disallowed, it will be disabled""" + + # class AllowSVE(Bool): + # """Allow players to include the mod 'Stardew Valley Expanded'. If disallowed, it will be removed from the mods""" + + allow_allsanity: Union[AllowAllsanityGoal, bool] = True + allow_perfection: Union[AllowPerfectionGoal, bool] = True + allow_max_bundles: Union[AllowMaxPriceBundles, bool] = True + allow_chaos_er: Union[AllowChaosER, bool] = False + allow_shipsanity_everything: Union[AllowShipsanityEverything, bool] = True + allow_hatsanity_perfection: Union[AllowHatsanityNearOrPostPerfection, bool] = True + allow_custom_logic: Union[AllowCustomLogic, bool] = True + allow_jojapocalypse: Union[AllowJojapocalypse, bool] = False + # allow_sve: Union[AllowSVE, bool] = True diff --git a/worlds/stardew_valley/regions/model.py b/worlds/stardew_valley/regions/model.py index 07c390155895..be0d4096d6e9 100644 --- a/worlds/stardew_valley/regions/model.py +++ b/worlds/stardew_valley/regions/model.py @@ -89,6 +89,6 @@ def is_eligible_for_randomization(self, chosen_randomization_flag: Randomization @dataclass(frozen=True) class ModRegionsData: - mod_name: str + content_pack: str regions: list[RegionData] connections: list[ConnectionData] diff --git a/worlds/stardew_valley/regions/vanilla_data.py b/worlds/stardew_valley/regions/vanilla_data.py index dbb83e1063c3..5a66cbe8cbcd 100644 --- a/worlds/stardew_valley/regions/vanilla_data.py +++ b/worlds/stardew_valley/regions/vanilla_data.py @@ -15,33 +15,37 @@ (Entrance.farm_to_backwoods, Entrance.farm_to_bus_stop, Entrance.farm_to_forest, Entrance.farm_to_farmcave, Entrance.enter_greenhouse, Entrance.enter_coop, Entrance.enter_barn, Entrance.enter_shed, Entrance.enter_slime_hutch, LogicEntrance.grow_spring_crops, LogicEntrance.grow_summer_crops, LogicEntrance.grow_fall_crops, LogicEntrance.grow_winter_crops, LogicEntrance.shipping, - LogicEntrance.fishing,)), + LogicEntrance.fishing, LogicEntrance.find_secret_notes,)), RegionData(RegionName.backwoods, (Entrance.backwoods_to_mountain,)), RegionData(RegionName.bus_stop, (Entrance.bus_stop_to_town, Entrance.take_bus_to_desert, Entrance.bus_stop_to_tunnel_entrance)), RegionData(RegionName.forest, - (Entrance.forest_to_town, Entrance.enter_secret_woods, Entrance.forest_to_wizard_tower, Entrance.forest_to_marnie_ranch, - Entrance.forest_to_leah_cottage, Entrance.forest_to_sewer, Entrance.forest_to_mastery_cave, LogicEntrance.buy_from_traveling_merchant, - LogicEntrance.complete_raccoon_requests, LogicEntrance.fish_in_waterfall, LogicEntrance.attend_flower_dance, LogicEntrance.attend_trout_derby, - LogicEntrance.attend_festival_of_ice)), + (Entrance.forest_to_town, Entrance.enter_secret_woods, Entrance.forest_to_wizard_tower, + Entrance.forest_to_marnie_ranch, Entrance.forest_to_leah_cottage, Entrance.forest_to_sewer, + Entrance.forest_to_mastery_cave, Entrance.forest_beach_shortcut, Entrance.feed_trash_bear, + LogicEntrance.buy_from_traveling_merchant, LogicEntrance.has_giant_stump, LogicEntrance.fish_in_waterfall, + LogicEntrance.attend_flower_dance, LogicEntrance.attend_trout_derby, LogicEntrance.attend_festival_of_ice, LogicEntrance.buy_from_hat_mouse)), RegionData(LogicRegion.forest_waterfall), + RegionData(RegionName.trash_bear), RegionData(RegionName.farm_cave), RegionData(RegionName.greenhouse, (LogicEntrance.grow_spring_crops_in_greenhouse, LogicEntrance.grow_summer_crops_in_greenhouse, LogicEntrance.grow_fall_crops_in_greenhouse, LogicEntrance.grow_winter_crops_in_greenhouse, LogicEntrance.grow_indoor_crops_in_greenhouse)), RegionData(RegionName.mountain, (Entrance.mountain_to_railroad, Entrance.mountain_to_tent, Entrance.mountain_to_carpenter_shop, - Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild, - Entrance.mountain_to_town, Entrance.mountain_to_maru_room)), + Entrance.mountain_to_town, Entrance.mountain_to_maru_room, Entrance.mountain_to_outside_adventure_guild, + Entrance.mountain_lake_to_outside_adventure_guild_shortcut, Entrance.mountain_town_shortcut, Entrance.mountain_jojamart_shortcut)), + RegionData(RegionName.outside_adventure_guild, (Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild,)), RegionData(RegionName.maru_room), - RegionData(RegionName.tunnel_entrance, (Entrance.tunnel_entrance_to_bus_tunnel,)), + RegionData(RegionName.tunnel_entrance, (Entrance.tunnel_entrance_to_bus_tunnel, Entrance.tunnel_backwoods_shortcut,)), RegionData(RegionName.bus_tunnel), RegionData(RegionName.town, (Entrance.town_to_community_center, Entrance.town_to_beach, Entrance.town_to_hospital, Entrance.town_to_pierre_general_store, Entrance.town_to_saloon, Entrance.town_to_alex_house, Entrance.town_to_trailer, Entrance.town_to_mayor_manor, Entrance.town_to_sam_house, Entrance.town_to_haley_house, Entrance.town_to_sewer, Entrance.town_to_clint_blacksmith, Entrance.town_to_museum, Entrance.town_to_jojamart, - Entrance.purchase_movie_ticket, LogicEntrance.buy_experience_books, LogicEntrance.attend_egg_festival, LogicEntrance.attend_fair, - LogicEntrance.attend_spirit_eve, LogicEntrance.attend_winter_star)), + Entrance.town_tidepools_shortcut, + Entrance.purchase_movie_ticket, LogicEntrance.buy_books, LogicEntrance.attend_egg_festival, LogicEntrance.attend_fair, + LogicEntrance.attend_spirit_eve, LogicEntrance.attend_winter_star, LogicEntrance.search_garbage_cans)), RegionData(RegionName.beach, (Entrance.beach_to_willy_fish_shop, Entrance.enter_elliott_house, Entrance.enter_tide_pools, LogicEntrance.attend_luau, LogicEntrance.attend_moonlight_jellies, LogicEntrance.attend_night_market, LogicEntrance.attend_squidfest)), @@ -51,8 +55,9 @@ RegionData(RegionName.mastery_cave), RegionData(RegionName.sewer, (Entrance.enter_mutant_bug_lair,)), RegionData(RegionName.mutant_bug_lair), - RegionData(RegionName.wizard_tower, (Entrance.enter_wizard_basement, Entrance.use_desert_obelisk)), + RegionData(RegionName.wizard_tower, (Entrance.enter_wizard_basement, Entrance.use_desert_obelisk, LogicEntrance.purchase_wizard_blueprints)), RegionData(RegionName.wizard_basement), + RegionData(LogicRegion.wizard_blueprints), RegionData(RegionName.tent), RegionData(RegionName.carpenter, (Entrance.enter_sebastian_room,)), RegionData(RegionName.sebastian_room), @@ -81,7 +86,9 @@ RegionData(RegionName.junimo_kart_4), RegionData(RegionName.alex_house), RegionData(RegionName.trailer), - RegionData(RegionName.mayor_house), + RegionData(RegionName.mayor_house, (Entrance.enter_lewis_bedroom,)), + RegionData(RegionName.lewis_bedroom, (Entrance.enter_shorts_maze,)), + RegionData(RegionName.purple_shorts_maze), RegionData(RegionName.sam_house), RegionData(RegionName.haley_house), RegionData(RegionName.blacksmith, (LogicEntrance.blacksmith_copper,)), @@ -93,8 +100,9 @@ RegionData(RegionName.fish_shop), RegionData(RegionName.elliott_house), RegionData(RegionName.tide_pools), - RegionData(RegionName.bathhouse_entrance, (Entrance.enter_locker_room,)), - RegionData(RegionName.locker_room, (Entrance.enter_public_bath,)), + RegionData(RegionName.bathhouse_entrance, (Entrance.enter_mens_locker_room, Entrance.enter_womens_locker_room)), + RegionData(RegionName.mens_locker_room, (Entrance.mens_lockers_to_public_bath,)), + RegionData(RegionName.womens_locker_room, (Entrance.womens_lockers_to_public_bath,)), RegionData(RegionName.public_bath), RegionData(RegionName.witch_warp_cave, (Entrance.enter_witch_swamp,)), RegionData(RegionName.witch_swamp, (Entrance.enter_witch_hut,)), @@ -102,12 +110,13 @@ RegionData(RegionName.quarry, (Entrance.enter_quarry_mine_entrance,)), RegionData(RegionName.quarry_mine_entrance, (Entrance.enter_quarry_mine,)), RegionData(RegionName.quarry_mine), - RegionData(RegionName.secret_woods), + RegionData(RegionName.secret_woods, (LogicEntrance.buy_from_lost_items_shop,)), RegionData(RegionName.desert, (Entrance.enter_skull_cavern_entrance, Entrance.enter_oasis, LogicEntrance.attend_desert_festival)), RegionData(RegionName.oasis, (Entrance.enter_casino,)), RegionData(RegionName.casino), RegionData(RegionName.skull_cavern_entrance, (Entrance.enter_skull_cavern,)), - RegionData(RegionName.skull_cavern, (Entrance.mine_to_skull_cavern_floor_25,)), + RegionData(RegionName.skull_cavern, (Entrance.mine_in_skull_cavern, Entrance.mine_to_skull_cavern_floor_25)), + RegionData(RegionName.skull_cavern_mining), RegionData(RegionName.skull_cavern_25, (Entrance.mine_to_skull_cavern_floor_50,)), RegionData(RegionName.skull_cavern_50, (Entrance.mine_to_skull_cavern_floor_75,)), RegionData(RegionName.skull_cavern_75, (Entrance.mine_to_skull_cavern_floor_100,)), @@ -165,6 +174,7 @@ RegionData(LogicRegion.indoor_farming), RegionData(LogicRegion.shipping), + RegionData(LogicRegion.secret_notes), RegionData(LogicRegion.traveling_cart, (LogicEntrance.buy_from_traveling_merchant_sunday, LogicEntrance.buy_from_traveling_merchant_monday, LogicEntrance.buy_from_traveling_merchant_tuesday, @@ -179,8 +189,21 @@ RegionData(LogicRegion.traveling_cart_thursday), RegionData(LogicRegion.traveling_cart_friday), RegionData(LogicRegion.traveling_cart_saturday), - RegionData(LogicRegion.raccoon_daddy, (LogicEntrance.buy_from_raccoon,)), - RegionData(LogicRegion.raccoon_shop), + RegionData(LogicRegion.raccoon_daddy, (LogicEntrance.buy_from_raccoon_1, LogicEntrance.can_complete_raccoon_requests_1)), + RegionData(LogicRegion.raccoon_request_1, (LogicEntrance.can_complete_raccoon_requests_2,)), + RegionData(LogicRegion.raccoon_request_2, (LogicEntrance.can_complete_raccoon_requests_3,)), + RegionData(LogicRegion.raccoon_request_3, (LogicEntrance.can_complete_raccoon_requests_4,)), + RegionData(LogicRegion.raccoon_request_4, (LogicEntrance.can_complete_raccoon_requests_5,)), + RegionData(LogicRegion.raccoon_request_5, (LogicEntrance.can_complete_raccoon_requests_6,)), + RegionData(LogicRegion.raccoon_request_6, (LogicEntrance.can_complete_raccoon_requests_7,)), + RegionData(LogicRegion.raccoon_request_7, (LogicEntrance.can_complete_raccoon_requests_8,)), + RegionData(LogicRegion.raccoon_request_8), + RegionData(LogicRegion.raccoon_shop_1, (LogicEntrance.buy_from_raccoon_2,)), + RegionData(LogicRegion.raccoon_shop_2, (LogicEntrance.buy_from_raccoon_3,)), + RegionData(LogicRegion.raccoon_shop_3, (LogicEntrance.buy_from_raccoon_4,)), + RegionData(LogicRegion.raccoon_shop_4, (LogicEntrance.buy_from_raccoon_5,)), + RegionData(LogicRegion.raccoon_shop_5, (LogicEntrance.buy_from_raccoon_6,)), + RegionData(LogicRegion.raccoon_shop_6), RegionData(LogicRegion.egg_festival), RegionData(LogicRegion.desert_festival), @@ -194,17 +217,24 @@ RegionData(LogicRegion.night_market), RegionData(LogicRegion.winter_star), RegionData(LogicRegion.squidfest), - RegionData(LogicRegion.bookseller_1, (LogicEntrance.buy_year1_books,)), - RegionData(LogicRegion.bookseller_2, (LogicEntrance.buy_year3_books,)), - RegionData(LogicRegion.bookseller_3), + RegionData(LogicRegion.bookseller, (LogicEntrance.buy_permanent_books, LogicEntrance.buy_rare_books, LogicEntrance.buy_experience_books,)), + RegionData(LogicRegion.bookseller_permanent), + RegionData(LogicRegion.bookseller_rare), + RegionData(LogicRegion.bookseller_experience), + RegionData(LogicRegion.hat_mouse), + RegionData(LogicRegion.lost_items_shop), + RegionData(LogicRegion.garbage_cans), ) ginger_island_regions = ( # This overrides the regions from vanilla... When regions are moved to content packs, overriding existing entrances should no longer be necessary. RegionData(RegionName.mountain, (Entrance.mountain_to_railroad, Entrance.mountain_to_tent, Entrance.mountain_to_carpenter_shop, - Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild, + Entrance.mountain_to_outside_adventure_guild, + Entrance.mountain_lake_to_outside_adventure_guild_shortcut, Entrance.mountain_town_shortcut, Entrance.mountain_jojamart_shortcut, Entrance.mountain_to_town, Entrance.mountain_to_maru_room, Entrance.mountain_to_leo_treehouse)), - RegionData(RegionName.wizard_tower, (Entrance.enter_wizard_basement, Entrance.use_desert_obelisk, Entrance.use_island_obelisk,)), + RegionData(RegionName.outside_adventure_guild, (Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild,)), + RegionData(RegionName.wizard_tower, + (Entrance.enter_wizard_basement, Entrance.use_desert_obelisk, Entrance.use_island_obelisk, LogicEntrance.purchase_wizard_blueprints,)), RegionData(RegionName.fish_shop, (Entrance.fish_shop_to_boat_tunnel,)), RegionData(RegionName.mines_floor_120, (Entrance.dig_to_dangerous_mines_20, Entrance.dig_to_dangerous_mines_60, Entrance.dig_to_dangerous_mines_100)), RegionData(RegionName.skull_cavern_200, (Entrance.enter_dangerous_skull_cavern,)), @@ -276,7 +306,7 @@ ConnectionData(Entrance.take_bus_to_desert, RegionName.desert), ConnectionData(Entrance.forest_to_town, RegionName.town), ConnectionData(Entrance.forest_to_wizard_tower, RegionName.wizard_tower, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.enter_wizard_basement, RegionName.wizard_basement, flag=RandomizationFlag.BUILDINGS), ConnectionData(Entrance.forest_to_marnie_ranch, RegionName.ranch, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), @@ -286,6 +316,7 @@ ConnectionData(Entrance.forest_to_sewer, RegionName.sewer, flag=RandomizationFlag.BUILDINGS), # We remove the bit for masteries, because the mastery cave is to be excluded from the randomization if masteries are not shuffled. ConnectionData(Entrance.forest_to_mastery_cave, RegionName.mastery_cave, flag=RandomizationFlag.BUILDINGS ^ RandomizationFlag.EXCLUDE_MASTERIES), + ConnectionData(Entrance.feed_trash_bear, RegionName.trash_bear), ConnectionData(Entrance.town_to_sewer, RegionName.sewer, flag=RandomizationFlag.BUILDINGS), ConnectionData(Entrance.enter_mutant_bug_lair, RegionName.mutant_bug_lair, flag=RandomizationFlag.BUILDINGS), ConnectionData(Entrance.mountain_to_railroad, RegionName.railroad), @@ -296,6 +327,7 @@ ConnectionData(Entrance.mountain_to_maru_room, RegionName.maru_room, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.enter_sebastian_room, RegionName.sebastian_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.mountain_to_outside_adventure_guild, RegionName.outside_adventure_guild), ConnectionData(Entrance.mountain_to_adventurer_guild, RegionName.adventurer_guild, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.adventurer_guild_to_bedroom, RegionName.adventurer_guild_bedroom), @@ -305,7 +337,7 @@ ConnectionData(Entrance.enter_quarry_mine, RegionName.quarry_mine), ConnectionData(Entrance.mountain_to_town, RegionName.town), ConnectionData(Entrance.town_to_community_center, RegionName.community_center, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.access_crafts_room, RegionName.crafts_room), ConnectionData(Entrance.access_pantry, RegionName.pantry), ConnectionData(Entrance.access_fish_tank, RegionName.fish_tank), @@ -335,6 +367,8 @@ flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.town_to_mayor_manor, RegionName.mayor_house, flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_lewis_bedroom, RegionName.lewis_bedroom), + ConnectionData(Entrance.enter_shorts_maze, RegionName.purple_shorts_maze, flag=RandomizationFlag.BUILDINGS), ConnectionData(Entrance.town_to_alex_house, RegionName.alex_house, flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.town_to_trailer, RegionName.trailer, @@ -353,7 +387,7 @@ flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.enter_tide_pools, RegionName.tide_pools), ConnectionData(Entrance.mountain_to_the_mines, RegionName.mines, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.dig_to_mines_floor_5, RegionName.mines_floor_5), ConnectionData(Entrance.dig_to_mines_floor_10, RegionName.mines_floor_10), ConnectionData(Entrance.dig_to_mines_floor_15, RegionName.mines_floor_15), @@ -384,6 +418,7 @@ flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(Entrance.enter_casino, RegionName.casino, flag=RandomizationFlag.BUILDINGS), ConnectionData(Entrance.enter_skull_cavern, RegionName.skull_cavern), + ConnectionData(Entrance.mine_in_skull_cavern, RegionName.skull_cavern_mining), ConnectionData(Entrance.mine_to_skull_cavern_floor_25, RegionName.skull_cavern_25), ConnectionData(Entrance.mine_to_skull_cavern_floor_50, RegionName.skull_cavern_50), ConnectionData(Entrance.mine_to_skull_cavern_floor_75, RegionName.skull_cavern_75), @@ -398,8 +433,18 @@ ConnectionData(Entrance.witch_warp_to_wizard_basement, RegionName.wizard_basement, flag=RandomizationFlag.BUILDINGS), ConnectionData(Entrance.enter_bathhouse_entrance, RegionName.bathhouse_entrance, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_locker_room, RegionName.locker_room, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_public_bath, RegionName.public_bath, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_mens_locker_room, RegionName.mens_locker_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_womens_locker_room, RegionName.womens_locker_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.mens_lockers_to_public_bath, RegionName.public_bath, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.womens_lockers_to_public_bath, RegionName.public_bath, flag=RandomizationFlag.BUILDINGS), + + ConnectionData(Entrance.town_tidepools_shortcut, RegionName.tide_pools), + ConnectionData(Entrance.forest_beach_shortcut, RegionName.beach), + ConnectionData(Entrance.mountain_jojamart_shortcut, RegionName.town), + ConnectionData(Entrance.tunnel_backwoods_shortcut, RegionName.backwoods), + ConnectionData(Entrance.mountain_town_shortcut, RegionName.town), + ConnectionData(Entrance.mountain_lake_to_outside_adventure_guild_shortcut, RegionName.outside_adventure_guild), + ConnectionData(LogicEntrance.talk_to_mines_dwarf, LogicRegion.mines_dwarf_shop), ConnectionData(LogicEntrance.buy_from_traveling_merchant, LogicRegion.traveling_cart), @@ -410,9 +455,22 @@ ConnectionData(LogicEntrance.buy_from_traveling_merchant_thursday, LogicRegion.traveling_cart_thursday), ConnectionData(LogicEntrance.buy_from_traveling_merchant_friday, LogicRegion.traveling_cart_friday), ConnectionData(LogicEntrance.buy_from_traveling_merchant_saturday, LogicRegion.traveling_cart_saturday), - ConnectionData(LogicEntrance.complete_raccoon_requests, LogicRegion.raccoon_daddy), + ConnectionData(LogicEntrance.has_giant_stump, LogicRegion.raccoon_daddy), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_1, LogicRegion.raccoon_request_1), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_2, LogicRegion.raccoon_request_2), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_3, LogicRegion.raccoon_request_3), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_4, LogicRegion.raccoon_request_4), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_5, LogicRegion.raccoon_request_5), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_6, LogicRegion.raccoon_request_6), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_7, LogicRegion.raccoon_request_7), + ConnectionData(LogicEntrance.can_complete_raccoon_requests_8, LogicRegion.raccoon_request_8), ConnectionData(LogicEntrance.fish_in_waterfall, LogicRegion.forest_waterfall), - ConnectionData(LogicEntrance.buy_from_raccoon, LogicRegion.raccoon_shop), + ConnectionData(LogicEntrance.buy_from_raccoon_1, LogicRegion.raccoon_shop_1), + ConnectionData(LogicEntrance.buy_from_raccoon_2, LogicRegion.raccoon_shop_2), + ConnectionData(LogicEntrance.buy_from_raccoon_3, LogicRegion.raccoon_shop_3), + ConnectionData(LogicEntrance.buy_from_raccoon_4, LogicRegion.raccoon_shop_4), + ConnectionData(LogicEntrance.buy_from_raccoon_5, LogicRegion.raccoon_shop_5), + ConnectionData(LogicEntrance.buy_from_raccoon_6, LogicRegion.raccoon_shop_6), ConnectionData(LogicEntrance.farmhouse_cooking, LogicRegion.kitchen), ConnectionData(LogicEntrance.watch_queen_of_sauce, LogicRegion.queen_of_sauce), @@ -429,6 +487,7 @@ ConnectionData(LogicEntrance.grow_summer_fall_crops_in_fall, LogicRegion.summer_or_fall_farming), ConnectionData(LogicEntrance.shipping, LogicRegion.shipping), + ConnectionData(LogicEntrance.find_secret_notes, LogicRegion.secret_notes), ConnectionData(LogicEntrance.blacksmith_copper, LogicRegion.blacksmith_copper), ConnectionData(LogicEntrance.blacksmith_iron, LogicRegion.blacksmith_iron), ConnectionData(LogicEntrance.blacksmith_gold, LogicRegion.blacksmith_gold), @@ -443,12 +502,17 @@ ConnectionData(LogicEntrance.attend_fair, LogicRegion.fair), ConnectionData(LogicEntrance.attend_spirit_eve, LogicRegion.spirit_eve), ConnectionData(LogicEntrance.attend_festival_of_ice, LogicRegion.festival_of_ice), + ConnectionData(LogicEntrance.buy_from_hat_mouse, LogicRegion.hat_mouse), + ConnectionData(LogicEntrance.buy_from_lost_items_shop, LogicRegion.lost_items_shop), ConnectionData(LogicEntrance.attend_night_market, LogicRegion.night_market), ConnectionData(LogicEntrance.attend_winter_star, LogicRegion.winter_star), ConnectionData(LogicEntrance.attend_squidfest, LogicRegion.squidfest), - ConnectionData(LogicEntrance.buy_experience_books, LogicRegion.bookseller_1), - ConnectionData(LogicEntrance.buy_year1_books, LogicRegion.bookseller_2), - ConnectionData(LogicEntrance.buy_year3_books, LogicRegion.bookseller_3), + ConnectionData(LogicEntrance.buy_books, LogicRegion.bookseller), + ConnectionData(LogicEntrance.buy_permanent_books, LogicRegion.bookseller_permanent), + ConnectionData(LogicEntrance.buy_rare_books, LogicRegion.bookseller_rare), + ConnectionData(LogicEntrance.buy_experience_books, LogicRegion.bookseller_experience), + ConnectionData(LogicEntrance.search_garbage_cans, LogicRegion.garbage_cans), + ConnectionData(LogicEntrance.purchase_wizard_blueprints, LogicRegion.wizard_blueprints), ) ginger_island_connections = ( diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index 7351871a3954..3c78d665df65 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -1,53 +1,70 @@ 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 +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, \ +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 ExcludeGingerIsland, SpecialOrderLocations, Museumsanity, BackpackProgression, Shipsanity, \ +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.ap_names.ap_option_names import WalnutsanityOptionName -from .strings.ap_names.community_upgrade_names import CommunityUpgrade +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.building_names import Building +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 +from .strings.craftable_names import Bomb, Furniture, Consumable, Craftable from .strings.crop_names import Fruit, Vegetable -from .strings.entrance_names import dig_to_mines_floor, dig_to_skull_floor, Entrance, move_to_woods_depth, DeepWoodsEntrance, AlecEntrance, \ +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 MetalBar, Mineral +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 +from .strings.region_names import Region, LogicRegion from .strings.season_names import Season from .strings.skill_names import Skill -from .strings.tool_names import Tool, ToolMaterial +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 @@ -55,82 +72,125 @@ 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): - multiworld = world.multiworld world_options = world.options world_content = world.content - player = world.player + rule_collector = StardewRuleCollector(world.multiworld, world.player, world_content) logic = world.logic bundle_rooms: List[BundleRoom] = world.modified_bundles - - all_location_names = set(location.name for location in multiworld.get_locations(player)) - - set_entrance_rules(logic, multiworld, player, world_options) - set_ginger_island_rules(logic, multiworld, player, world_options) - - set_tool_rules(logic, multiworld, player, world_content) - set_skills_rules(logic, multiworld, player, world_content) - set_bundle_rules(bundle_rooms, logic, multiworld, player, world_options) - set_building_rules(logic, multiworld, player, world_content) - set_cropsanity_rules(logic, multiworld, player, world_content) - set_story_quests_rules(all_location_names, logic, multiworld, player, world_options) - set_special_order_rules(all_location_names, logic, multiworld, player, world_options) - set_help_wanted_quests_rules(logic, multiworld, player, world_options) - set_fishsanity_rules(all_location_names, logic, multiworld, player) - set_museumsanity_rules(all_location_names, logic, multiworld, player, world_options) - - set_friendsanity_rules(logic, multiworld, player, world_content) - set_backpack_rules(logic, multiworld, player, world_options) - set_festival_rules(all_location_names, logic, multiworld, player) - set_monstersanity_rules(all_location_names, logic, multiworld, player, world_options) - set_shipsanity_rules(all_location_names, logic, multiworld, player, world_options) - set_cooksanity_rules(all_location_names, logic, multiworld, player, world_options) - set_chefsanity_rules(all_location_names, logic, multiworld, player, world_options) - set_craftsanity_rules(all_location_names, logic, multiworld, player, world_options) - set_booksanity_rules(logic, multiworld, player, world_content) - set_isolated_locations_rules(logic, multiworld, player) - set_traveling_merchant_day_rules(logic, multiworld, player) - set_arcade_machine_rules(logic, multiworld, player, world_options) - - set_deepwoods_rules(logic, multiworld, player, world_options) - set_magic_spell_rules(logic, multiworld, player, world_options) - set_sve_rules(logic, multiworld, player, world_options) - - -def set_isolated_locations_rules(logic: StardewLogic, multiworld, player): - set_rule(multiworld.get_location("Old Master Cannoli", player), - logic.has(Fruit.sweet_gem_berry)) - set_rule(multiworld.get_location("Galaxy Sword Shrine", player), - logic.has("Prismatic Shard")) - set_rule(multiworld.get_location("Krobus Stardrop", player), - logic.money.can_spend(20000)) - set_rule(multiworld.get_location("Demetrius's Breakthrough", player), - logic.money.can_have_earned_total(25000)) - set_rule(multiworld.get_location("Pot Of Gold", player), - logic.season.has(Season.spring)) - - -def set_tool_rules(logic: StardewLogic, multiworld, player, content: StardewContent): - if not content.features.tool_progression.is_progressive: + 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 - set_rule(multiworld.get_location("Purchase Fiberglass Rod", player), - (logic.skill.has_level(Skill.fishing, 2) & logic.money.can_spend(1800))) - set_rule(multiworld.get_location("Purchase Iridium Rod", player), - (logic.skill.has_level(Skill.fishing, 6) & logic.money.can_spend(7500))) + 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))) - set_rule(multiworld.get_location("Copper Pan Cutscene", player), logic.received("Glittering Boulder Removed")) + rule_collector.set_location_rule("Copper Pan Cutscene", logic.received("Glittering Boulder Removed")) - materials = [None, "Copper", "Iron", "Gold", "Iridium"] - tool = [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can, Tool.pan] - for (previous, material), tool in itertools.product(zip(materials[:4], materials[1:]), tool): - if previous is None: - continue - tool_upgrade_location = multiworld.get_location(f"{material} {tool} Upgrade", player) - set_rule(tool_upgrade_location, logic.tool.has_tool(tool, previous)) + # 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, multiworld, player, content: StardewContent): +def set_building_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent): building_progression = content.features.building_progression if not building_progression.is_progressive: return @@ -141,33 +201,27 @@ def set_building_rules(logic: StardewLogic, multiworld, player, content: Stardew location_name = building_progression.to_location_name(building.name) - set_rule(multiworld.get_location(location_name, player), - logic.building.can_build(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, multiworld, player, world_options: StardewValleyOptions): +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: - location = multiworld.get_location(bundle.name, player) bundle_rules = logic.bundle.can_complete_bundle(bundle) - if bundle_room.name == CCRoom.raccoon_requests: - num = int(bundle.name[-1]) - extra_raccoons = 1 if world_options.quest_locations.has_story_quests() else 0 - extra_raccoons = extra_raccoons + num - bundle_rules = logic.received(CommunityUpgrade.raccoon, extra_raccoons) & bundle_rules - if num > 1: - previous_bundle_name = f"Raccoon Request {num - 1}" - bundle_rules = bundle_rules & logic.region.can_reach_location(previous_bundle_name) room_rules.append(bundle_rules) - set_rule(location, 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}" - set_rule(multiworld.get_location(room_location, player), And(*room_rules)) + rule_collector.set_location_rule(room_location, And(*room_rules)) -def set_skills_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, content: StardewContent): +def set_skills_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent): skill_progression = content.features.skill_progression if not skill_progression.is_progressive: return @@ -175,188 +229,252 @@ def set_skills_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, c 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) - location = multiworld.get_location(level_name, player) - set_rule(location, rule) + rule_collector.set_location_rule(level_name, rule) if skill_progression.is_mastery_randomized(skill): rule = logic.skill.can_earn_mastery(skill.name) - location = multiworld.get_location(skill.mastery_name, player) - set_rule(location, rule) - - -def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): - set_mines_floor_entrance_rules(logic, multiworld, player) - set_skull_cavern_floor_entrance_rules(logic, multiworld, player) - set_blacksmith_entrance_rules(logic, multiworld, player) - set_skill_entrance_rules(logic, multiworld, player, world_options) - set_traveling_merchant_day_rules(logic, multiworld, player) - set_dangerous_mine_rules(logic, multiworld, player, world_options) - - set_entrance_rule(multiworld, player, Entrance.enter_tide_pools, logic.received("Beach Bridge") | (logic.mod.magic.can_blink())) - set_entrance_rule(multiworld, player, Entrance.enter_quarry, logic.received("Bridge Repair") | (logic.mod.magic.can_blink())) - set_entrance_rule(multiworld, player, Entrance.enter_secret_woods, logic.tool.has_tool(Tool.axe, "Iron") | (logic.mod.magic.can_blink())) - set_entrance_rule(multiworld, player, Entrance.forest_to_wizard_tower, logic.region.can_reach(Region.community_center)) - set_entrance_rule(multiworld, player, Entrance.forest_to_sewer, logic.wallet.has_rusty_key()) - set_entrance_rule(multiworld, player, Entrance.town_to_sewer, logic.wallet.has_rusty_key()) - set_entrance_rule(multiworld, player, Entrance.enter_abandoned_jojamart, logic.has_abandoned_jojamart()) + 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() - set_entrance_rule(multiworld, player, Entrance.enter_movie_theater, movie_theater_rule) - set_entrance_rule(multiworld, player, Entrance.purchase_movie_ticket, movie_theater_rule) - set_entrance_rule(multiworld, player, Entrance.take_bus_to_desert, logic.received("Bus Repair") & logic.money.can_spend(500)) - set_entrance_rule(multiworld, player, Entrance.enter_skull_cavern, logic.received(Wallet.skull_key)) - set_entrance_rule(multiworld, player, LogicEntrance.talk_to_mines_dwarf, - logic.wallet.can_speak_dwarf() & logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron)) - set_entrance_rule(multiworld, player, LogicEntrance.buy_from_traveling_merchant, logic.traveling_merchant.has_days() & logic.money.can_spend(1200)) - set_entrance_rule(multiworld, player, LogicEntrance.buy_from_raccoon, logic.quest.has_raccoon_shop()) - set_entrance_rule(multiworld, player, LogicEntrance.fish_in_waterfall, - logic.skill.has_level(Skill.fishing, 5) & logic.tool.has_fishing_rod(2)) - - set_farm_buildings_entrance_rules(logic, multiworld, player) - - set_entrance_rule(multiworld, player, Entrance.mountain_to_railroad, logic.received("Railroad Boulder Removed")) - set_entrance_rule(multiworld, player, Entrance.enter_witch_warp_cave, logic.quest.has_dark_talisman() | (logic.mod.magic.can_blink())) - set_entrance_rule(multiworld, player, Entrance.enter_witch_hut, (logic.quest.can_complete_quest(Quest.goblin_problem) | logic.mod.magic.can_blink())) - set_entrance_rule(multiworld, player, 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()) - set_entrance_rule(multiworld, player, Entrance.enter_casino, logic.quest.has_club_card()) - - set_bedroom_entrance_rules(logic, multiworld, player, world_options) - set_festival_entrance_rules(logic, multiworld, player) - set_island_entrance_rule(multiworld, player, LogicEntrance.island_cooking, logic.cooking.can_cook_in_kitchen, world_options) - set_entrance_rule(multiworld, player, LogicEntrance.farmhouse_cooking, logic.cooking.can_cook_in_kitchen) - set_entrance_rule(multiworld, player, LogicEntrance.shipping, logic.shipping.can_use_shipping_bin) - set_entrance_rule(multiworld, player, LogicEntrance.watch_queen_of_sauce, logic.action.can_watch(Channel.queen_of_sauce)) - set_entrance_rule(multiworld, player, Entrance.forest_to_mastery_cave, logic.skill.can_enter_mastery_cave) - set_entrance_rule(multiworld, player, LogicEntrance.buy_experience_books, logic.time.has_lived_months(2)) - set_entrance_rule(multiworld, player, LogicEntrance.buy_year1_books, logic.time.has_year_two) - set_entrance_rule(multiworld, player, LogicEntrance.buy_year3_books, logic.time.has_year_three) - set_entrance_rule(multiworld, player, Entrance.adventurer_guild_to_bedroom, logic.monster.can_kill_max(Generic.any)) - - -def set_dangerous_mine_rules(logic, multiworld, player, world_options: StardewValleyOptions): - if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: + 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) - set_entrance_rule(multiworld, player, Entrance.dig_to_dangerous_mines_20, dangerous_mine_rule) - set_entrance_rule(multiworld, player, Entrance.dig_to_dangerous_mines_60, dangerous_mine_rule) - set_entrance_rule(multiworld, player, Entrance.dig_to_dangerous_mines_100, dangerous_mine_rule) - set_entrance_rule(multiworld, player, 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, multiworld, player): - set_entrance_rule(multiworld, player, Entrance.downstairs_to_cellar, logic.building.has_building(Building.cellar)) - set_entrance_rule(multiworld, player, Entrance.use_desert_obelisk, logic.can_use_obelisk(Transportation.desert_obelisk)) - set_entrance_rule(multiworld, player, Entrance.enter_greenhouse, logic.received("Greenhouse")) - set_entrance_rule(multiworld, player, Entrance.enter_coop, logic.building.has_building(Building.coop)) - set_entrance_rule(multiworld, player, Entrance.enter_barn, logic.building.has_building(Building.barn)) - set_entrance_rule(multiworld, player, Entrance.enter_shed, logic.building.has_building(Building.shed)) - set_entrance_rule(multiworld, player, Entrance.enter_slime_hutch, logic.building.has_building(Building.slime_hutch)) - - -def set_bedroom_entrance_rules(logic, multiworld, player, world_options: StardewValleyOptions): - set_entrance_rule(multiworld, player, Entrance.enter_harvey_room, logic.relationship.has_hearts(NPC.harvey, 2)) - set_entrance_rule(multiworld, player, Entrance.mountain_to_maru_room, logic.relationship.has_hearts(NPC.maru, 2)) - set_entrance_rule(multiworld, player, Entrance.enter_sebastian_room, (logic.relationship.has_hearts(NPC.sebastian, 2) | logic.mod.magic.can_blink())) - set_entrance_rule(multiworld, player, Entrance.forest_to_leah_cottage, logic.relationship.has_hearts(NPC.leah, 2)) - set_entrance_rule(multiworld, player, Entrance.enter_elliott_house, logic.relationship.has_hearts(NPC.elliott, 2)) - set_entrance_rule(multiworld, player, Entrance.enter_sunroom, logic.relationship.has_hearts(NPC.caroline, 2)) - set_entrance_rule(multiworld, player, Entrance.enter_wizard_basement, logic.relationship.has_hearts(NPC.wizard, 4)) - if ModNames.alec in world_options.mods: - set_entrance_rule(multiworld, player, AlecEntrance.petshop_to_bedroom, (logic.relationship.has_hearts(ModNPC.alec, 2) | logic.mod.magic.can_blink())) - if ModNames.lacey in world_options.mods: - set_entrance_rule(multiworld, player, LaceyEntrance.forest_to_hat_house, logic.relationship.has_hearts(ModNPC.lacey, 2)) - - -def set_mines_floor_entrance_rules(logic, multiworld, player): + 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): - rule = logic.mine.has_mine_elevator_to_floor(floor - 10) + 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) - set_entrance_rule(multiworld, player, dig_to_mines_floor(floor), rule) + 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)) -def set_skull_cavern_floor_entrance_rules(logic, multiworld, player): for floor in range(25, 200 + 25, 25): - rule = logic.mod.elevator.has_skull_cavern_elevator_to_floor(floor - 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) - set_entrance_rule(multiworld, player, dig_to_skull_floor(floor), rule) - - -def set_skill_entrance_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): - set_entrance_rule(multiworld, player, LogicEntrance.grow_spring_crops, logic.farming.has_farming_tools & logic.season.has_spring) - set_entrance_rule(multiworld, player, LogicEntrance.grow_summer_crops, logic.farming.has_farming_tools & logic.season.has_summer) - set_entrance_rule(multiworld, player, LogicEntrance.grow_fall_crops, logic.farming.has_farming_tools & logic.season.has_fall) - set_entrance_rule(multiworld, player, LogicEntrance.grow_spring_crops_in_greenhouse, logic.farming.has_farming_tools) - set_entrance_rule(multiworld, player, LogicEntrance.grow_summer_crops_in_greenhouse, logic.farming.has_farming_tools) - set_entrance_rule(multiworld, player, LogicEntrance.grow_fall_crops_in_greenhouse, logic.farming.has_farming_tools) - set_entrance_rule(multiworld, player, LogicEntrance.grow_indoor_crops_in_greenhouse, logic.farming.has_farming_tools) - set_island_entrance_rule(multiworld, player, LogicEntrance.grow_spring_crops_on_island, logic.farming.has_farming_tools, world_options) - set_island_entrance_rule(multiworld, player, LogicEntrance.grow_summer_crops_on_island, logic.farming.has_farming_tools, world_options) - set_island_entrance_rule(multiworld, player, LogicEntrance.grow_fall_crops_on_island, logic.farming.has_farming_tools, world_options) - set_island_entrance_rule(multiworld, player, LogicEntrance.grow_indoor_crops_on_island, logic.farming.has_farming_tools, world_options) - set_entrance_rule(multiworld, player, LogicEntrance.grow_summer_fall_crops_in_summer, true_) - set_entrance_rule(multiworld, player, LogicEntrance.grow_summer_fall_crops_in_fall, true_) - - set_entrance_rule(multiworld, player, LogicEntrance.fishing, logic.fishing.can_fish_anywhere()) - - -def set_blacksmith_entrance_rules(logic, multiworld, player): - set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_copper, MetalBar.copper, ToolMaterial.copper) - set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_iron, MetalBar.iron, ToolMaterial.iron) - set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_gold, MetalBar.gold, ToolMaterial.gold) - set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_iridium, MetalBar.iridium, ToolMaterial.iridium) - - -def set_blacksmith_upgrade_rule(logic, multiworld, player, entrance_name: str, item_name: str, tool_material: str): + 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]) - set_entrance_rule(multiworld, player, entrance_name, upgrade_rule) + rule_collector.set_entrance_rule(entrance_name, upgrade_rule) -def set_festival_entrance_rules(logic, multiworld, player): - set_entrance_rule(multiworld, player, LogicEntrance.attend_egg_festival, logic.season.has(Season.spring)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_desert_festival, logic.season.has(Season.spring) & logic.received("Bus Repair")) - set_entrance_rule(multiworld, player, LogicEntrance.attend_flower_dance, logic.season.has(Season.spring)) +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)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_luau, logic.season.has(Season.summer)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_trout_derby, logic.season.has(Season.summer)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_moonlight_jellies, logic.season.has(Season.summer)) + 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)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_fair, logic.season.has(Season.fall)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_spirit_eve, logic.season.has(Season.fall)) + 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)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_festival_of_ice, logic.season.has(Season.winter)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_squidfest, logic.season.has(Season.winter)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_night_market, logic.season.has(Season.winter)) - set_entrance_rule(multiworld, player, LogicEntrance.attend_winter_star, logic.season.has(Season.winter)) + 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, multiworld, player, world_options: StardewValleyOptions): - set_island_entrances_rules(logic, multiworld, player, world_options) - if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: +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, multiworld, player) - set_island_parrot_rules(logic, multiworld, player) - set_rule(multiworld.get_location("Open Professor Snail Cave", player), - logic.has(Bomb.cherry_bomb)) - set_rule(multiworld.get_location("Complete Island Field Office", player), - logic.walnut.can_complete_field_office()) - set_walnut_rules(logic, multiworld, player, world_options) + 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, multiworld, player): - set_rule(multiworld.get_location("Repair Boat Hull", player), - logic.has(Material.hardwood)) - set_rule(multiworld.get_location("Repair Boat Anchor", player), - logic.has(MetalBar.iridium)) - set_rule(multiworld.get_location("Repair Ticket Machine", player), - logic.has(ArtisanGood.battery_pack)) +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, multiworld, player, world_options: StardewValleyOptions): +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 = { @@ -368,17 +486,17 @@ def set_island_entrances_rules(logic: StardewLogic, multiworld, player, world_op 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, + 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(0) | logic.received("Volcano Bridge") | logic.mod.magic.can_blink(), - Entrance.volcano_to_secret_beach: logic.tool.can_water(2), - Entrance.climb_to_volcano_5: logic.ability.can_mine_perfectly() & logic.tool.can_water(1), + 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(1), + 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, @@ -395,85 +513,76 @@ def set_island_entrances_rules(logic: StardewLogic, multiworld, player, world_op else: entrance_rules[parrot] = parrot_express_rule - set_many_island_entrances_rules(multiworld, player, entrance_rules, world_options) + rule_collector.set_many_island_entrances_rules(entrance_rules) -def set_island_parrot_rules(logic: StardewLogic, multiworld, player): +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) - set_rule(multiworld.get_location("Leo's Parrot", player), - has_walnut) - set_rule(multiworld.get_location("Island West Turtle", player), - has_10_walnut & logic.received("Island North Turtle")) - set_rule(multiworld.get_location("Island Farmhouse", player), - has_20_walnut) - set_rule(multiworld.get_location("Island Mailbox", player), - has_5_walnut & logic.received("Island Farmhouse")) - set_rule(multiworld.get_location(Transportation.farm_obelisk, player), - has_20_walnut & logic.received("Island Mailbox")) - set_rule(multiworld.get_location("Dig Site Bridge", player), - has_10_walnut & logic.received("Island West Turtle")) - set_rule(multiworld.get_location("Island Trader", player), - has_10_walnut & logic.received("Island Farmhouse")) - set_rule(multiworld.get_location("Volcano Bridge", player), - has_5_walnut & logic.received("Island West Turtle") & - logic.region.can_reach(Region.volcano_floor_10)) - set_rule(multiworld.get_location("Volcano Exit Shortcut", player), - has_5_walnut & logic.received("Island West Turtle")) - set_rule(multiworld.get_location("Island Resort", player), - has_20_walnut & logic.received("Island Farmhouse")) - set_rule(multiworld.get_location(Transportation.parrot_express, player), - has_10_walnut) - - -def set_walnut_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): + 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, multiworld, player, world_options) - set_walnut_bushes_rules(logic, multiworld, player, world_options) - set_walnut_dig_spot_rules(logic, multiworld, player, world_options) - set_walnut_repeatable_rules(logic, multiworld, player, world_options) + 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, multiworld, player, world_options): +def set_walnut_puzzle_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options): if WalnutsanityOptionName.puzzles not in world_options.walnutsanity: return - set_rule(multiworld.get_location("Walnutsanity: Open Golden Coconut", player), logic.has(Geode.golden_coconut)) - set_rule(multiworld.get_location("Walnutsanity: Banana Altar", player), logic.has(Fruit.banana)) - set_rule(multiworld.get_location("Walnutsanity: Leo's Tree", player), logic.tool.has_tool(Tool.axe)) - set_rule(multiworld.get_location("Walnutsanity: Gem Birds Shrine", player), logic.has(Mineral.amethyst) & logic.has(Mineral.aquamarine) & - logic.has(Mineral.emerald) & logic.has(Mineral.ruby) & logic.has(Mineral.topaz) & - logic.region.can_reach_all((Region.island_north, Region.island_west, Region.island_east, Region.island_south))) - set_rule(multiworld.get_location("Walnutsanity: Gourmand Frog Melon", player), logic.has(Fruit.melon) & logic.region.can_reach(Region.island_west)) - set_rule(multiworld.get_location("Walnutsanity: Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) & - logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Walnutsanity: Gourmand Frog Melon")) - set_rule(multiworld.get_location("Walnutsanity: Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) & - logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Walnutsanity: Gourmand Frog Wheat")) - set_rule(multiworld.get_location("Walnutsanity: Whack A Mole", player), logic.tool.has_tool(Tool.watering_can, ToolMaterial.iridium)) - set_rule(multiworld.get_location("Walnutsanity: Complete Large Animal Collection", player), logic.walnut.can_complete_large_animal_collection()) - set_rule(multiworld.get_location("Walnutsanity: Complete Snake Collection", player), logic.walnut.can_complete_snake_collection()) - set_rule(multiworld.get_location("Walnutsanity: Complete Mummified Frog Collection", player), logic.walnut.can_complete_frog_collection()) - set_rule(multiworld.get_location("Walnutsanity: Complete Mummified Bat Collection", player), logic.walnut.can_complete_bat_collection()) - set_rule(multiworld.get_location("Walnutsanity: Purple Flowers Island Survey", player), logic.walnut.can_start_field_office) - set_rule(multiworld.get_location("Walnutsanity: Purple Starfish Island Survey", player), logic.walnut.can_start_field_office) - set_rule(multiworld.get_location("Walnutsanity: Protruding Tree Walnut", player), logic.combat.has_slingshot) - set_rule(multiworld.get_location("Walnutsanity: Starfish Tide Pool", player), logic.tool.has_fishing_rod(1)) - set_rule(multiworld.get_location("Walnutsanity: Mermaid Song", player), logic.has(Furniture.flute_block)) - - -def set_walnut_bushes_rules(logic, multiworld, player, world_options): + 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, multiworld, player, world_options): +def set_walnut_dig_spot_rules(logic, rule_collector: StardewRuleCollector, world_options): if WalnutsanityOptionName.dig_spots not in world_options.walnutsanity: return @@ -483,58 +592,58 @@ def set_walnut_dig_spot_rules(logic, multiworld, player, world_options): 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) - set_rule(multiworld.get_location(dig_spot_walnut.name, player), rule) + rule_collector.set_location_rule(dig_spot_walnut.name, rule) -def set_walnut_repeatable_rules(logic, multiworld, player, world_options): +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): - set_rule(multiworld.get_location(f"Walnutsanity: Fishing Walnut {i}", player), logic.tool.has_fishing_rod(1)) - set_rule(multiworld.get_location(f"Walnutsanity: Harvesting Walnut {i}", player), logic.skill.can_get_farming_xp) - set_rule(multiworld.get_location(f"Walnutsanity: Mussel Node Walnut {i}", player), logic.tool.has_tool(Tool.pickaxe)) - set_rule(multiworld.get_location(f"Walnutsanity: Volcano Rocks Walnut {i}", player), logic.tool.has_tool(Tool.pickaxe)) - set_rule(multiworld.get_location(f"Walnutsanity: Volcano Monsters Walnut {i}", player), logic.combat.has_galaxy_weapon) - set_rule(multiworld.get_location(f"Walnutsanity: Volcano Crates Walnut {i}", player), logic.combat.has_any_weapon) - set_rule(multiworld.get_location(f"Walnutsanity: Tiger Slime Walnut", player), logic.monster.can_kill(Monster.tiger_slime)) + 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, multiworld, player, world_content: StardewContent): +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))) - set_rule(multiworld.get_location(location, player), logic.source.has_access_to_any(harvest_sources)) + 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, multiworld, player, world_options: StardewValleyOptions): +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 in locations.locations_by_tag[LocationTags.STORY_QUEST]: - if quest.name in all_location_names and (quest.mod_name is None or quest.mod_name in world_options.mods): - set_rule(multiworld.get_location(quest.name, player), - logic.registry.quest_rules[quest.name]) + 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, multiworld, player, - world_options: StardewValleyOptions): +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] - set_rule(multiworld.get_location(board_order.name, player), order_rule) + rule_collector.set_location_rule(board_order.name, order_rule) - if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: - return - if world_options.special_order_locations & SpecialOrderLocations.value_qi: + 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] - set_rule(multiworld.get_location(qi_order.name, player), order_rule) + rule_collector.set_location_rule(qi_order.name, order_rule) help_wanted_prefix = "Help Wanted:" @@ -544,7 +653,7 @@ def set_special_order_rules(all_location_names: Set[str], logic: StardewLogic, m slay_monsters = "Slay Monsters" -def set_help_wanted_quests_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): +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 @@ -555,55 +664,53 @@ def set_help_wanted_quests_rules(logic: StardewLogic, multiworld, player, world_ 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(multiworld, player, month_rule, quest_number) + set_help_wanted_delivery_rule(logic, rule_collector, month_rule, quest_number) elif quest_number_in_set == 4: - set_help_wanted_fishing_rule(multiworld, player, month_rule, quest_number) + set_help_wanted_fishing_rule(logic, rule_collector, month_rule, quest_number) elif quest_number_in_set == 5: - set_help_wanted_slay_monsters_rule(multiworld, player, month_rule, quest_number) + set_help_wanted_slay_monsters_rule(logic, rule_collector, month_rule, quest_number) elif quest_number_in_set == 6: - set_help_wanted_gathering_rule(multiworld, player, month_rule, quest_number) + set_help_wanted_gathering_rule(logic, rule_collector, month_rule, quest_number) -def set_help_wanted_delivery_rule(multiworld, player, 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}" - set_rule(multiworld.get_location(location_name, player), month_rule) + rule_collector.set_location_rule(location_name, logic.quest.can_do_item_delivery_quest() & month_rule) -def set_help_wanted_gathering_rule(multiworld, player, month_rule, quest_number): +def set_help_wanted_gathering_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, month_rule, quest_number): location_name = f"{help_wanted_prefix} {gathering} {quest_number}" - set_rule(multiworld.get_location(location_name, player), month_rule) + rule_collector.set_location_rule(location_name, logic.quest.can_do_gathering_quest() & month_rule) -def set_help_wanted_fishing_rule(multiworld, player, month_rule, quest_number): +def set_help_wanted_fishing_rule(logic: StardewLogic, rule_collector: StardewRuleCollector, month_rule, quest_number): location_name = f"{help_wanted_prefix} {fishing} {quest_number}" - set_rule(multiworld.get_location(location_name, player), month_rule) + rule_collector.set_location_rule(location_name, logic.quest.can_do_fishing_quest() & month_rule) -def set_help_wanted_slay_monsters_rule(multiworld, player, month_rule, quest_number): +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}" - set_rule(multiworld.get_location(location_name, player), month_rule) + 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, multiworld: MultiWorld, player: int): +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):] - set_rule(multiworld.get_location(fish_location.name, player), - logic.has(fish_name)) + rule_collector.set_location_rule(fish_location.name, logic.has(fish_name)) -def set_museumsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld: MultiWorld, player: int, - world_options: StardewValleyOptions): +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, multiworld, museum_milestone, museum_prefix, player) + 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, multiworld, museum_prefix, player) + set_museum_individual_donations_rules(all_location_names, logic, rule_collector, museum_prefix) -def set_museum_individual_donations_rules(all_location_names, logic: StardewLogic, multiworld, museum_prefix, player): +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 @@ -613,13 +720,11 @@ def set_museum_individual_donations_rules(all_location_names, logic: StardewLogi 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) - set_rule(multiworld.get_location(museum_location.name, player), - rule) + rule_collector.set_location_rule(museum_location.name, rule) counter += 1 -def set_museum_milestone_rule(logic: StardewLogic, multiworld: MultiWorld, museum_milestone, museum_prefix: str, - player: int): +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" @@ -644,7 +749,7 @@ def set_museum_milestone_rule(logic: StardewLogic, multiworld: MultiWorld, museu rule = logic.museum.can_find_museum_item(Artifact.ancient_seed) & logic.received(metal_detector, 2) if rule is None: return - set_rule(multiworld.get_location(museum_milestone.name, player), rule) + rule_collector.set_location_rule(museum_milestone.name, rule) def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items, donation_func): @@ -655,85 +760,89 @@ def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, acce return rule -def set_backpack_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - if world_options.backpack_progression != BackpackProgression.option_vanilla: - set_rule(multiworld.get_location("Large Pack", player), - logic.money.can_spend(2000)) - set_rule(multiworld.get_location("Deluxe Pack", player), - (logic.money.can_spend(10000) & logic.received("Progressive Backpack"))) - if ModNames.big_backpack in world_options.mods: - set_rule(multiworld.get_location("Premium Pack", player), - (logic.money.can_spend(150000) & - logic.received("Progressive Backpack", 2))) - +def set_backpack_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, world_options: StardewValleyOptions, content: StardewContent): + if world_options.backpack_progression == BackpackProgression.option_vanilla: + return -def set_festival_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player): + 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: - set_rule(multiworld.get_location(festival.name, player), - logic.registry.festival_rules[festival.name]) + 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, multiworld, player, world_options: StardewValleyOptions): +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, multiworld, player, monstersanity_option) + 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, multiworld, player) + set_monstersanity_progressive_category_rules(all_location_names, logic, rule_collector) return - set_monstersanity_category_rules(all_location_names, logic, multiworld, player, monstersanity_option) + set_monstersanity_category_rules(all_location_names, logic, rule_collector, monstersanity_option) -def set_monstersanity_monster_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, 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 - location = multiworld.get_location(location_name, player) 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]) - set_rule(location, rule) + rule_collector.set_location_rule(location_name, rule) -def set_monstersanity_progressive_category_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player): +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, multiworld, player, monster_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, multiworld, player, monster_category: str): +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, multiworld, player, monster_category, location_name, 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, multiworld, player, +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 - location = multiworld.get_location(location_name, player) 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) - set_rule(location, rule) + rule_collector.set_location_rule(location_name, rule) def get_monster_eradication_number(location_name, monster_category) -> int: @@ -744,20 +853,19 @@ def get_monster_eradication_number(location_name, monster_category) -> int: return 1000 -def set_monstersanity_category_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, monstersanity_option): +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 - location = multiworld.get_location(location_name, player) 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) - set_rule(location, rule) + rule_collector.set_location_rule(location_name, rule) -def set_shipsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): +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 @@ -767,10 +875,10 @@ def set_shipsanity_rules(all_location_names: Set[str], logic: StardewLogic, mult if location.name not in all_location_names: continue item_to_ship = location.name[len(shipsanity_prefix):] - set_rule(multiworld.get_location(location.name, player), logic.shipping.can_ship(item_to_ship)) + 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, multiworld, player, world_options: StardewValleyOptions): +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 @@ -782,12 +890,12 @@ def set_cooksanity_rules(all_location_names: Set[str], logic: StardewLogic, mult recipe_name = location.name[len(cooksanity_prefix):] recipe = all_cooking_recipes_by_name[recipe_name] cook_rule = logic.cooking.can_cook(recipe) - set_rule(multiworld.get_location(location.name, player), cook_rule) + rule_collector.set_location_rule(location.name, cook_rule) -def set_chefsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): +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.option_none: + if chefsanity_option == Chefsanity.preset_none: return chefsanity_suffix = " Recipe" @@ -797,10 +905,10 @@ def set_chefsanity_rules(all_location_names: Set[str], logic: StardewLogic, mult recipe_name = location.name[:-len(chefsanity_suffix)] recipe = all_cooking_recipes_by_name[recipe_name] learn_rule = logic.cooking.can_learn_recipe(recipe.source) - set_rule(multiworld.get_location(location.name, player), learn_rule) + rule_collector.set_location_rule(location.name, learn_rule) -def set_craftsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): +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 @@ -818,213 +926,377 @@ def set_craftsanity_rules(all_location_names: Set[str], logic: StardewLogic, mul recipe_name = location.name[len(craft_prefix):] recipe = all_crafting_recipes_by_name[recipe_name] craft_rule = logic.crafting.can_craft(recipe) - set_rule(multiworld.get_location(location.name, player), craft_rule) + rule_collector.set_location_rule(location.name, craft_rule) -def set_booksanity_rules(logic: StardewLogic, multiworld, player, content: StardewContent): +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): - set_rule(multiworld.get_location(booksanity.to_location_name(book.name), player), logic.has(book.name)) + 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 - set_rule(multiworld.get_location(booksanity.to_location_name(book), player), logic.received(booksanity.progressive_lost_book, i)) + rule_collector.set_location_rule(booksanity.to_location_name(book), logic.received(booksanity.progressive_lost_book, i)) -def set_traveling_merchant_day_rules(logic: StardewLogic, multiworld: MultiWorld, player: int): +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}" - set_entrance_rule(multiworld, player, entrance_name, logic.received(item_for_day)) + rule_collector.set_entrance_rule(entrance_name, logic.received(item_for_day)) -def set_arcade_machine_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): +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: - set_entrance_rule(multiworld, player, Entrance.play_junimo_kart, play_junimo_kart_rule) + 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 - set_entrance_rule(multiworld, player, Entrance.play_junimo_kart, play_junimo_kart_rule & logic.has("Junimo Kart Small Buff")) - set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_2, logic.has("Junimo Kart Medium Buff")) - set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_3, logic.has("Junimo Kart Big Buff")) - set_entrance_rule(multiworld, player, Entrance.reach_junimo_kart_4, logic.has("Junimo Kart Max Buff")) - set_entrance_rule(multiworld, player, Entrance.play_journey_of_the_prairie_king, logic.has("JotPK Small Buff")) - set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_2, logic.has("JotPK Medium Buff")) - set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_3, logic.has("JotPK Big Buff")) - set_rule(multiworld.get_location("Journey of the Prairie King Victory", player), - logic.has("JotPK Max Buff")) + 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 -def set_friendsanity_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, content: StardewContent): + 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 - set_rule(multiworld.get_location("Spouse Stardrop", player), - logic.relationship.has_hearts_with_any_bachelor(13) & logic.relationship.can_get_married()) - set_rule(multiworld.get_location("Have a Baby", player), - logic.relationship.can_reproduce(1)) - set_rule(multiworld.get_location("Have Another Baby", player), - logic.relationship.can_reproduce(2)) + 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) - set_rule(multiworld.get_location(location_name, player), rule) + 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) - set_rule(multiworld.get_location(location_name, player), rule) + rule_collector.set_location_rule(location_name, rule) -def set_deepwoods_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - if ModNames.deepwoods in world_options.mods: - set_rule(multiworld.get_location("Breaking Up Deep Woods Gingerbread House", player), - logic.tool.has_tool(Tool.axe, "Gold")) - set_rule(multiworld.get_location("Chop Down a Deep Woods Iridium Tree", player), - logic.tool.has_tool(Tool.axe, "Iridium")) - set_entrance_rule(multiworld, player, DeepWoodsEntrance.use_woods_obelisk, logic.received("Woods Obelisk")) - for depth in range(10, 100 + 10, 10): - set_entrance_rule(multiworld, player, move_to_woods_depth(depth), logic.mod.deepwoods.can_chop_to_depth(depth)) - set_rule(multiworld.get_location("The Sword in the Stone", player), - logic.mod.deepwoods.can_pull_sword() & logic.mod.deepwoods.can_chop_to_depth(100)) - - -def set_magic_spell_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - if ModNames.magic not in world_options.mods: +def set_deepwoods_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent): + if not content.is_enabled(ModNames.deepwoods): return - set_rule(multiworld.get_location("Analyze: Clear Debris", player), - (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic"))) - set_rule(multiworld.get_location("Analyze: Till", player), - logic.tool.has_tool("Hoe", "Basic")) - set_rule(multiworld.get_location("Analyze: Water", player), - logic.tool.has_tool("Watering Can", "Basic")) - set_rule(multiworld.get_location("Analyze All Toil School Locations", player), - (logic.tool.has_tool("Watering Can", "Basic") & logic.tool.has_tool("Hoe", "Basic") - & (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic")))) - # Do I *want* to add boots into logic when you get them even in vanilla without effort? idk - set_rule(multiworld.get_location("Analyze: Evac", player), - logic.ability.can_mine_perfectly()) - set_rule(multiworld.get_location("Analyze: Haste", player), - logic.has("Coffee")) - set_rule(multiworld.get_location("Analyze: Heal", player), - logic.has("Life Elixir")) - set_rule(multiworld.get_location("Analyze All Life School Locations", player), - (logic.has("Coffee") & logic.has("Life Elixir") - & logic.ability.can_mine_perfectly())) - set_rule(multiworld.get_location("Analyze: Descend", player), - logic.region.can_reach(Region.mines)) - set_rule(multiworld.get_location("Analyze: Fireball", player), - logic.has("Fire Quartz")) - set_rule(multiworld.get_location("Analyze: Frostbolt", player), - logic.region.can_reach(Region.mines_floor_60) & logic.fishing.can_fish(85)) - set_rule(multiworld.get_location("Analyze All Elemental School Locations", player), - logic.has("Fire Quartz") & logic.region.can_reach(Region.mines_floor_60) & logic.fishing.can_fish(85)) - # set_rule(multiworld.get_location("Analyze: Lantern", player),) - set_rule(multiworld.get_location("Analyze: Tendrils", player), - logic.region.can_reach(Region.farm)) - set_rule(multiworld.get_location("Analyze: Shockwave", player), - logic.has("Earth Crystal")) - set_rule(multiworld.get_location("Analyze All Nature School Locations", player), - (logic.has("Earth Crystal") & logic.region.can_reach("Farm"))), - set_rule(multiworld.get_location("Analyze: Meteor", player), - (logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))), - set_rule(multiworld.get_location("Analyze: Lucksteal", player), - logic.region.can_reach(Region.witch_hut)) - set_rule(multiworld.get_location("Analyze: Bloodmana", player), - logic.region.can_reach(Region.mines_floor_100)) - set_rule(multiworld.get_location("Analyze All Eldritch School Locations", player), - (logic.region.can_reach(Region.witch_hut) & - logic.region.can_reach(Region.mines_floor_100) & - logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))) - set_rule(multiworld.get_location("Analyze Every Magic School Location", player), - (logic.tool.has_tool("Watering Can", "Basic") & logic.tool.has_tool("Hoe", "Basic") - & (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic")) & - logic.has("Coffee") & logic.has("Life Elixir") - & logic.ability.can_mine_perfectly() & logic.has("Earth Crystal") & - logic.has("Fire Quartz") & logic.fishing.can_fish(85) & - logic.region.can_reach(Region.witch_hut) & - logic.region.can_reach(Region.mines_floor_100) & - logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))) - - -def set_sve_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - if ModNames.sve not in world_options.mods: - return - set_entrance_rule(multiworld, player, SVEEntrance.forest_to_lost_woods, logic.bundle.can_complete_community_center) - set_entrance_rule(multiworld, player, SVEEntrance.enter_summit, logic.mod.sve.has_iridium_bomb()) - set_entrance_rule(multiworld, player, SVEEntrance.backwoods_to_grove, logic.mod.sve.has_any_rune()) - set_entrance_rule(multiworld, player, SVEEntrance.badlands_to_cave, logic.has("Aegis Elixir") | logic.combat.can_fight_at_level(Performance.maximum)) - set_entrance_rule(multiworld, player, SVEEntrance.forest_west_to_spring, logic.quest.can_complete_quest(Quest.magic_ink)) - set_entrance_rule(multiworld, player, SVEEntrance.railroad_to_grampleton_station, logic.received(SVEQuestItem.scarlett_job_offer)) - set_entrance_rule(multiworld, player, SVEEntrance.secret_woods_to_west, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) - set_entrance_rule(multiworld, player, SVEEntrance.grandpa_shed_to_interior, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) - set_entrance_rule(multiworld, player, SVEEntrance.aurora_warp_to_aurora, logic.received(SVERunes.nexus_aurora)) - set_entrance_rule(multiworld, player, SVEEntrance.farm_warp_to_farm, logic.received(SVERunes.nexus_farm)) - set_entrance_rule(multiworld, player, SVEEntrance.guild_warp_to_guild, logic.received(SVERunes.nexus_guild)) - set_entrance_rule(multiworld, player, SVEEntrance.junimo_warp_to_junimo, logic.received(SVERunes.nexus_junimo)) - set_entrance_rule(multiworld, player, SVEEntrance.spring_warp_to_spring, logic.received(SVERunes.nexus_spring)) - set_entrance_rule(multiworld, player, SVEEntrance.outpost_warp_to_outpost, logic.received(SVERunes.nexus_outpost)) - set_entrance_rule(multiworld, player, SVEEntrance.wizard_warp_to_wizard, logic.received(SVERunes.nexus_wizard)) - set_entrance_rule(multiworld, player, SVEEntrance.use_purple_junimo, logic.relationship.has_hearts(ModNPC.apples, 10)) - set_entrance_rule(multiworld, player, SVEEntrance.grandpa_interior_to_upstairs, logic.mod.sve.has_grandpa_shed_repaired()) - set_entrance_rule(multiworld, player, SVEEntrance.use_bear_shop, (logic.mod.sve.can_buy_bear_recipe())) - set_entrance_rule(multiworld, player, SVEEntrance.railroad_to_grampleton_station, logic.received(SVEQuestItem.scarlett_job_offer)) - set_entrance_rule(multiworld, player, SVEEntrance.museum_to_gunther_bedroom, logic.relationship.has_hearts(ModNPC.gunther, 2)) - set_entrance_rule(multiworld, player, 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: - set_rule(multiworld.get_location(location, player), - logic.registry.sve_location_rules[location]) - set_sve_ginger_island_rules(logic, multiworld, player, world_options) - set_boarding_house_rules(logic, multiworld, player, world_options) + 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_sve_ginger_island_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: +def set_magic_spell_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent): + if not content.is_enabled(ModNames.magic): return - set_entrance_rule(multiworld, player, SVEEntrance.summit_to_highlands, logic.mod.sve.has_marlon_boat()) - set_entrance_rule(multiworld, player, SVEEntrance.wizard_to_fable_reef, logic.received(SVEQuestItem.fable_reef_portal)) - set_entrance_rule(multiworld, player, SVEEntrance.highlands_to_cave, - logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron) & logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) - set_entrance_rule(multiworld, player, SVEEntrance.highlands_to_pond, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) - -def set_boarding_house_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): - if ModNames.boarding_house not in world_options.mods: + 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 - set_entrance_rule(multiworld, player, BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) - -def set_entrance_rule(multiworld, player, entrance: str, rule: StardewRule): - 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}") - multiworld.register_indirect_condition(multiworld.get_region(region, player), multiworld.get_entrance(entrance, player)) - - set_rule(multiworld.get_entrance(entrance, player), rule) - except KeyError as ex: - logger.error(f"""Failed to evaluate indirect connection in: {explain(rule, CollectionState(multiworld))}""") - raise ex + 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_island_entrance_rule(multiworld, player, entrance: str, rule: StardewRule, world_options: StardewValleyOptions): - if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: +def set_sve_ginger_island_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent): + if not content.is_enabled(ginger_island_content_pack): return - set_entrance_rule(multiworld, player, entrance, rule) + 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_many_island_entrances_rules(multiworld, player, entrance_rules: Dict[str, StardewRule], world_options: StardewValleyOptions): - if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: +def set_boarding_house_rules(logic: StardewLogic, rule_collector: StardewRuleCollector, content: StardewContent): + if not content.is_enabled(ModNames.boarding_house): return - for entrance, rule in entrance_rules.items(): - set_entrance_rule(multiworld, player, entrance, rule) + rule_collector.set_entrance_rule(BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) diff --git a/worlds/stardew_valley/scripts/export_locations.py b/worlds/stardew_valley/scripts/export_locations.py index c181faec7b94..ce38274d8a03 100644 --- a/worlds/stardew_valley/scripts/export_locations.py +++ b/worlds/stardew_valley/scripts/export_locations.py @@ -17,14 +17,16 @@ with open("output/stardew_valley_location_table.json", "w+") as f: locations = { "Cheat Console": - {"code": -1, "region": "Archipelago"}, + {"code": -1, "region": "Archipelago", "tags": [], "content_packs": []}, "Server": - {"code": -2, "region": "Archipelago"} + {"code": -2, "region": "Archipelago", "tags": [], "content_packs": []} } locations.update({ location.name: { "code": location.code, "region": location.region, + "tags": sorted([tag.name for tag in location.tags]), + "content_packs": sorted([pack for pack in location.content_packs]), } for location in location_table.values() if location.code is not None diff --git a/worlds/stardew_valley/scripts/update_data.py b/worlds/stardew_valley/scripts/update_data.py deleted file mode 100644 index 5c2e6a57a4db..000000000000 --- a/worlds/stardew_valley/scripts/update_data.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Update data script -This script can be used to assign new ids for the items and locations in the CSV file. It also regenerates the items -based on the resource packs. - -To run the script, use `python -m worlds.stardew_valley.scripts.update_data` from the repository root. -""" - -import csv -import itertools -from pathlib import Path -from typing import List - -from worlds.stardew_valley import LocationData -from worlds.stardew_valley.items import load_item_csv, Group, ItemData -from worlds.stardew_valley.locations import load_location_csv - -RESOURCE_PACK_CODE_OFFSET = 5000 -script_folder = Path(__file__) - - -def write_item_csv(items: List[ItemData]): - with open((script_folder.parent.parent / "data/items.csv").resolve(), "w", newline="") as file: - writer = csv.DictWriter(file, ["id", "name", "classification", "groups"]) - writer.writeheader() - for item in items: - item_dict = { - "id": item.code_without_offset, - "name": item.name, - "classification": item.classification.name, - "groups": ",".join(sorted(group.name for group in item.groups)) - } - writer.writerow(item_dict) - - -def write_location_csv(locations: List[LocationData]): - with open((script_folder.parent.parent / "data/locations.csv").resolve(), "w", newline="") as file: - write = csv.DictWriter(file, ["id", "region", "name", "tags", "mod_name"]) - write.writeheader() - for location in locations: - location_dict = { - "id": location.code_without_offset, - "name": location.name, - "region": location.region, - "tags": ",".join(sorted(group.name for group in location.tags)), - "mod_name": location.mod_name - } - write.writerow(location_dict) - - -if __name__ == "__main__": - loaded_items = load_item_csv() - - item_counter = itertools.count(max(item.code_without_offset - for item in loaded_items - if Group.RESOURCE_PACK not in item.groups - and item.code_without_offset is not None) + 1) - - resource_pack_counter = itertools.count(max(item.code_without_offset - for item in loaded_items - if Group.RESOURCE_PACK in item.groups - and item.code_without_offset is not None) + 1) - items_to_write = [] - for item in loaded_items: - if item.code_without_offset is None: - if Group.RESOURCE_PACK in item.groups: - new_code = next(resource_pack_counter) - else: - new_code = next(item_counter) - items_to_write.append(ItemData(new_code, item.name, item.classification, item.groups)) - continue - - items_to_write.append(item) - - write_item_csv(items_to_write) - - loaded_locations = load_location_csv() - location_counter = itertools.count(max(location.code_without_offset - for location in loaded_locations - if location.code_without_offset is not None) + 1) - locations_to_write = [] - for location in loaded_locations: - if location.code_without_offset is None: - locations_to_write.append( - LocationData(next(location_counter), location.region, location.name, location.mod_name, location.tags)) - continue - - locations_to_write.append(location) - - write_location_csv(locations_to_write) diff --git a/worlds/stardew_valley/stardew.ico b/worlds/stardew_valley/stardew.ico new file mode 100644 index 000000000000..2a6c6a3beb60 Binary files /dev/null and b/worlds/stardew_valley/stardew.ico differ diff --git a/worlds/stardew_valley/stardew.png b/worlds/stardew_valley/stardew.png new file mode 100644 index 000000000000..8ec396836f05 Binary files /dev/null and b/worlds/stardew_valley/stardew.png differ diff --git a/worlds/stardew_valley/stardew_rule/base.py b/worlds/stardew_valley/stardew_rule/base.py index ff1fbba37648..beb946a05ce3 100644 --- a/worlds/stardew_valley/stardew_rule/base.py +++ b/worlds/stardew_valley/stardew_rule/base.py @@ -251,10 +251,13 @@ def evaluate_while_simplifying_stateful(self, state): # If the queue is None, it means we have not start simplifying. Otherwise, we will continue simplification where we left. if local_state.rules_to_simplify is None: - rules_to_simplify = frozenset(local_state.original_simplifiable_rules) - if self.complement in rules_to_simplify: - return self.short_circuit_simplification() - local_state.rules_to_simplify = deque(rules_to_simplify) + try: + rules_to_simplify = frozenset(local_state.original_simplifiable_rules) + if self.complement in rules_to_simplify: + return self.short_circuit_simplification() + local_state.rules_to_simplify = deque(rules_to_simplify) + except Exception as err: + assert False, err # Start simplification where we left. while local_state.rules_to_simplify: @@ -432,8 +435,17 @@ def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRul def rules_count(self): return len(self.rules) + def __str__(self): + if all(value == 1 for value in self.counter.values()): + return f"Has {self.count} of [{', '.join(str(rule) for rule in self.counter.keys())}]" + + return f"Has {self.count} of [{', '.join(f'{value}x {str(rule)}' for rule, value in self.counter.items())}]" + def __repr__(self): - return f"Received {self.count} [{', '.join(f'{value}x {repr(rule)}' for rule, value in self.counter.items())}]" + if all(value == 1 for value in self.counter.values()): + return f"Has {self.count} of [{', '.join(repr(rule) for rule in self.counter.keys())}]" + + return f"Has {self.count} of [{', '.join(f'{value}x {repr(rule)}' for rule, value in self.counter.items())}]" @dataclass(frozen=True) diff --git a/worlds/stardew_valley/stardew_rule/indirect_connection.py b/worlds/stardew_valley/stardew_rule/indirect_connection.py index 17433f7df4a8..03dcb1f9a986 100644 --- a/worlds/stardew_valley/stardew_rule/indirect_connection.py +++ b/worlds/stardew_valley/stardew_rule/indirect_connection.py @@ -3,6 +3,8 @@ from . import StardewRule, Reach, Count, AggregatingStardewRule, Has +MAX_DEPTH = 100 + def look_for_indirect_connection(rule: StardewRule) -> Set[str]: required_regions = set() @@ -17,27 +19,27 @@ def _find(rule: StardewRule, regions: Set[str], depth: int): @_find.register def _(rule: AggregatingStardewRule, regions: Set[str], depth: int): - assert depth < 50, "Recursion depth exceeded" + assert depth < MAX_DEPTH, "Recursion depth exceeded" for r in rule.original_rules: _find(r, regions, depth + 1) @_find.register def _(rule: Count, regions: Set[str], depth: int): - assert depth < 50, "Recursion depth exceeded" + assert depth < MAX_DEPTH, "Recursion depth exceeded" for r in rule.rules: _find(r, regions, depth + 1) @_find.register def _(rule: Has, regions: Set[str], depth: int): - assert depth < 50, f"Recursion depth exceeded on {rule.item}" + assert depth < MAX_DEPTH, f"Recursion depth exceeded on {rule.item}" r = rule.other_rules[rule.item] _find(r, regions, depth + 1) @_find.register def _(rule: Reach, regions: Set[str], depth: int): - assert depth < 50, "Recursion depth exceeded" + assert depth < MAX_DEPTH, "Recursion depth exceeded" if rule.resolution_hint == "Region": regions.add(rule.spot) diff --git a/worlds/stardew_valley/stardew_rule/rule_explain.py b/worlds/stardew_valley/stardew_rule/rule_explain.py index 2e2b9c959d7f..8361c3c11483 100644 --- a/worlds/stardew_valley/stardew_rule/rule_explain.py +++ b/worlds/stardew_valley/stardew_rule/rule_explain.py @@ -1,5 +1,6 @@ from __future__ import annotations +import enum from dataclasses import dataclass, field from functools import cached_property, singledispatch from typing import Iterable, Set, Tuple, List, Optional @@ -9,12 +10,46 @@ from . import StardewRule, AggregatingStardewRule, Count, Has, TotalReceived, Received, Reach, true_ +class ExplainMode(enum.Enum): + VERBOSE = enum.auto() + CLIENT = enum.auto() + + +@dataclass +class MoreExplanation: + rule: StardewRule + state: CollectionState + more_index: int + mode: ExplainMode + + @cached_property + def result(self) -> bool: + try: + return self.rule(self.state) + except KeyError: + return False + + def summary(self, depth=0) -> str: + if self.mode is ExplainMode.CLIENT: + depth *= 2 + + line = " " * depth + f"{str(self.rule)} -> {self.result}" + line += f" [use `/more {self.more_index}` to explain]" + + return line + + def __str__(self, depth=0): + return self.summary(depth) + + @dataclass class RuleExplanation: rule: StardewRule state: CollectionState = field(repr=False, hash=False) - expected: bool + expected: bool | None + mode: ExplainMode sub_rules: Iterable[StardewRule] = field(default_factory=list) + more_explanations: List[StardewRule] = field(default_factory=list, repr=False, hash=False) explored_rules_key: Set[Tuple[str, str]] = field(default_factory=set, repr=False, hash=False) current_rule_explored: bool = False @@ -25,19 +60,25 @@ def __post_init__(self): self.sub_rules = [] def summary(self, depth=0) -> str: - summary = " " * depth + f"{str(self.rule)} -> {self.result}" + if self.mode is ExplainMode.CLIENT: + depth *= 2 + + line = " " * depth + f"{str(self.rule)} -> {self.result}" if self.current_rule_explored: - summary += " [Already explained]" - return summary + line += " [Already explained]" + + return line def __str__(self, depth=0): if not self.sub_rules: return self.summary(depth) - return self.summary(depth) + "\n" + "\n".join(i.__str__(depth + 1) - if i.result is not self.expected else i.summary(depth + 1) + return self.summary(depth) + "\n" + "\n".join(i.__str__(depth + 1) if self.expected is None or i.result is not self.expected else i.summary(depth + 1) for i in sorted(self.explained_sub_rules, key=lambda x: x.result)) + def more(self, more_index: int) -> RuleExplanation: + return explain(self.more_explanations[more_index], self.state, self.expected, self.mode) + @cached_property def result(self) -> bool: try: @@ -51,7 +92,21 @@ def explained_sub_rules(self) -> List[RuleExplanation]: if rule_key is not None: self.explored_rules_key.add(rule_key) - return [_explain(i, self.state, self.expected, self.explored_rules_key) for i in self.sub_rules] + if self.mode == ExplainMode.CLIENT: + sub_explanations = [] + for sub_rule in self.sub_rules: + if isinstance(sub_rule, Reach) and sub_rule.resolution_hint == 'Entrance': + sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations), self.mode)) + self.more_explanations.append(sub_rule) + elif isinstance(sub_rule, Reach) and sub_rule.resolution_hint == 'Location': + sub_explanations.append(MoreExplanation(sub_rule, self.state, len(self.more_explanations), self.mode)) + self.more_explanations.append(sub_rule) + else: + sub_explanations.append(_explain(sub_rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key)) + + return sub_explanations + + return [_explain(sub_rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key) for sub_rule in self.sub_rules] @dataclass @@ -60,9 +115,13 @@ class CountSubRuleExplanation(RuleExplanation): @staticmethod def from_explanation(expl: RuleExplanation, count: int) -> CountSubRuleExplanation: - return CountSubRuleExplanation(expl.rule, expl.state, expl.expected, expl.sub_rules, expl.explored_rules_key, expl.current_rule_explored, count) + return CountSubRuleExplanation(expl.rule, expl.state, expl.expected, expl.mode, expl.sub_rules, more_explanations=expl.more_explanations, + explored_rules_key=expl.explored_rules_key, current_rule_explored=expl.current_rule_explored, count=count) def summary(self, depth=0) -> str: + if self.mode is ExplainMode.CLIENT: + depth *= 2 + summary = " " * depth + f"{self.count}x {str(self.rule)} -> {self.result}" if self.current_rule_explored: summary += " [Already explained]" @@ -75,49 +134,61 @@ class CountExplanation(RuleExplanation): @cached_property def explained_sub_rules(self) -> List[RuleExplanation]: + if all(value == 1 for value in self.rule.counter.values()): + return super().explained_sub_rules + return [ - CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.explored_rules_key), count) + CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.mode, self.more_explanations, self.explored_rules_key), + count) for rule, count in self.rule.counter.items() ] -def explain(rule: CollectionRule, state: CollectionState, expected: bool = True) -> RuleExplanation: +def explain(rule: CollectionRule, state: CollectionState, expected: bool | None = True, mode: ExplainMode = ExplainMode.VERBOSE) -> RuleExplanation: if isinstance(rule, StardewRule): - return _explain(rule, state, expected, explored_spots=set()) + return _explain(rule, state, expected, mode, more_explanations=list(), explored_spots=set()) else: return f"Value of rule {str(rule)} was not {str(expected)} in {str(state)}" # noqa @singledispatch -def _explain(rule: StardewRule, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) +def _explain(rule: StardewRule, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, rule.original_rules, explored_rules_key=explored_spots) +def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return RuleExplanation(rule, state, expected, mode, rule.original_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Count, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return CountExplanation(rule, state, expected, rule.rules, explored_rules_key=explored_spots) +def _(rule: Count, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return CountExplanation(rule, state, expected, mode, rule.rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Has, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: +def _(rule: Has, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: try: - return RuleExplanation(rule, state, expected, [rule.other_rules[rule.item]], explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, [rule.other_rules[rule.item]], more_explanations=more_explanations, + explored_rules_key=explored_spots) except KeyError: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: TotalReceived, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: - return RuleExplanation(rule, state, expected, [Received(i, rule.player, 1) for i in rule.items], explored_rules_key=explored_spots) +def _(rule: TotalReceived, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: + return RuleExplanation(rule, state, expected, mode, [Received(i, rule.player, 1) for i in rule.items], more_explanations=more_explanations, + explored_rules_key=explored_spots) @_explain.register -def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: +def _(rule: Reach, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: access_rules = None if rule.resolution_hint == 'Location': spot = state.multiworld.get_location(rule.spot, rule.player) @@ -135,6 +206,10 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T elif rule.resolution_hint == 'Entrance': spot = state.multiworld.get_entrance(rule.spot, rule.player) + if isinstance(spot.access_rule, StardewRule): + if spot.access_rule is not true_: + access_rules = [spot.access_rule] + if isinstance(spot.access_rule, StardewRule): if spot.access_rule is true_: access_rules = [Reach(spot.parent_region.name, "Region", rule.player)] @@ -149,13 +224,14 @@ def _(rule: Reach, state: CollectionState, expected: bool, explored_spots: Set[T access_rules = [*(Reach(e.name, "Entrance", rule.player) for e in spot.entrances)] if not access_rules: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) - return RuleExplanation(rule, state, expected, access_rules, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, access_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @_explain.register -def _(rule: Received, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: +def _(rule: Received, state: CollectionState, expected: bool | None, mode: ExplainMode, more_explanations: list[StardewRule], + explored_spots: Set[Tuple[str, str]]) -> RuleExplanation: access_rules = None if rule.event: try: @@ -168,9 +244,9 @@ def _(rule: Received, state: CollectionState, expected: bool, explored_spots: Se pass if not access_rules: - return RuleExplanation(rule, state, expected, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, more_explanations=more_explanations, explored_rules_key=explored_spots) - return RuleExplanation(rule, state, expected, access_rules, explored_rules_key=explored_spots) + return RuleExplanation(rule, state, expected, mode, access_rules, more_explanations=more_explanations, explored_rules_key=explored_spots) @singledispatch diff --git a/worlds/stardew_valley/stardew_rule/state.py b/worlds/stardew_valley/stardew_rule/state.py index d60f08ac4c94..3fe294a3baf5 100644 --- a/worlds/stardew_valley/stardew_rule/state.py +++ b/worlds/stardew_valley/stardew_rule/state.py @@ -1,14 +1,11 @@ from dataclasses import dataclass -from typing import Iterable, Union, List, Tuple, Hashable, TYPE_CHECKING +from typing import Iterable, Union, List, Tuple, Hashable from BaseClasses import CollectionState from .base import BaseStardewRule, CombinableStardewRule from .protocol import StardewRule from ..strings.ap_names.event_names import Event -if TYPE_CHECKING: - from .. import StardewValleyWorld - class TotalReceived(BaseStardewRule): count: int @@ -18,10 +15,10 @@ class TotalReceived(BaseStardewRule): def __init__(self, count: int, items: Union[str, Iterable[str]], player: int): items_list: List[str] - if isinstance(items, Iterable): - items_list = [*items] - else: + if isinstance(items, str): items_list = [items] + else: + items_list = [*items] self.player = player self.items = items_list diff --git a/worlds/stardew_valley/strings/animal_product_names.py b/worlds/stardew_valley/strings/animal_product_names.py index 1b7490a60756..d0928f75b5a4 100644 --- a/worlds/stardew_valley/strings/animal_product_names.py +++ b/worlds/stardew_valley/strings/animal_product_names.py @@ -1,5 +1,6 @@ class AnimalProduct: any_egg = "Any Egg" + any_milk = "Any Milk" brown_egg = "Egg (Brown)" chicken_egg = "Chicken Egg" cow_milk = "Cow Milk" @@ -38,10 +39,13 @@ class AnimalProduct: slime_egg_red = "Red Slime Egg" slime_egg_tiger = "Tiger Slime Egg" squid_ink = "Squid Ink" - sturgeon_roe = "Sturgeon Roe" truffle = "Truffle" void_egg_starter = "Void Egg (Starter)" """This item does not really exist and should never end up being displayed. It's there to patch the loop in logic because of the Chicken-and-egg problem.""" void_egg = "Void Egg" wool = "Wool" + + @classmethod + def specific_roe(cls, fish: str) -> str: + return f"{cls.roe} [{fish}]" diff --git a/worlds/stardew_valley/strings/ap_names/ap_option_names.py b/worlds/stardew_valley/strings/ap_names/ap_option_names.py index 7ff2cc783d11..f4d9cc3944bb 100644 --- a/worlds/stardew_valley/strings/ap_names/ap_option_names.py +++ b/worlds/stardew_valley/strings/ap_names/ap_option_names.py @@ -17,3 +17,86 @@ class BuffOptionName: fishing_bar = "Fishing Bar Size" quality = "Quality" glow = "Glow" + + +class SecretsanityOptionName: + easy = "Easy" + difficult = "Difficult" + fishing = "Fishing" + secret_notes = "Secret Notes" + + +class EatsanityOptionName: + cooking = "Cooking" + crops = "Crops" + fish = "Fish" + artisan = "Artisan" + shop = "Shop" + poisonous = "Poisonous" + lock_effects = "Lock Effects" + + +class ChefsanityOptionName: + queen_of_sauce = "Queen of Sauce" + purchases = "Purchases" + skills = "Skills" + friendship = "Friendship" + + +class StartWithoutOptionName: + tools = "Tools" + backpack = "Backpack" + landslide = "Landslide" + community_center = "Community Center" + buildings = "Buildings" + + +class HatsanityOptionName: + tailoring = "Tailoring" + easy = "Easy" + medium = "Medium" + difficult = "Difficult" + rng = "RNG" + near_perfection = "Near Perfection" + post_perfection = "Post Perfection" + + +class AllowedFillerOptionName: + farming = "Farming Items" + fishing = "Fishing Items" + fruit_trees = "Fruit Trees" + food = "Food" + buff_food = "Buff Food" + consumables = "Consumables" + machines = "Machines" + storage = "Storage" + quality_of_life = "Quality Of Life" + materials = "Materials" + currencies = "Currencies" + money = "Money" + hats = "Hats" + decorations = "Decorations" + rings = "Rings" + + +class CustomLogicOptionName: + chair_skips = "Chair Skips" + easy_fishing = "Easy Fishing" + hard_fishing = "Hard Fishing" + extreme_fishing = "Extreme Fishing" + easy_mining = "Easy Mining" + hard_mining = "Hard Mining" + extreme_mining = "Extreme Mining" + easy_combat = "Easy Combat" + hard_combat = "Hard Combat" + extreme_combat = "Extreme Combat" + deep_mining = "Deep Mining" + very_deep_mining = "Very Deep Mining" + ignore_birthdays = "Ignore Birthdays" + easy_money = "Easy Money" + hard_money = "Hard Money" + extreme_money = "Extreme Money" + nightmare_money = "Nightmare Money" + bomb_hoeing = "Bomb Hoeing" + rain_watering = "Rain Watering" + critical_free_samples = "Critical Free Samples" diff --git a/worlds/stardew_valley/strings/ap_names/ap_weapon_names.py b/worlds/stardew_valley/strings/ap_names/ap_weapon_names.py index 7fcd6873761c..3c7a079e2f29 100644 --- a/worlds/stardew_valley/strings/ap_names/ap_weapon_names.py +++ b/worlds/stardew_valley/strings/ap_names/ap_weapon_names.py @@ -5,3 +5,5 @@ class APWeapon: dagger = "Progressive Dagger" slingshot = "Progressive Slingshot" footwear = "Progressive Footwear" + + all_weapons = (weapon, sword, club, dagger) diff --git a/worlds/stardew_valley/strings/ap_names/community_upgrade_names.py b/worlds/stardew_valley/strings/ap_names/community_upgrade_names.py index 6826b9234a30..04ca9a6c5d40 100644 --- a/worlds/stardew_valley/strings/ap_names/community_upgrade_names.py +++ b/worlds/stardew_valley/strings/ap_names/community_upgrade_names.py @@ -4,3 +4,10 @@ class CommunityUpgrade: mushroom_boxes = "Mushroom Boxes" movie_theater = "Progressive Movie Theater" mr_qi_plane_ride = "Mr Qi's Plane Ride" + + +class Bookseller: + days = "Progressive Bookseller Days" + stock_experience_books = "Bookseller Stock: Progressive Experience Books" + stock_rare_books = "Bookseller Stock: Progressive Rare Books" + stock_permanent_books = "Bookseller Stock: Permanent Books" diff --git a/worlds/stardew_valley/strings/ap_names/event_names.py b/worlds/stardew_valley/strings/ap_names/event_names.py index 68f000bdc316..43c098492473 100644 --- a/worlds/stardew_valley/strings/ap_names/event_names.py +++ b/worlds/stardew_valley/strings/ap_names/event_names.py @@ -10,5 +10,7 @@ class Event: victory = event("Victory") received_walnuts = event("Received Walnuts") + received_qi_gems = event("Received Qi Gems") + received_progressive_weapon = event("Received Progressive Weapon") received_progression_item = event("Received Progression Item") received_progression_percent = event("Received Progression Percent") diff --git a/worlds/stardew_valley/strings/ap_names/mods/mod_items.py b/worlds/stardew_valley/strings/ap_names/mods/mod_items.py index d87a81f5e51d..7327737292d8 100644 --- a/worlds/stardew_valley/strings/ap_names/mods/mod_items.py +++ b/worlds/stardew_valley/strings/ap_names/mods/mod_items.py @@ -59,3 +59,6 @@ class SVERunes: nexus_wizard = "Nexus: Wizard Runes" nexus_items: List[str] = [nexus_farm, nexus_wizard, nexus_spring, nexus_aurora, nexus_guild, nexus_junimo, nexus_outpost] + +class ModBooks: + digging_like_worms = "Digging Like Worms" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/ap_names/transport_names.py b/worlds/stardew_valley/strings/ap_names/transport_names.py index 7617a2387465..c44978760f22 100644 --- a/worlds/stardew_valley/strings/ap_names/transport_names.py +++ b/worlds/stardew_valley/strings/ap_names/transport_names.py @@ -1,4 +1,5 @@ class Transportation: + bus_repair = "Bus Repair" boat_repair = "Boat Repair" island_obelisk = "Island Obelisk" desert_obelisk = "Desert Obelisk" diff --git a/worlds/stardew_valley/strings/artisan_good_names.py b/worlds/stardew_valley/strings/artisan_good_names.py index 366189568cf7..fbe2fa70adb6 100644 --- a/worlds/stardew_valley/strings/artisan_good_names.py +++ b/worlds/stardew_valley/strings/artisan_good_names.py @@ -1,3 +1,6 @@ +from worlds.stardew_valley.strings.fish_names import Fish + + class ArtisanGood: honey = "Honey" oak_resin = "Oak Resin" @@ -61,6 +64,16 @@ def specific_smoked_fish(cls, fish: str) -> str: def specific_bait(cls, fish: str) -> str: return f"{cls.targeted_bait} [{fish}]" + @classmethod + def specific_aged_roe(cls, fish: str) -> str: + if fish == Fish.sturgeon: + return ArtisanGood.caviar + return f"{cls.aged_roe} [{fish}]" + + @classmethod + def specific_honey(cls, flower: str) -> str: + return f"{cls.honey} [{flower}]" + class ModArtisanGood: pterodactyl_egg = "Pterodactyl Egg" diff --git a/worlds/stardew_valley/strings/backpack_tiers.py b/worlds/stardew_valley/strings/backpack_tiers.py new file mode 100644 index 000000000000..f64935ed2f82 --- /dev/null +++ b/worlds/stardew_valley/strings/backpack_tiers.py @@ -0,0 +1,15 @@ +class Backpack: + small = "Small Pack" + large = "Large Pack" + deluxe = "Deluxe Pack" + premium = "Premium Pack" + prices_per_tier = {small: 400, large: 2000, deluxe: 10000, premium: 50000} + + @staticmethod + def get_purchasable_tiers(bigger_backpack: bool, start_without_backpack: bool = False) -> list[str]: + tiers = [Backpack.large, Backpack.deluxe] + if bigger_backpack: + tiers.append(Backpack.premium) + if start_without_backpack: + tiers.insert(0, Backpack.small) + return tiers diff --git a/worlds/stardew_valley/strings/book_names.py b/worlds/stardew_valley/strings/book_names.py index 6c271f42ae9c..1f5cb671f0da 100644 --- a/worlds/stardew_valley/strings/book_names.py +++ b/worlds/stardew_valley/strings/book_names.py @@ -41,7 +41,7 @@ class LostBook: tips_on_farming = lost_book("Tips on Farming") this_is_a_book_by_marnie = lost_book("This is a book by Marnie") on_foraging = lost_book("On Foraging") - the_fisherman_act_1 = lost_book("The Fisherman, Act 1") + the_fisherman_act_1 = lost_book("The Fisherman, Act I") how_deep_do_the_mines_go = lost_book("How Deep do the mines go?") an_old_farmers_journal = lost_book("An Old Farmer's Journal") scarecrows = lost_book("Scarecrows") diff --git a/worlds/stardew_valley/strings/boot_names.py b/worlds/stardew_valley/strings/boot_names.py new file mode 100644 index 000000000000..f7b0fb9f2ea0 --- /dev/null +++ b/worlds/stardew_valley/strings/boot_names.py @@ -0,0 +1,20 @@ +from typing import Dict, List + +boots_by_tier: Dict[int, List[str]] = dict() +tier_by_boots: Dict[str, int] = dict() + + +def create_boots(tier: int, name: str) -> str: + if tier not in boots_by_tier: + boots_by_tier[tier] = [] + boots_by_tier[tier].append(name) + tier_by_boots[name] = tier + return name + + +def tier_4_boots(name: str) -> str: + return create_boots(4, name) + + +class Boots: + mermaid_boots = tier_4_boots("Mermaid Boots") diff --git a/worlds/stardew_valley/strings/building_names.py b/worlds/stardew_valley/strings/building_names.py index 50c84b67ca5a..a1c3f77a4fd6 100644 --- a/worlds/stardew_valley/strings/building_names.py +++ b/worlds/stardew_valley/strings/building_names.py @@ -21,5 +21,14 @@ class Building: pet_bowl = "Pet Bowl" +class WizardBuilding: + earth_obelisk = "Earth Obelisk" + water_obelisk = "Water Obelisk" + desert_obelisk = "Desert Obelisk" + island_obelisk = "Island Obelisk" + junimo_hut = "Junimo Hut" + gold_clock = "Gold Clock" + + class ModBuilding: tractor_garage = "Tractor Garage" diff --git a/worlds/stardew_valley/strings/bundle_names.py b/worlds/stardew_valley/strings/bundle_names.py index 5f560a545434..842e0f3f5d44 100644 --- a/worlds/stardew_valley/strings/bundle_names.py +++ b/worlds/stardew_valley/strings/bundle_names.py @@ -10,99 +10,228 @@ class CCRoom: all_cc_bundle_names = [] +all_meme_bundle_names = [] -def cc_bundle(name: str) -> str: - all_cc_bundle_names.append(name) - return name +def name_bundle(name: str) -> str: + return f"{name} Bundle" + + +def register_bundle(bundle_name: str, is_meme: bool = False) -> str: + all_cc_bundle_names.append(bundle_name) + if is_meme: + all_meme_bundle_names.append(bundle_name) + return bundle_name + + +def cc_bundle(name: str, is_meme: bool = False) -> str: + full_name = name_bundle(name) + register_bundle(full_name, is_meme) + # print(full_name) + return full_name + + +def meme_bundle(name: str) -> str: + return cc_bundle(name, True) class BundleName: - spring_foraging = cc_bundle("Spring Foraging Bundle") - summer_foraging = cc_bundle("Summer Foraging Bundle") - fall_foraging = cc_bundle("Fall Foraging Bundle") - winter_foraging = cc_bundle("Winter Foraging Bundle") - construction = cc_bundle("Construction Bundle") - exotic_foraging = cc_bundle("Exotic Foraging Bundle") - beach_foraging = cc_bundle("Beach Foraging Bundle") - mines_foraging = cc_bundle("Mines Foraging Bundle") - desert_foraging = cc_bundle("Desert Foraging Bundle") - island_foraging = cc_bundle("Island Foraging Bundle") - sticky = cc_bundle("Sticky Bundle") - forest = cc_bundle("Forest Bundle") - green_rain = cc_bundle("Green Rain Bundle") - wild_medicine = cc_bundle("Wild Medicine Bundle") - quality_foraging = cc_bundle("Quality Foraging Bundle") - spring_crops = cc_bundle("Spring Crops Bundle") - summer_crops = cc_bundle("Summer Crops Bundle") - fall_crops = cc_bundle("Fall Crops Bundle") - quality_crops = cc_bundle("Quality Crops Bundle") - animal = cc_bundle("Animal Bundle") - artisan = cc_bundle("Artisan Bundle") - rare_crops = cc_bundle("Rare Crops Bundle") - fish_farmer = cc_bundle("Fish Farmer's Bundle") - garden = cc_bundle("Garden Bundle") - brewer = cc_bundle("Brewer's Bundle") - orchard = cc_bundle("Orchard Bundle") - island_crops = cc_bundle("Island Crops Bundle") - agronomist = cc_bundle("Agronomist's Bundle") - slime_farmer = cc_bundle("Slime Farmer Bundle") - sommelier = cc_bundle("Sommelier Bundle") - dry = cc_bundle("Dry Bundle") - river_fish = cc_bundle("River Fish Bundle") - lake_fish = cc_bundle("Lake Fish Bundle") - ocean_fish = cc_bundle("Ocean Fish Bundle") - night_fish = cc_bundle("Night Fishing Bundle") - crab_pot = cc_bundle("Crab Pot Bundle") - trash = cc_bundle("Trash Bundle") - recycling = cc_bundle("Recycling Bundle") - specialty_fish = cc_bundle("Specialty Fish Bundle") - spring_fish = cc_bundle("Spring Fishing Bundle") - summer_fish = cc_bundle("Summer Fishing Bundle") - fall_fish = cc_bundle("Fall Fishing Bundle") - winter_fish = cc_bundle("Winter Fishing Bundle") - rain_fish = cc_bundle("Rain Fishing Bundle") - quality_fish = cc_bundle("Quality Fish Bundle") - master_fisher = cc_bundle("Master Fisher's Bundle") - legendary_fish = cc_bundle("Legendary Fish Bundle") - island_fish = cc_bundle("Island Fish Bundle") - deep_fishing = cc_bundle("Deep Fishing Bundle") - tackle = cc_bundle("Tackle Bundle") - bait = cc_bundle("Master Baiter Bundle") - specific_bait = cc_bundle("Specific Fishing Bundle") - fish_smoker = cc_bundle("Fish Smoker Bundle") - blacksmith = cc_bundle("Blacksmith's Bundle") - geologist = cc_bundle("Geologist's Bundle") - adventurer = cc_bundle("Adventurer's Bundle") - treasure_hunter = cc_bundle("Treasure Hunter's Bundle") - engineer = cc_bundle("Engineer's Bundle") - demolition = cc_bundle("Demolition Bundle") - paleontologist = cc_bundle("Paleontologist's Bundle") - archaeologist = cc_bundle("Archaeologist's Bundle") - chef = cc_bundle("Chef's Bundle") - dye = cc_bundle("Dye Bundle") - field_research = cc_bundle("Field Research Bundle") - fodder = cc_bundle("Fodder Bundle") - enchanter = cc_bundle("Enchanter's Bundle") - children = cc_bundle("Children's Bundle") - forager = cc_bundle("Forager's Bundle") - home_cook = cc_bundle("Home Cook's Bundle") - helper = cc_bundle("Helper's Bundle") - spirit_eve = cc_bundle("Spirit's Eve Bundle") - winter_star = cc_bundle("Winter Star Bundle") - bartender = cc_bundle("Bartender's Bundle") - calico = cc_bundle("Calico Bundle") - raccoon = cc_bundle("Raccoon Bundle") - money_2500 = cc_bundle("2,500g Bundle") - money_5000 = cc_bundle("5,000g Bundle") - money_10000 = cc_bundle("10,000g Bundle") - money_25000 = cc_bundle("25,000g Bundle") - gambler = cc_bundle("Gambler's Bundle") - carnival = cc_bundle("Carnival Bundle") - walnut_hunter = cc_bundle("Walnut Hunter Bundle") - qi_helper = cc_bundle("Qi's Helper Bundle") + spring_foraging = cc_bundle("Spring Foraging") + summer_foraging = cc_bundle("Summer Foraging") + fall_foraging = cc_bundle("Fall Foraging") + winter_foraging = cc_bundle("Winter Foraging") + construction = cc_bundle("Construction") + exotic_foraging = cc_bundle("Exotic Foraging") + beach_foraging = cc_bundle("Beach Foraging") + mines_foraging = cc_bundle("Mines Foraging") + desert_foraging = cc_bundle("Desert Foraging") + island_foraging = cc_bundle("Island Foraging") + sticky = cc_bundle("Sticky") + forest = cc_bundle("Forest") + green_rain = cc_bundle("Green Rain") + totems = cc_bundle("Totems") + wild_medicine = cc_bundle("Wild Medicine") + quality_foraging = cc_bundle("Quality Foraging") + spring_crops = cc_bundle("Spring Crops") + summer_crops = cc_bundle("Summer Crops") + fall_crops = cc_bundle("Fall Crops") + quality_crops = cc_bundle("Quality Crops") + animal = cc_bundle("Animal") + artisan = cc_bundle("Artisan") + rare_crops = cc_bundle("Rare Crops") + fish_farmer = cc_bundle("Fish Farmer's") + garden = cc_bundle("Garden") + brewer = cc_bundle("Brewer's") + orchard = cc_bundle("Orchard") + island_crops = cc_bundle("Island Crops") + agronomist = cc_bundle("Agronomist's") + slime_farmer = cc_bundle("Slime Farmer") + sommelier = cc_bundle("Sommelier") + dry = cc_bundle("Dry") + river_fish = cc_bundle("River Fish") + lake_fish = cc_bundle("Lake Fish") + ocean_fish = cc_bundle("Ocean Fish") + night_fish = cc_bundle("Night Fishing") + crab_pot = cc_bundle("Crab Pot") + trash = cc_bundle("Trash") + recycling = cc_bundle("Recycling") + specialty_fish = cc_bundle("Specialty Fish") + spring_fish = cc_bundle("Spring Fishing") + summer_fish = cc_bundle("Summer Fishing") + fall_fish = cc_bundle("Fall Fishing") + winter_fish = cc_bundle("Winter Fishing") + rain_fish = cc_bundle("Rain Fishing") + quality_fish = cc_bundle("Quality Fish") + master_fisher = cc_bundle("Master Fisher's") + legendary_fish = cc_bundle("Legendary Fish") + island_fish = cc_bundle("Island Fish") + deep_fishing = cc_bundle("Deep Fishing") + tackle = cc_bundle("Tackle") + bait = cc_bundle("Master Baiter") + specific_bait = cc_bundle("Specific Fishing") + fish_smoker = cc_bundle("Fish Smoker") + blacksmith = cc_bundle("Blacksmith's") + geologist = cc_bundle("Geologist's") + adventurer = cc_bundle("Adventurer's") + treasure_hunter = cc_bundle("Treasure Hunter's") + engineer = cc_bundle("Engineer's") + demolition = cc_bundle("Demolition") + paleontologist = cc_bundle("Paleontologist's") + archaeologist = cc_bundle("Archaeologist's") + chef = cc_bundle("Chef's") + dye = cc_bundle("Dye") + field_research = cc_bundle("Field Research") + fodder = cc_bundle("Fodder") + enchanter = cc_bundle("Enchanter's") + children = cc_bundle("Children's") + forager = cc_bundle("Forager's") + home_cook = cc_bundle("Home Cook's") + helper = cc_bundle("Helper's") + spirit_eve = cc_bundle("Spirit's Eve") + winter_star = cc_bundle("Winter Star") + bartender = cc_bundle("Bartender's") + calico = cc_bundle("Calico") + raccoon = cc_bundle("Raccoon") + money_2500 = cc_bundle("2,500g") + money_5000 = cc_bundle("5,000g") + money_10000 = cc_bundle("10,000g") + money_25000 = cc_bundle("25,000g") + gambler = cc_bundle("Gambler's") + carnival = cc_bundle("Carnival") + walnut_hunter = cc_bundle("Walnut Hunter") + qi_helper = cc_bundle("Qi's Helper") missing_bundle = "The Missing Bundle" raccoon_fish = "Raccoon Fish" raccoon_artisan = "Raccoon Artisan" raccoon_food = "Raccoon Food" raccoon_foraging = "Raccoon Foraging" + + +class MemeBundleName: + reconnection = meme_bundle("Reconnection") + hint = meme_bundle("Hint") + # colored_crystals = meme_bundle("Colored Crystals") + catch_and_release = meme_bundle("Catch And Release") + pollution = meme_bundle("Pollution") + sacrifice = meme_bundle("Sacrifice") + dr_seuss = meme_bundle("Dr Seuss") + algorerhythm = meme_bundle("TheAlGoreRhythm") + distracted = meme_bundle("Distracted") + square_hole = meme_bundle("Square Hole") + hairy = meme_bundle("Hairy") + stanley = meme_bundle("Stanley") + very_sticky = meme_bundle("Very Sticky") + investment = meme_bundle("Investment") + doctor = meme_bundle("Doctor") + scam = meme_bundle("Scam") + blossom_garden = meme_bundle("Blossom Garden") + deathlink = meme_bundle("DeathLink") + humble = meme_bundle("Humble") + asmr = meme_bundle("ASMR") + puzzle = meme_bundle("Puzzle") + cooperation = meme_bundle("Cooperation") + pomnut = meme_bundle("Pomnut") + ministry_of_madness = meme_bundle("Ministry of Madness") + loser_club = meme_bundle("Loser Club") + frazzleduck = meme_bundle("Frazzleduck") + argonmatrix = meme_bundle("ArgonMatrix") + pool = meme_bundle("Pool") + AAAA = meme_bundle("AAAA") + amons_fall = meme_bundle("Amon's Fall") + animal_well = meme_bundle("ANIMAL WELL") + anything_for_beyonce = meme_bundle("Anything For Beyonce") + archipela_go = meme_bundle("Archipela-Go!") + automation = meme_bundle("Automation") + bad_farmer = meme_bundle("Bad Farmer") + bad_fisherman = meme_bundle("Bad Fisherman") + balls = meme_bundle("Balls") + big_grapes = meme_bundle("Big Grapes") + bun_dle = register_bundle("Bun-dle", True) + bundle = meme_bundle("Bundle") + burger_king = meme_bundle("Burger King") + burger_king_revenge = meme_bundle("Burger King's Revenge") + caffeinated = meme_bundle("Caffeinated") + cap = meme_bundle("Cap") + capitalist = meme_bundle("Capitalist's") + celeste = meme_bundle("Celeste") + chaos_emerald = meme_bundle("Chaos Emerald") + clickbait = meme_bundle("Clickbait") + clique = meme_bundle("Clique") + hibernation = meme_bundle("Hibernation") + commitment = meme_bundle("Commitment") + communism = meme_bundle("Communism") + connection = meme_bundle("Connection") + cookie_clicker = meme_bundle("Cookie Clicker") + crab_rave = meme_bundle("Crab Rave") + crap_pot = meme_bundle("Crap Pot") + crowdfunding = meme_bundle("Crowdfunding") + death = meme_bundle("Death") + doctor_angler = meme_bundle("Doctor Angler") + eg = meme_bundle("Eg") + emmalution = meme_bundle("Emmalution") + exhaustion = meme_bundle("Exhaustion") + fast = meme_bundle("Fast") + firstborn = meme_bundle("Firstborn") + flashbang = meme_bundle("Flashbang") + floor_is_lava = meme_bundle("The Floor Is Lava") + fruit = meme_bundle("'Fruit'") + gacha = meme_bundle("Gacha") + hats_off_to_you = meme_bundle("Hats Off To You") + honeywell = meme_bundle("Honeywell") + honorable = meme_bundle("Honorable") + hurricane_tortilla = meme_bundle("Hurricane Tortilla") + ikea = meme_bundle("IKEA") + joetg = meme_bundle("Joetg") + journalist = meme_bundle("Journalist's") + kent_c = meme_bundle("Kent C.") + legendairy = meme_bundle("Legendairy") + lemonade_stand = meme_bundle("Lemonade Stand") + look_at_chickens = meme_bundle("Look At These Chickens") + mermaid = meme_bundle("Mermaid") + minecraft = meme_bundle("Minecraft") + nft = meme_bundle("NFT") + not_the_bees = meme_bundle("Not The Bees") + obelisks = meme_bundle("Obelisks") + off_your_back = meme_bundle("Off Your Back") + permit_a38 = meme_bundle("Permit A38") + potato = meme_bundle("Potato") + restraint = meme_bundle("Restraint") + reverse = meme_bundle("Reverse") + rick = meme_bundle("Rick") + romance = meme_bundle("Romance") + sappy = meme_bundle("Sappy") + schrodinger = meme_bundle("Schrodinger's") + screw_you = meme_bundle("Screw You") + sisyphus = meme_bundle("Sisyphus") + smapi = meme_bundle("SMAPI") + snitch = meme_bundle("Snitch") + speedrunners = meme_bundle("Speedrunner's") + sunmaid = meme_bundle("Sunmaid") + this_is_fine = meme_bundle("This Is Fine") + tick_tock = meme_bundle("Tick Tock") + tilesanity = meme_bundle("Tilesanity") + trap = meme_bundle("Trap") + trout = meme_bundle("Trout") + vampire = meme_bundle("Vampire") + vocaloid = meme_bundle("Vocaloid") + what_the_rock_is_cooking = meme_bundle("What The Rock Is Cooking") diff --git a/worlds/stardew_valley/strings/catalogue_names.py b/worlds/stardew_valley/strings/catalogue_names.py new file mode 100644 index 000000000000..62373ec923a3 --- /dev/null +++ b/worlds/stardew_valley/strings/catalogue_names.py @@ -0,0 +1,97 @@ +from typing import Dict, List + + +class Catalogue: + wizard = "Wizard Catalogue" + furniture = "Furniture Catalogue" + + +items_by_catalogue: Dict[str, List[str]] = dict() + + +def catalogue_item(item: str, catalogue: str) -> str: + if catalogue not in items_by_catalogue: + items_by_catalogue[catalogue] = [] + items_by_catalogue[catalogue].append(item) + return item + + +def wizard_catalogue_item(item: str) -> str: + return catalogue_item(item, Catalogue.wizard) + + +def furniture_catalogue_item(item: str) -> str: + return catalogue_item(item, Catalogue.furniture) + + +class CatalogueItem: + wizard_bed = wizard_catalogue_item("Wizard Bed") + wizard_dresser = wizard_catalogue_item("Wizard Dresser") + wizard_chair = wizard_catalogue_item("Wizard Chair") + wizard_stool = wizard_catalogue_item("Wizard Stool") + wizard_table = wizard_catalogue_item("Wizard Table") + wizard_tea_table = wizard_catalogue_item("Wizard Tea Table") + wizard_end_table = wizard_catalogue_item("Wizard End Table") + wizard_bookcase = wizard_catalogue_item("Wizard Bookcase") + large_wizard_bookcase = wizard_catalogue_item("Large Wizard Bookcase") + short_wizard_bookcase = wizard_catalogue_item("Short Wizard Bookcase") + small_wizard_bookcase = wizard_catalogue_item("Small Wizard Bookcase") + wizard_study = wizard_catalogue_item("Wizard Study") + wizard_bookshelf = wizard_catalogue_item("Wizard Bookshelf") + elixir_shelf = wizard_catalogue_item("Elixir Shelf") + small_elixir_shelf = wizard_catalogue_item("Small Elixir Shelf") + stacked_elixir_shelf = wizard_catalogue_item("Stacked Elixir Shelf") + small_stacked_elixir_shelf = wizard_catalogue_item("Small Stacked Elixir Shelf") + elixir_table = wizard_catalogue_item("Elixir Table") + long_elixir_table = wizard_catalogue_item("Long Elixir Table") + two_elixirs = wizard_catalogue_item("Two Elixirs") + elixir_bundle = wizard_catalogue_item("Elixir Bundle") + cauldron = wizard_catalogue_item("Cauldron") + wizard_fireplace = wizard_catalogue_item("Wizard Fireplace") + crystal_ball = wizard_catalogue_item("Crystal Ball") + amethyst_crystal_ball = wizard_catalogue_item("Amethyst Crystal Ball") + aquamarine_crystal_ball = wizard_catalogue_item("Aquamarine Crystal Ball") + emerald_crystal_ball = wizard_catalogue_item("Emerald Crystal Ball") + ruby_crystal_ball = wizard_catalogue_item("Ruby Crystal Ball") + topaz_crystal_ball = wizard_catalogue_item("Topaz Crystal Ball") + blue_book = wizard_catalogue_item("Blue Book") + fallen_blue_book = wizard_catalogue_item("Fallen Blue Book") + brown_book = wizard_catalogue_item("Brown Book") + fallen_brown_book = wizard_catalogue_item("Fallen Brown Book") + green_book = wizard_catalogue_item("Green Book") + fallen_green_book = wizard_catalogue_item("Fallen Green Book") + purple_book = wizard_catalogue_item("Purple Book") + fallen_purple_book = wizard_catalogue_item("Fallen Purple Book") + red_book = wizard_catalogue_item("Red Book") + fallen_red_book = wizard_catalogue_item("Fallen Red Book") + yellow_book = wizard_catalogue_item("Yellow Book") + fallen_yellow_book = wizard_catalogue_item("Fallen Yellow Book") + book_pile = wizard_catalogue_item("Book Pile") + large_book_pile = wizard_catalogue_item("Large Book Pile") + small_book_pile = wizard_catalogue_item("Small Book Pile") + book_stack = wizard_catalogue_item("Book Stack") + large_book_stack = wizard_catalogue_item("Large Book Stack") + small_book_stack = wizard_catalogue_item("Small Book Stack") + decorative_wizard_door = wizard_catalogue_item("Decorative Wizard Door") + glyph = wizard_catalogue_item("Glyph") + runes = wizard_catalogue_item("'Runes'") + void_swirlds = wizard_catalogue_item("'Void Swirlds'") + wizards_tower = wizard_catalogue_item("'Wizard's Tower'") + witchs_broom = wizard_catalogue_item("Witch's Broom") + rune_rug = wizard_catalogue_item("Rune Rug") + starry_moon_rug = wizard_catalogue_item("Starry Moon Rug") + swirld_rug = wizard_catalogue_item("Swirld Rug") + wizard_cushion = wizard_catalogue_item("Wizard Cushion") + dark_wizard_cushion = wizard_catalogue_item("Dark Wizard Cushion") + wizard_lamp = wizard_catalogue_item("Wizard Lamp") + potted_red_mushroom = wizard_catalogue_item("Potted Red Mushroom") + curly_tree = wizard_catalogue_item("Curly Tree") + swamp_plant = wizard_catalogue_item("Swamp Plant") + stone_flooring = wizard_catalogue_item("Stone Flooring") + + stone_flooring = furniture_catalogue_item("Country Lamp") + box_lamp = furniture_catalogue_item("Box Lamp") + modern_lamp = furniture_catalogue_item("Modern Lamp") + classic_lamp = furniture_catalogue_item("Classic Lamp") + candle_lamp = furniture_catalogue_item("Candle Lamp") + ornate_lamp = furniture_catalogue_item("Ornate Lamp") diff --git a/worlds/stardew_valley/strings/craftable_names.py b/worlds/stardew_valley/strings/craftable_names.py index 891330c3ae51..e687f64287f5 100644 --- a/worlds/stardew_valley/strings/craftable_names.py +++ b/worlds/stardew_valley/strings/craftable_names.py @@ -63,6 +63,7 @@ class Fishing: curiosity_lure = "Curiosity Lure" deluxe_bait = "Deluxe Bait" challenge_bait = "Challenge Bait" + golden_bobber = "Golden Bobber" class Ring: @@ -121,10 +122,16 @@ class Lighting: class Furniture: + modern_lamp = "Modern Lamp" + candle_lamp = "Candle Lamp" tub_o_flowers = "Tub o' Flowers" wicked_statue = "Wicked Statue" flute_block = "Flute Block" drum_block = "Drum Block" + single_bed = "Single Bed" + cursed_mannequin = "Cursed Mannequin" + crane_game_house_plant = "House Plant 13 (Crane Game)" + exotic_double_bed = "Exotic Double Bed" class Storage: @@ -176,7 +183,7 @@ class ModEdible: class ModCraftable: travel_core = "Travel Core" glass_brazier = "Glass Brazier" - water_shifter = "Water Shifter" + water_sifter = "Water Sifter" rusty_brazier = "Rusty Brazier" glass_fence = "Glass Fence" bone_fence = "Bone Fence" diff --git a/worlds/stardew_valley/strings/currency_names.py b/worlds/stardew_valley/strings/currency_names.py index 21ccb5b55c58..1ffcdbf7a762 100644 --- a/worlds/stardew_valley/strings/currency_names.py +++ b/worlds/stardew_valley/strings/currency_names.py @@ -11,6 +11,28 @@ class Currency: @staticmethod def is_currency(item: str) -> bool: - return item in [Currency.qi_coin, Currency.golden_walnut, Currency.qi_gem, Currency.star_token, Currency.money] + return item in [Currency.qi_coin, Currency.golden_walnut, Currency.qi_gem, Currency.star_token, Currency.money, + MemeCurrency.code, MemeCurrency.clic, MemeCurrency.steps, MemeCurrency.time, MemeCurrency.energy, MemeCurrency.blood, + MemeCurrency.cookies, MemeCurrency.child, MemeCurrency.dead_crops, MemeCurrency.dead_pumpkins, MemeCurrency.missed_fish, + MemeCurrency.time_elapsed, MemeCurrency.honeywell, MemeCurrency.sleep_days, MemeCurrency.bank_money, MemeCurrency.deathlinks, + MemeCurrency.goat] +class MemeCurrency: + goat = "Goat" + deathlinks = "DeathLinks" + bank_money = "Bank Money" + sleep_days = "Sleep Days" + blood = "Blood" + child = "Child" + clic = "Clic" + code = "Code" + cookies = "CookieClics" + dead_crops = "Dead Crop" + dead_pumpkins = "Dead Pumpkin" + energy = "Energy" + steps = "Steps" + time = "Time" + time_elapsed = "Time Elapsed" + honeywell = "Honeywell" + missed_fish = "Missed Fish" diff --git a/worlds/stardew_valley/strings/entrance_names.py b/worlds/stardew_valley/strings/entrance_names.py index bad46c42947d..7b0d1834e5a0 100644 --- a/worlds/stardew_valley/strings/entrance_names.py +++ b/worlds/stardew_valley/strings/entrance_names.py @@ -66,6 +66,8 @@ class Entrance: town_to_alex_house = "Town to Alex's House" town_to_trailer = "Town to Trailer" town_to_mayor_manor = "Town to Mayor's Manor" + enter_lewis_bedroom = "Enter Lewis's Bedroom" + enter_shorts_maze = "Mayor's Manor to Purple Shorts Maze" town_to_sam_house = "Town to Sam's House" town_to_haley_house = "Town to Haley's House" town_to_sewer = "Town to Sewer" @@ -95,8 +97,10 @@ class Entrance: reach_junimo_kart_2 = "Reach Junimo Kart 2" reach_junimo_kart_3 = "Reach Junimo Kart 3" reach_junimo_kart_4 = "Reach Junimo Kart 4" - enter_locker_room = "Bathhouse Entrance to Locker Room" - enter_public_bath = "Locker Room to Public Bath" + enter_mens_locker_room = "Bathhouse Entrance to Men's Locker Room" + enter_womens_locker_room = "Bathhouse Entrance to Women's Locker Room" + mens_lockers_to_public_bath = "Men's Locker Room to Public Bath" + womens_lockers_to_public_bath = "Women's Locker Room to Public Bath" enter_witch_swamp = "Witch Warp Cave to Witch's Swamp" enter_witch_hut = "Witch's Swamp to Witch's Hut" witch_warp_to_wizard_basement = "Witch's Hut to Wizard Basement" @@ -106,6 +110,7 @@ class Entrance: enter_casino = "Oasis to Casino" enter_skull_cavern_entrance = "Desert to Skull Cavern Entrance" enter_skull_cavern = "Skull Cavern Entrance to Skull Cavern" + mine_in_skull_cavern = "Can Mine in Skull Cavern" mine_to_skull_cavern_floor_25 = dig_to_skull_floor(25) mine_to_skull_cavern_floor_50 = dig_to_skull_floor(50) mine_to_skull_cavern_floor_75 = dig_to_skull_floor(75) @@ -177,6 +182,16 @@ class Entrance: parrot_express_jungle_to_docks = "Parrot Express Jungle to Docks" parrot_express_dig_site_to_docks = "Parrot Express Dig Site to Docks" parrot_express_volcano_to_docks = "Parrot Express Volcano to Docks" + mountain_to_outside_adventure_guild = "Mountain to Outside Adventure Guild" + + forest_beach_shortcut = "Forest to Beach Shortcut" + mountain_jojamart_shortcut = "Mountain to Jojamart Shortcut" + mountain_town_shortcut = "Mountain to Town Shortcut" + town_tidepools_shortcut = "Town to Tide Pools Shortcut" + tunnel_backwoods_shortcut = "Tunnel to Backwoods Shortcut" + mountain_lake_to_outside_adventure_guild_shortcut = "Mountain Lake to Outside Adventure Guild" + + feed_trash_bear = "Feed Trash Bear" class LogicEntrance: @@ -231,15 +246,34 @@ def blacksmith_upgrade(material: str) -> str: attend_fair = "Attend Stardew Valley Fair" attend_spirit_eve = "Attend Spirit's Eve" attend_festival_of_ice = "Attend Festival of Ice" + buy_from_hat_mouse = "Buy From Hat Mouse" + buy_from_lost_items_shop = "Buy From Lost Items Shop" attend_night_market = "Attend Night Market" attend_winter_star = "Attend Feast of the Winter Star" attend_squidfest = "Attend SquidFest" - buy_experience_books = "Buy Experience Books from the bookseller" - buy_year1_books = "Buy Year 1 Books from the Bookseller" - buy_year3_books = "Buy Year 3 Books from the Bookseller" - complete_raccoon_requests = "Complete Raccoon Requests" - buy_from_raccoon = "Buy From Raccoon" + buy_books = "Buy from the bookseller" + buy_permanent_books = "Buy Permanent Books" + buy_rare_books = "Buy Rare Books" + buy_experience_books = "Buy Experience Books" + has_giant_stump = "Has Giant Stump" + can_complete_raccoon_requests_1 = "Can Complete Raccoon Request 1" + can_complete_raccoon_requests_2 = "Can Complete Raccoon Request 2" + can_complete_raccoon_requests_3 = "Can Complete Raccoon Request 3" + can_complete_raccoon_requests_4 = "Can Complete Raccoon Request 4" + can_complete_raccoon_requests_5 = "Can Complete Raccoon Request 5" + can_complete_raccoon_requests_6 = "Can Complete Raccoon Request 6" + can_complete_raccoon_requests_7 = "Can Complete Raccoon Request 7" + can_complete_raccoon_requests_8 = "Can Complete Raccoon Request 8" + buy_from_raccoon_1 = "Buy From Raccoon After 1 Request" + buy_from_raccoon_2 = "Buy From Raccoon After 2 Requests" + buy_from_raccoon_3 = "Buy From Raccoon After 3 Requests" + buy_from_raccoon_4 = "Buy From Raccoon After 4 Requests" + buy_from_raccoon_5 = "Buy From Raccoon After 5 Requests" + buy_from_raccoon_6 = "Buy From Raccoon After 6 Requests" fish_in_waterfall = "Fish In Waterfall" + find_secret_notes = "Find Secret Notes" + search_garbage_cans = "Search Garbage Cans" + purchase_wizard_blueprints = "Purchase Wizard Blueprints" # Skull Cavern Elevator diff --git a/worlds/stardew_valley/strings/festival_check_names.py b/worlds/stardew_valley/strings/festival_check_names.py index b59b3cd03f17..e6755a0aa6b2 100644 --- a/worlds/stardew_valley/strings/festival_check_names.py +++ b/worlds/stardew_valley/strings/festival_check_names.py @@ -16,7 +16,7 @@ class FestivalCheck: lupini_the_serpent = "Lupini: The Serpent" lupini_three_trees = "Lupini: Three Trees" lupini_tropical_fish = "Lupini: 'Tropical Fish #173'" - mermaid_pearl = "Mermaid Pearl" + mermaid_show = "Mermaid Show" moonlight_jellies = "Dance of the Moonlight Jellies" rarecrow_1 = "Rarecrow #1 (Turnip Head)" rarecrow_2 = "Rarecrow #2 (Witch)" diff --git a/worlds/stardew_valley/strings/flower_names.py b/worlds/stardew_valley/strings/flower_names.py index 7e708fc3c074..e07f82b06bf8 100644 --- a/worlds/stardew_valley/strings/flower_names.py +++ b/worlds/stardew_valley/strings/flower_names.py @@ -1,7 +1,15 @@ +all_flowers = [] + + +def flower(flower_name: str) -> str: + all_flowers.append(flower_name) + return flower_name + + class Flower: - blue_jazz = "Blue Jazz" - fairy_rose = "Fairy Rose" - poppy = "Poppy" - summer_spangle = "Summer Spangle" - sunflower = "Sunflower" - tulip = "Tulip" + blue_jazz = flower("Blue Jazz") + fairy_rose = flower("Fairy Rose") + poppy = flower("Poppy") + summer_spangle = flower("Summer Spangle") + sunflower = flower("Sunflower") + tulip = flower("Tulip") diff --git a/worlds/stardew_valley/strings/goal_names.py b/worlds/stardew_valley/strings/goal_names.py index 601b00510428..2eeea03ccb1b 100644 --- a/worlds/stardew_valley/strings/goal_names.py +++ b/worlds/stardew_valley/strings/goal_names.py @@ -13,5 +13,7 @@ class Goal: craft_master = "Craft Master" legend = "Legend" mystery_of_the_stardrops = "Mystery of the Stardrops" + mad_hatter = "Mad Hatter" + ultimate_foodie = "Ultimate Foodie" allsanity = "Allsanity" perfection = "Perfection" diff --git a/worlds/stardew_valley/strings/machine_names.py b/worlds/stardew_valley/strings/machine_names.py index d9e249a33594..382593278aa7 100644 --- a/worlds/stardew_valley/strings/machine_names.py +++ b/worlds/stardew_valley/strings/machine_names.py @@ -34,4 +34,6 @@ class Machine: anvil = "Anvil" mini_forge = "Mini-Forge" bait_maker = "Bait Maker" + sewing_machine = "Sewing Machine" + statue_endless_fortune = "Statue Of Endless Fortune" diff --git a/worlds/stardew_valley/strings/meme_item_names.py b/worlds/stardew_valley/strings/meme_item_names.py new file mode 100644 index 000000000000..8689f8e6dd27 --- /dev/null +++ b/worlds/stardew_valley/strings/meme_item_names.py @@ -0,0 +1,20 @@ +class MemeItem: + pot_of_gold = "PotOfGold" + seed_spot = "Seed Spot" + green_rain_weeds_0 = "GreenRainWeeds0" + lumber = "Lumber" + weeds = "Weeds" + twig = "Twig" + artifact_spot = "Artifact Spot" + warp_totem_qis_arena = "Warp Totem: Qi's Arena" + supply_crate = "SupplyCrate" + slime_crate = "Slime Crate" + decorative_pot = "Decorative Pot" + camping_stove = "Camping Stove" + worn_pants = "Worn Pants" + worn_left_ring = "Worn Left Ring" + worn_right_ring = "Worn Right Ring" + worn_shirt = "Worn Shirt" + worn_boots = "Worn Boots" + worn_hat = "Worn Hat" + trap = "Fun Trap" diff --git a/worlds/stardew_valley/strings/metal_names.py b/worlds/stardew_valley/strings/metal_names.py index 7efdc7ed338e..fa0d01843726 100644 --- a/worlds/stardew_valley/strings/metal_names.py +++ b/worlds/stardew_valley/strings/metal_names.py @@ -17,7 +17,7 @@ class Ore: iron = "Iron Ore" gold = "Gold Ore" iridium = "Iridium Ore" - radioactive = "Radioactive Bar" + radioactive = "Radioactive Ore" class MetalBar: @@ -26,58 +26,67 @@ class MetalBar: iron = "Iron Bar" gold = "Gold Bar" iridium = "Iridium Bar" - radioactive = "Radioactive Ore" + radioactive = "Radioactive Bar" class Mineral: - petrified_slime = "Petrified Slime" - quartz = "Quartz" + amethyst = "Amethyst" + any_gem = "Any Gem" + aquamarine = "Aquamarine" + diamond = "Diamond" earth_crystal = "Earth Crystal" + emerald = "Emerald" fire_quartz = "Fire Quartz" - marble = "Marble" - prismatic_shard = "Prismatic Shard" - diamond = "Diamond" frozen_tear = "Frozen Tear" - aquamarine = "Aquamarine" - topaz = "Topaz" + ghost_crystal = "Ghost Crystal" jade = "Jade" + jamborite = "Jamborite" + kyanite = "Kyanite" + lemon_stone = "Lemon Stone" + limestone = "Limestone" + marble = "Marble" + mudstone = "Mudstone" + obsidian = "Obsidian" + opal = "Opal" + petrified_slime = "Petrified Slime" + prismatic_shard = "Prismatic Shard" + quartz = "Quartz" ruby = "Ruby" - emerald = "Emerald" - amethyst = "Amethyst" + thunder_egg = "Thunder Egg" tigerseye = "Tigerseye" + topaz = "Topaz" class Artifact: - prehistoric_handaxe = "Prehistoric Handaxe" - dwarf_gadget = artifact("Dwarf Gadget") - ancient_seed = artifact("Ancient Seed") - glass_shards = artifact("Glass Shards") - rusty_cog = artifact("Rusty Cog") - rare_disc = artifact("Rare Disc") + anchor = artifact("Anchor") ancient_doll = artifact("Ancient Doll") ancient_drum = artifact("Ancient Drum") + ancient_seed = artifact("Ancient Seed") ancient_sword = artifact("Ancient Sword") arrowhead = artifact("Arrowhead") bone_flute = artifact("Bone Flute") chewing_stick = artifact("Chewing Stick") chicken_statue = artifact("Chicken Statue") - anchor = artifact("Anchor") chipped_amphora = artifact("Chipped Amphora") + dwarf_gadget = artifact("Dwarf Gadget") dwarf_scroll_i = artifact("Dwarf Scroll I") dwarf_scroll_ii = artifact("Dwarf Scroll II") dwarf_scroll_iii = artifact("Dwarf Scroll III") dwarf_scroll_iv = artifact("Dwarf Scroll IV") dwarvish_helm = artifact("Dwarvish Helm") elvish_jewelry = artifact("Elvish Jewelry") + glass_shards = artifact("Glass Shards") golden_mask = artifact("Golden Mask") golden_relic = artifact("Golden Relic") ornamental_fan = artifact("Ornamental Fan") - prehistoric_hammer = artifact("Prehistoric Handaxe") + prehistoric_handaxe = artifact("Prehistoric Handaxe") prehistoric_tool = artifact("Prehistoric Tool") + rare_disc = artifact("Rare Disc") + rusty_cog = artifact("Rusty Cog") rusty_spoon = artifact("Rusty Spoon") rusty_spur = artifact("Rusty Spur") - strange_doll_green = artifact("Strange Doll (Green)") strange_doll = artifact("Strange Doll") + strange_doll_green = artifact("Strange Doll (Green)") class Fossil: @@ -94,13 +103,12 @@ class Fossil: mummified_frog = fossil("Mummified Frog") nautilus_fossil = fossil("Nautilus Fossil") palm_fossil = fossil("Palm Fossil") - prehistoric_hand = fossil("Skeletal Hand") prehistoric_rib = fossil("Prehistoric Rib") prehistoric_scapula = fossil("Prehistoric Scapula") prehistoric_skull = fossil("Prehistoric Skull") prehistoric_tibia = fossil("Prehistoric Tibia") prehistoric_vertebra = fossil("Prehistoric Vertebra") - skeletal_hand = "Skeletal Hand" + skeletal_hand = fossil("Skeletal Hand") skeletal_tail = fossil("Skeletal Tail") snake_skull = fossil("Snake Skull") snake_vertebrae = fossil("Snake Vertebrae") @@ -108,37 +116,37 @@ class Fossil: class ModArtifact: - ancient_hilt = "Ancient Hilt" ancient_blade = "Ancient Blade" + ancient_doll_body = "Ancient Doll Body" + ancient_doll_legs = "Ancient Doll Legs" + ancient_hilt = "Ancient Hilt" + chipped_amphora_piece_1 = "Chipped Amphora Piece 1" + chipped_amphora_piece_2 = "Chipped Amphora Piece 2" mask_piece_1 = "Mask Piece 1" mask_piece_2 = "Mask Piece 2" mask_piece_3 = "Mask Piece 3" - ancient_doll_body = "Ancient Doll Body" - ancient_doll_legs = "Ancient Doll Legs" prismatic_shard_piece_1 = "Prismatic Shard Piece 1" prismatic_shard_piece_2 = "Prismatic Shard Piece 2" prismatic_shard_piece_3 = "Prismatic Shard Piece 3" prismatic_shard_piece_4 = "Prismatic Shard Piece 4" - chipped_amphora_piece_1 = "Chipped Amphora Piece 1" - chipped_amphora_piece_2 = "Chipped Amphora Piece 2" class ModFossil: - neanderthal_limb_bones = "Neanderthal Limb Bones" - neanderthal_ribs = "Neanderthal Ribs" - neanderthal_skull = "Neanderthal Skull" - neanderthal_pelvis = "Neanderthal Pelvis" - dinosaur_tooth = "Dinosaur Tooth" - dinosaur_skull = "Dinosaur Skull" dinosaur_claw = "Dinosaur Claw" dinosaur_femur = "Dinosaur Femur" - dinosaur_ribs = "Dinosaur Ribs" dinosaur_pelvis = "Dinosaur Pelvis" + dinosaur_ribs = "Dinosaur Ribs" + dinosaur_skull = "Dinosaur Skull" + dinosaur_tooth = "Dinosaur Tooth" dinosaur_vertebra = "Dinosaur Vertebra" - pterodactyl_ribs = "Pterodactyl Ribs" - pterodactyl_skull = "Pterodactyl Skull" - pterodactyl_r_wing_bone = "Pterodactyl R Wing Bone" + neanderthal_limb_bones = "Neanderthal Limb Bones" + neanderthal_pelvis = "Neanderthal Pelvis" + neanderthal_ribs = "Neanderthal Ribs" + neanderthal_skull = "Neanderthal Skull" + pterodactyl_claw = "Pterodactyl Claw" pterodactyl_l_wing_bone = "Pterodactyl L Wing Bone" pterodactyl_phalange = "Pterodactyl Phalange" + pterodactyl_r_wing_bone = "Pterodactyl R Wing Bone" + pterodactyl_ribs = "Pterodactyl Ribs" + pterodactyl_skull = "Pterodactyl Skull" pterodactyl_vertebra = "Pterodactyl Vertebra" - pterodactyl_claw = "Pterodactyl Claw" diff --git a/worlds/stardew_valley/strings/monster_names.py b/worlds/stardew_valley/strings/monster_names.py index e995d563f059..59c0ddd60783 100644 --- a/worlds/stardew_valley/strings/monster_names.py +++ b/worlds/stardew_valley/strings/monster_names.py @@ -49,6 +49,8 @@ class Monster: royal_serpent = "Royal Serpent" magma_sprite = "Magma Sprite" magma_sparker = "Magma Sparker" + metal_head = "Metal Head" + haunted_skull = "Haunted Skull" class MonsterCategory: @@ -64,4 +66,7 @@ class MonsterCategory: pepper_rex = "Pepper Rex" serpents = "Serpents" magma_sprites = "Magma Sprites" + metal_heads = "Metal Heads" + none = "None" + diff --git a/worlds/stardew_valley/strings/quality_names.py b/worlds/stardew_valley/strings/quality_names.py index 740bb5a3efc2..2ddd86fee2df 100644 --- a/worlds/stardew_valley/strings/quality_names.py +++ b/worlds/stardew_valley/strings/quality_names.py @@ -57,7 +57,22 @@ def get_highest(qualities: List[str]) -> str: return ArtisanQuality.basic +class AnimalProductQuality: + basic = "Basic AnimalProduct" + silver = "Silver AnimalProduct" + gold = "Gold AnimalProduct" + iridium = "Iridium AnimalProduct" + + @staticmethod + def get_highest(qualities: List[str]) -> str: + for quality in animal_product_qualities_in_desc_order: + if quality in qualities: + return quality + return AnimalProductQuality.basic + + crop_qualities_in_desc_order = [CropQuality.iridium, CropQuality.gold, CropQuality.silver, CropQuality.basic] fish_qualities_in_desc_order = [FishQuality.iridium, FishQuality.gold, FishQuality.silver, FishQuality.basic] forage_qualities_in_desc_order = [ForageQuality.iridium, ForageQuality.gold, ForageQuality.silver, ForageQuality.basic] artisan_qualities_in_desc_order = [ArtisanQuality.iridium, ArtisanQuality.gold, ArtisanQuality.silver, ArtisanQuality.basic] +animal_product_qualities_in_desc_order = [AnimalProductQuality.iridium, AnimalProductQuality.gold, AnimalProductQuality.silver, AnimalProductQuality.basic] diff --git a/worlds/stardew_valley/strings/region_names.py b/worlds/stardew_valley/strings/region_names.py index 567f13158185..d9fe1a43e39c 100644 --- a/worlds/stardew_valley/strings/region_names.py +++ b/worlds/stardew_valley/strings/region_names.py @@ -1,3 +1,6 @@ +from .tool_names import ToolMaterial + + class Region: menu = "Menu" stardew_valley = "Stardew Valley" @@ -31,6 +34,7 @@ class Region: mines = "The Mines" skull_cavern_entrance = "Skull Cavern Entrance" skull_cavern = "Skull Cavern" + skull_cavern_mining = "Skull Cavern Mining" sewer = "Sewer" mutant_bug_lair = "Mutant Bug Lair" witch_swamp = "Witch's Swamp" @@ -106,8 +110,11 @@ class Region: boat_tunnel = "Boat Tunnel" tide_pools = "Tide Pools" bathhouse_entrance = "Bathhouse Entrance" - locker_room = "Locker Room" + mens_locker_room = "Men's Locker Room" + womens_locker_room = "Women's Locker Room" public_bath = "Public Bath" + lewis_bedroom = "Lewis's Bedroom" + purple_shorts_maze = "Purple Shorts Maze" jotpk_world_1 = "JotPK World 1" jotpk_world_2 = "JotPK World 2" jotpk_world_3 = "JotPK World 3" @@ -142,6 +149,8 @@ class Region: dangerous_mines_20 = "Dangerous Mines - Floor 20" dangerous_mines_60 = "Dangerous Mines - Floor 60" dangerous_mines_100 = "Dangerous Mines - Floor 100" + outside_adventure_guild = "Outside Adventure Guild" + trash_bear = "Trash Bear" class LogicRegion: @@ -164,10 +173,10 @@ class LogicRegion: def blacksmith_upgrade(material: str) -> str: return f"Blacksmith {material} Upgrades" - blacksmith_copper = blacksmith_upgrade("Copper") - blacksmith_iron = blacksmith_upgrade("Iron") - blacksmith_gold = blacksmith_upgrade("Gold") - blacksmith_iridium = blacksmith_upgrade("Iridium") + blacksmith_copper = blacksmith_upgrade(ToolMaterial.copper) + blacksmith_iron = blacksmith_upgrade(ToolMaterial.iron) + blacksmith_gold = blacksmith_upgrade(ToolMaterial.gold) + blacksmith_iridium = blacksmith_upgrade(ToolMaterial.iridium) spring_farming = "Spring Farming" summer_farming = "Summer Farming" @@ -190,11 +199,30 @@ def blacksmith_upgrade(material: str) -> str: winter_star = "Feast of the Winter Star" squidfest = "SquidFest" raccoon_daddy = "Raccoon Bundles" - raccoon_shop = "Raccoon Shop" - bookseller_1 = "Bookseller Experience Books" - bookseller_2 = "Bookseller Year 1 Books" - bookseller_3 = "Bookseller Year 3 Books" + raccoon_request_1 = "Raccoon Request 1" + raccoon_request_2 = "Raccoon Request 2" + raccoon_request_3 = "Raccoon Request 3" + raccoon_request_4 = "Raccoon Request 4" + raccoon_request_5 = "Raccoon Request 5" + raccoon_request_6 = "Raccoon Request 6" + raccoon_request_7 = "Raccoon Request 7" + raccoon_request_8 = "Raccoon Request 8" + raccoon_shop_1 = "Raccoon Shop After 1 Request" + raccoon_shop_2 = "Raccoon Shop After 2 Requests" + raccoon_shop_3 = "Raccoon Shop After 3 Requests" + raccoon_shop_4 = "Raccoon Shop After 4 Requests" + raccoon_shop_5 = "Raccoon Shop After 5 Requests" + raccoon_shop_6 = "Raccoon Shop After 6 Requests" + bookseller = "Bookseller" + bookseller_permanent = "Bookseller Permanent Books" + bookseller_rare = "Bookseller Rare Books" + bookseller_experience = "Bookseller Experience Books" forest_waterfall = "Waterfall" + secret_notes = "Secret Notes" + hat_mouse = "Hat Mouse" + lost_items_shop = "Lost Items Shop" + garbage_cans = "Garbage Cans" + wizard_blueprints = "Wizard Blueprints" class DeepWoodsRegion: diff --git a/worlds/stardew_valley/strings/ring_names.py b/worlds/stardew_valley/strings/ring_names.py new file mode 100644 index 000000000000..ab7f29425d8d --- /dev/null +++ b/worlds/stardew_valley/strings/ring_names.py @@ -0,0 +1,16 @@ +all_ring_names = [] + + +def ring(name: str) -> str: + all_ring_names.append(name) + return name + + +class Ring: + slime_charmer = ring("Slime Charmer Ring") + ring_of_yoba = ring("Ring of Yoba") + sturdy = ring("Sturdy Ring") + burglar = ring("Burglar's Ring") + iridium_band = ring("Iridium Band") + napalm = ring("Napalm Ring") + hot_java = ring("Hot Java Ring") \ No newline at end of file diff --git a/worlds/stardew_valley/strings/special_item_names.py b/worlds/stardew_valley/strings/special_item_names.py new file mode 100644 index 000000000000..b20f2eb0949f --- /dev/null +++ b/worlds/stardew_valley/strings/special_item_names.py @@ -0,0 +1,10 @@ +class SpecialItem: + lucky_purple_shorts = "Lucky Purple Shorts" + trimmed_purple_shorts = "Trimmed Lucky Purple Shorts" + far_away_stone = "Far Away Stone" + solid_gold_lewis = "Solid Gold Lewis" + advanced_tv_remote = "Advanced TV Remote" + + +class NotReallyAnItem: + death = "Death" diff --git a/worlds/stardew_valley/strings/tool_names.py b/worlds/stardew_valley/strings/tool_names.py index 761f50e0a9bb..4a83357c911f 100644 --- a/worlds/stardew_valley/strings/tool_names.py +++ b/worlds/stardew_valley/strings/tool_names.py @@ -7,7 +7,6 @@ class Tool: pan = "Pan" fishing_rod = "Fishing Rod" scythe = "Scythe" - golden_scythe = "Golden Scythe" class ToolMaterial: @@ -16,11 +15,36 @@ class ToolMaterial: iron = "Iron" gold = "Gold" iridium = "Iridium" - tiers = {0: basic, - 1: copper, - 2: iron, - 3: gold, - 4: iridium} + materials = [basic, copper, iron, gold, iridium] + tiers = { + 1: basic, + 2: copper, + 3: iron, + 4: gold, + 5: iridium + } + + +class FishingRod: + training = "Training" + bamboo = "Bamboo" + fiberglass = "Fiberglass" + iridium = "Iridium" + advanced_iridium = "Advanced Iridium" + tiers = { + 1: training, + 2: bamboo, + 3: fiberglass, + 4: iridium, + 5: advanced_iridium + } + material_to_tier = { + training: 1, + bamboo: 2, + fiberglass: 3, + iridium: 4, + advanced_iridium: 5 + } class APTool: @@ -29,4 +53,4 @@ class APTool: hoe = f"Progressive {Tool.hoe}" watering_can = f"Progressive {Tool.watering_can}" trash_can = f"Progressive {Tool.trash_can}" - fishing_rod = f"Progressive {Tool.fishing_rod}" \ No newline at end of file + fishing_rod = f"Progressive {Tool.fishing_rod}" diff --git a/worlds/stardew_valley/strings/trap_names.py b/worlds/stardew_valley/strings/trap_names.py new file mode 100644 index 000000000000..12185143e705 --- /dev/null +++ b/worlds/stardew_valley/strings/trap_names.py @@ -0,0 +1,32 @@ +all_traps = [] + + +def trap(name: str) -> str: + all_traps.append(name) + return name + + +class Trap: + trap("Burnt Trap") + trap("Darkness Trap") + trap("Frozen Trap") + trap("Jinxed Trap") + trap("Nauseated Trap") + trap("Slimed Trap") + trap("Weakness Trap") + trap("Taxes Trap") + trap("Random Teleport Trap") + trap("The Crows Trap") + trap("Monsters Trap") + trap("Debris Trap") + trap("Shuffle Trap") + trap("Pariah Trap") + trap("Drought Trap") + trap("Time Flies Trap") + trap("Babies Trap") + trap("Meow Trap") + trap("Bark Trap") + trap("Benjamin Budton Trap") + trap("Inflation Trap") + trap("Bomb Trap") + trap("Nudge Trap") diff --git a/worlds/stardew_valley/strings/tv_channel_names.py b/worlds/stardew_valley/strings/tv_channel_names.py index e01bd55e2c77..cf98aa2e14a4 100644 --- a/worlds/stardew_valley/strings/tv_channel_names.py +++ b/worlds/stardew_valley/strings/tv_channel_names.py @@ -1,2 +1,3 @@ class Channel: queen_of_sauce = "The Queen of Sauce" + sinister_signal = "Sinister Signal" diff --git a/worlds/stardew_valley/strings/wallet_item_names.py b/worlds/stardew_valley/strings/wallet_item_names.py index 743d1f0c0155..c029f0d563d9 100644 --- a/worlds/stardew_valley/strings/wallet_item_names.py +++ b/worlds/stardew_valley/strings/wallet_item_names.py @@ -10,3 +10,6 @@ class Wallet: club_card = "Club Card" mastery_of_the_five_ways = "Mastery Of The Five Ways" key_to_the_town = "Key To The Town" + magic_ink = "Magic Ink" + mens_locker_key = "Men's Locker Key" + womens_locker_key = "Women's Locker Key" diff --git a/worlds/stardew_valley/test/TestBackpack.py b/worlds/stardew_valley/test/TestBackpack.py index bccafd15e55f..feda16791a30 100644 --- a/worlds/stardew_valley/test/TestBackpack.py +++ b/worlds/stardew_valley/test/TestBackpack.py @@ -1,33 +1,45 @@ from .bases import SVTestBase -from .. import options +from .. import options, StartWithoutOptionName -class TestBackpackVanilla(SVTestBase): - options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla} +class TestBackpackBase(SVTestBase): + expected_starting_backpacks: int = 0 + expected_total_backpacks: int = 2 + + def test_has_correct_number_of_backpacks(self): + backpack_item_name = "Progressive Backpack" + item_names = [item.name for item in self.multiworld.get_items()] + precollected_items = [item.name for item in self.multiworld.precollected_items[self.player]] + if self.expected_total_backpacks <= 0: + self.assertNotIn(backpack_item_name, item_names) + else: + self.assertEqual(item_names.count(backpack_item_name), self.expected_total_backpacks - self.expected_starting_backpacks) + if self.expected_starting_backpacks <= 0: + self.assertNotIn(backpack_item_name, precollected_items) + else: + self.assertEqual(precollected_items.count(backpack_item_name), self.expected_starting_backpacks) - def test_no_backpack(self): - with self.subTest("no items"): - item_names = {item.name for item in self.multiworld.get_items()} - self.assertNotIn("Progressive Backpack", item_names) - with self.subTest("no locations"): - location_names = {location.name for location in self.multiworld.get_locations()} - self.assertNotIn("Large Pack", location_names) - self.assertNotIn("Deluxe Pack", location_names) +class TestBackpackVanilla(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla} + expected_total_backpacks = 0 + def test_no_backpacks(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) -class TestBackpackProgressive(SVTestBase): - options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive} - def test_backpack(self): - with self.subTest(check="has items"): - item_names = [item.name for item in self.multiworld.get_items()] - self.assertEqual(item_names.count("Progressive Backpack"), 2) +class TestBackpackProgressive(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive} + expected_total_backpacks = 2 - with self.subTest(check="has locations"): - location_names = {location.name for location in self.multiworld.get_locations()} - self.assertIn("Large Pack", location_names) - self.assertIn("Deluxe Pack", location_names) + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertIn("Large Pack", location_names) + self.assertIn("Deluxe Pack", location_names) class TestBackpackEarlyProgressive(TestBackpackProgressive): @@ -38,8 +50,164 @@ def run_default_tests(self) -> bool: # EarlyProgressive is default return False - def test_backpack(self): - super().test_backpack() + def test_has_correct_backpack_locations(self): + super().test_has_correct_backpack_locations() with self.subTest(check="is early"): self.assertIn("Progressive Backpack", self.multiworld.early_items[1]) + + +class TestBackpackSplit1(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_1} + expected_total_backpacks = 24 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + + +class TestBackpackSplit2(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_2} + expected_total_backpacks = 12 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 7): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + for i in range(7, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + + +class TestBackpackSplit4(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_4} + expected_total_backpacks = 6 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 4): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + for i in range(4, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + + +class TestBackpackSplit6(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_6} + expected_total_backpacks = 4 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 3): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + for i in range(3, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + + +class TestStartWithoutBackpackSize1(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_1, + options.StartWithout.internal_name: frozenset({StartWithoutOptionName.backpack})} + expected_starting_backpacks = 6 + expected_total_backpacks = 36 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 13): + self.assertIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + + +class TestStartWithoutBackpackSize3(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_3, + options.StartWithout.internal_name: frozenset({StartWithoutOptionName.backpack})} + expected_starting_backpacks = 2 + expected_total_backpacks = 12 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 5): + self.assertIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + for i in range(5, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + + +class TestStartWithoutBackpackSize4(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_4, + options.StartWithout.internal_name: frozenset({StartWithoutOptionName.backpack})} + expected_starting_backpacks = 2 + expected_total_backpacks = 9 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Small Pack", location_names) + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + for i in range(1, 4): + self.assertIn(f"Small Pack {i}", location_names) + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + for i in range(4, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + + +class TestStartWithoutBackpackSize12(TestBackpackBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, + options.StartWithout.internal_name: frozenset({StartWithoutOptionName.backpack})} + expected_starting_backpacks = 1 + expected_total_backpacks = 3 + + def test_has_correct_backpack_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertIn("Small Pack", location_names) + self.assertIn("Large Pack", location_names) + self.assertIn("Deluxe Pack", location_names) + for i in range(1, 13): + self.assertNotIn(f"Small Pack {i}", location_names) + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) diff --git a/worlds/stardew_valley/test/TestBeta7Logic.py b/worlds/stardew_valley/test/TestBeta7Logic.py new file mode 100644 index 000000000000..1fa8b2aed957 --- /dev/null +++ b/worlds/stardew_valley/test/TestBeta7Logic.py @@ -0,0 +1,293 @@ +from worlds.stardew_valley import BackpackProgression, ToolProgression, SeasonRandomization +from worlds.stardew_valley.mods.mod_data import ModNames +from worlds.stardew_valley.options import BackpackSize, Mods, QuestLocations, SkillProgression, Secretsanity, Museumsanity, Booksanity, Hatsanity, Cropsanity, \ + StartWithout +from worlds.stardew_valley.strings.ap_names.ap_option_names import SecretsanityOptionName, StartWithoutOptionName +from worlds.stardew_valley.test.bases import SVTestBase + + +class TestAvailableBackpacksSize1(SVTestBase): + options = { + SeasonRandomization: SeasonRandomization.option_disabled, + Cropsanity: Cropsanity.option_disabled, + BackpackProgression: BackpackProgression.option_progressive, + BackpackSize: 1, + StartWithout: frozenset({StartWithoutOptionName.backpack}), + Mods: frozenset({ModNames.big_backpack}), + } + + def test_can_purchase_correct_number_of_backpacks(self): + backpack_names = ["Small Pack", "Large Pack", "Deluxe Pack", "Premium Pack"] + backpack_location_names = [] + for backpack_name in backpack_names: + for i in range(1, 13): + backpack_location_names.append(f"{backpack_name} {i}") + + items_required = ["Shipping Bin", "Progressive Hoe", "Progressive Watering Can"] + for item in items_required: + self.collect(item) + self.collect_lots_of_money(0.15) + number_owned_backpacks = self.multiworld.state.prog_items[self.player]["Progressive Backpack"] + while number_owned_backpacks < 48: + number_available = number_owned_backpacks + 1 + with self.subTest(f"Can Purchase {number_available} backpacks when you own {number_owned_backpacks} backpacks"): + for i in range(0, len(backpack_location_names)): + if i < number_available: + self.assert_can_reach_location(backpack_location_names[i]) + else: + self.assert_cannot_reach_location(backpack_location_names[i]) + self.collect("Progressive Backpack") + number_owned_backpacks = self.multiworld.state.prog_items[self.player]["Progressive Backpack"] + + +class TestAvailableBackpacksSize4(SVTestBase): + options = { + SeasonRandomization: SeasonRandomization.option_disabled, + Cropsanity: Cropsanity.option_disabled, + BackpackProgression: BackpackProgression.option_progressive, + BackpackSize: 4, + StartWithout: frozenset({StartWithoutOptionName.backpack}), + Mods: frozenset({ModNames.big_backpack}), + } + + def test_can_purchase_correct_number_of_backpacks(self): + backpack_names = ["Small Pack", "Large Pack", "Deluxe Pack", "Premium Pack"] + backpack_location_names = [] + for backpack_name in backpack_names: + for i in range(1, 4): + backpack_location_names.append(f"{backpack_name} {i}") + + items_required = ["Shipping Bin", "Progressive Hoe", "Progressive Watering Can"] + for item in items_required: + self.collect(item) + self.collect_lots_of_money(0.15) + + number_owned_backpacks = self.multiworld.state.prog_items[self.player]["Progressive Backpack"] + while number_owned_backpacks * 4 < 48: + number_available = number_owned_backpacks + 1 + with self.subTest(f"Can Purchase {number_available} backpacks when you own {number_owned_backpacks} backpacks"): + for i in range(0, len(backpack_location_names)): + if i < number_available: + self.assert_can_reach_location(backpack_location_names[i]) + else: + self.assert_cannot_reach_location(backpack_location_names[i]) + self.collect("Progressive Backpack") + number_owned_backpacks = self.multiworld.state.prog_items[self.player]["Progressive Backpack"] + + +class TestBeachBridgeWithStartingToolsRequiresNothing(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: StartWithout.preset_none, + } + + def test_beach_bridge_requires_axe(self): + beach_bridge_location = "Beach Bridge Repair" + self.assert_can_reach_location(beach_bridge_location) + + +class TestBeachBridgeWithoutStartingToolsRequiresAxe(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.tools}), + } + + def test_beach_bridge_requires_axe(self): + beach_bridge_location = "Beach Bridge Repair" + self.assert_cannot_reach_location(beach_bridge_location) + self.collect("Progressive Axe") + self.assert_can_reach_location(beach_bridge_location) + + +class TestGrimReaperWithStartingToolsRequiresQuarryAndWeapon(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: StartWithout.preset_none, + } + + def test_grim_reaper_requires_two_weapons(self): + grim_reaper_location = "Grim Reaper Statue" + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Landslide Removed") + self.collect("Bridge Repair") + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Progressive Weapon") + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Progressive Weapon") + self.assert_can_reach_location(grim_reaper_location) + + +class TestGrimRepairWithoutStartingToolsRequiresQuarryAndPickaxeAndWeapon(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.tools}), + } + + def test_grim_reaper_requires_weapon_and_pickaxe(self): + grim_reaper_location = "Grim Reaper Statue" + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Mountain Shortcuts") + self.collect("Bridge Repair") + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Progressive Weapon") + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Progressive Weapon") + self.assert_cannot_reach_location(grim_reaper_location) + self.collect("Progressive Pickaxe") + self.assert_can_reach_location(grim_reaper_location) + + +class TestGatheringQuestsWithStartingToolsRequiresMinesAccess(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.landslide}), + QuestLocations: 7, + } + + def test_gathering_quest_requires_landslide(self): + gathering_location = "Help Wanted: Gathering 1" + self.assert_cannot_reach_location(gathering_location) + self.collect("Landslide Removed") + self.assert_can_reach_location(gathering_location) + + +class TestGatheringQuestsWithoutStartingToolsRequiresMinesAndAxeAndPickaxe(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.tools, StartWithoutOptionName.landslide}), + QuestLocations: 7, + } + + def test_gathering_quest_requires_landslide_axe_and_pickaxe(self): + gathering_location = "Help Wanted: Gathering 1" + axe = self.create_item("Progressive Axe") + self.assert_cannot_reach_location(gathering_location) + self.collect("Landslide Removed") + self.assert_cannot_reach_location(gathering_location) + self.collect(axe) + self.assert_cannot_reach_location(gathering_location) + self.collect("Progressive Pickaxe") + self.assert_can_reach_location(gathering_location) + self.remove(axe) + self.assert_cannot_reach_location(gathering_location) + + +class TestPrizeTicketAndHelpWanted(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.tools, StartWithoutOptionName.landslide}), + QuestLocations: 7, + Booksanity: Booksanity.option_all, + Hatsanity: Hatsanity.preset_all, + } + + def test_prize_tickets_requires_all_help_wanteds_help_wanted(self): + locations = ["Wear Sports Cap", "Wear Chicken Mask", "Wear Polka Bow"] # , "Read Friendship 101"] # Friendship 101's bookseller source messes this up + items_required = ["Shipping Bin", "Progressive Fishing Rod", "Spring", "Progressive Mine Elevator", "Progressive Hoe", "Progressive Watering Can"] + for item in items_required: + self.collect(item) + self.collect_lots_of_money(0.75) + items_to_test = [self.create_item(item) for item in ["Progressive Fishing Rod", "Landslide Removed", "Progressive Axe", + "Progressive Pickaxe", "Progressive Weapon"]] + self.collect(items_to_test) + for location in locations: + with self.subTest(f"{location} can be accessed with all help wanted items"): + self.assert_can_reach_location(location) + for item in items_to_test: + self.remove(item) + with self.subTest(f"{location} Requires {item.name}"): + self.assert_cannot_reach_location(location) + self.collect(item) + self.assert_can_reach_location(location) + + +class TestSecretFishingRequiresFishingLevelsForDistance(SVTestBase): + options = { + SkillProgression: SkillProgression.option_progressive_with_masteries, + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.tools}), + Secretsanity: frozenset([SecretsanityOptionName.fishing]), + } + + def test_pyramid_decal_requires_level_1(self): + pyramid_decal_location = "Fishing Secret: Pyramid Decal" + items_required = ["Progressive Fishing Rod", "Shipping Bin"] * 5 + for item in items_required: + self.collect(item) + items_to_test = [self.create_item(item) for item in ["Bus Repair", "Fishing Level"]] + self.collect(items_to_test) + self.assert_can_reach_location(pyramid_decal_location) + for item in items_to_test: + with self.subTest(f"{pyramid_decal_location} Requires {item.name}"): + self.remove(item) + self.assert_cannot_reach_location(pyramid_decal_location) + self.collect(item) + self.assert_can_reach_location(pyramid_decal_location) + + def test_foliage_print_requires_level_4(self): + foliage_print_location = "Fishing Secret: Foliage Print" + items_required = ["Progressive Fishing Rod", "Shipping Bin"] * 5 + items_required.extend(["Fishing Level"] * 3) + for item in items_required: + self.collect(item) + items_to_test = [self.create_item(item) for item in ["Boat Repair", "Island North Turtle", "Fishing Level"]] + self.collect(items_to_test) + self.assert_can_reach_location(foliage_print_location) + for item in items_to_test: + with self.subTest(f"I{foliage_print_location} Requires {item.name}"): + self.remove(item) + self.assert_cannot_reach_location(foliage_print_location) + self.collect(item) + self.assert_can_reach_location(foliage_print_location) + + def test_iridium_krobus_requires_level_15(self): + iridium_krobus_location = "Fishing Secret: Iridium Krobus" + items_required = ["Progressive Sword", "Progressive Pickaxe", "Progressive Footwear", "Combat Level", "Progressive House", "Landslide Removed", "Progressive Mine Elevator", + "Mining Level", "Progressive Watering Can", "Progressive Hoe", "Progressive Fishing Rod", "50 Qi Gems", "Shipping Bin"] * 10 + self.remove_one_by_name("Spring") + items_required.extend(["Summer", "Fall", "Winter"]) + items_required.extend(["Fishing Level"] * 9) + for item in items_required: + self.collect(item) + self.collect_lots_of_money(0.6) + groups_of_items_to_test = {"Quality Seafoam Pudding": [self.create_item(item) for item in + ["Fish Pond", "Qi Walnut Room", "Boat Repair", "Island West Turtle", "Fishing Level"]], + "Enchanted Rod and Seafoam Pudding": [self.create_item(item) for item in + ["Bus Repair", "Fish Pond", "Boat Repair", "Island North Turtle", "Fishing Level", + "Skull Key"]], + "Desert Chef and Escargot": [self.create_item(item) for item in + ["Bus Repair", "Fishing Level", "Garlic Seeds", "Spring"]], + } + for item_group_to_test in groups_of_items_to_test: + items_to_test = groups_of_items_to_test[item_group_to_test] + with self.subTest(f"{iridium_krobus_location} Requires {item_group_to_test}"): + self.collect(items_to_test) + self.assert_can_reach_location(iridium_krobus_location) + for item in items_to_test: + with self.subTest(f"{iridium_krobus_location} Requires {item_group_to_test} [{item.name}]"): + self.remove(item) + self.assert_cannot_reach_location(iridium_krobus_location) + self.collect(item) + self.assert_can_reach_location(iridium_krobus_location) + self.remove(items_to_test) + + +class TestArtifactSpotDonationsRequireHoe(SVTestBase): + options = { + ToolProgression: ToolProgression.option_progressive, + StartWithout: frozenset({StartWithoutOptionName.tools}), + Museumsanity: Museumsanity.option_all, + } + + def test_artifact_spot_requires_hoe(self): + artifact_spot_artifacts = ["Chipped Amphora", "Ancient Doll", "Rusty Spoon", "Chicken Statue", "Prehistoric Tool"] + self.collect_lots_of_money(0.5) + self.collect("Traveling Merchant Metal Detector", 2) + hoe = self.create_item("Progressive Hoe") + for artifact in artifact_spot_artifacts: + with self.subTest(f"Artifact: {artifact}"): + artifact_location = f"Museumsanity: {artifact}" + self.assert_cannot_reach_location(artifact_location) + self.collect(hoe) + self.assert_can_reach_location(artifact_location) + self.remove(hoe) diff --git a/worlds/stardew_valley/test/TestBundles.py b/worlds/stardew_valley/test/TestBundles.py index 5b70158f5f54..ccdafcc99b9a 100644 --- a/worlds/stardew_valley/test/TestBundles.py +++ b/worlds/stardew_valley/test/TestBundles.py @@ -1,10 +1,15 @@ import unittest +from typing import Dict, Optional -from .bases import SVTestBase -from .. import BundleRandomization -from ..data.bundle_data import all_bundle_items_except_money, quality_crops_items_thematic, quality_foraging_items, quality_fish_items -from ..options import BundlePlando -from ..strings.bundle_names import BundleName +from .bases import SVTestBase, SVTestCase +from .. import BundleRandomization, location_table +from ..bundles.bundle import Bundle +from ..data.bundles_data.bundle_data import all_bundle_items_except_money, quality_crops_items_thematic, \ + quality_foraging_items, quality_fish_items +from ..data.bundles_data.meme_bundles import all_cc_meme_bundles +from ..locations import LocationTags +from ..options import BundleWhitelist, BundleBlacklist, BundlePrice +from ..strings.bundle_names import BundleName, MemeBundleName, all_meme_bundle_names from ..strings.crop_names import Fruit from ..strings.quality_names import CropQuality, ForageQuality, FishQuality @@ -53,23 +58,24 @@ def test_quality_fish_have_correct_quality(self): class TestRemixedPlandoBundles(SVTestBase): - plando_bundles = {BundleName.money_2500, BundleName.money_5000, BundleName.money_10000, BundleName.gambler, BundleName.ocean_fish, - BundleName.lake_fish, BundleName.deep_fishing, BundleName.spring_fish, BundleName.legendary_fish, BundleName.bait} + whitelist_bundles = {BundleName.money_2500, BundleName.money_5000, BundleName.money_10000, BundleName.gambler, BundleName.ocean_fish, + BundleName.lake_fish, BundleName.deep_fishing, BundleName.spring_fish, BundleName.legendary_fish, BundleName.bait} + blacklist_bundles = {BundleName.spring_foraging, BundleName.quality_crops, BundleName.night_fish, BundleName.carnival, BundleName.dye, + BundleName.enchanter} options = { BundleRandomization: BundleRandomization.option_remixed, - BundlePlando: frozenset(plando_bundles) + BundleWhitelist: frozenset(whitelist_bundles), + BundleBlacklist: frozenset(blacklist_bundles), } def test_all_plando_bundles_are_there(self): location_names = {location.name for location in self.multiworld.get_locations()} - for bundle_name in self.plando_bundles: + for bundle_name in self.whitelist_bundles: with self.subTest(f"{bundle_name}"): self.assertIn(bundle_name, location_names) - self.assertNotIn(BundleName.money_25000, location_names) - self.assertNotIn(BundleName.carnival, location_names) - self.assertNotIn(BundleName.night_fish, location_names) - self.assertNotIn(BundleName.specialty_fish, location_names) - self.assertNotIn(BundleName.specific_bait, location_names) + for bundle_name in self.blacklist_bundles: + with self.subTest(f"{bundle_name}"): + self.assertNotIn(bundle_name, location_names) class TestRemixedAnywhereBundles(SVTestBase): @@ -77,9 +83,12 @@ class TestRemixedAnywhereBundles(SVTestBase): BundleName.lake_fish, BundleName.river_fish, BundleName.night_fish, BundleName.legendary_fish, BundleName.specialty_fish, BundleName.bait, BundleName.specific_bait, BundleName.crab_pot, BundleName.tackle, BundleName.quality_fish, BundleName.rain_fish, BundleName.master_fisher} + blacklist_bundles = {BundleName.spring_foraging, BundleName.quality_crops, BundleName.summer_foraging, BundleName.carnival, BundleName.dye, + BundleName.enchanter, BundleName.fall_crops, BundleName.artisan} options = { BundleRandomization: BundleRandomization.option_remixed_anywhere, - BundlePlando: frozenset(fish_bundle_names) + BundleWhitelist: frozenset(fish_bundle_names), + BundleBlacklist: frozenset(blacklist_bundles), } def test_all_plando_bundles_are_there(self): @@ -87,3 +96,119 @@ def test_all_plando_bundles_are_there(self): for bundle_name in self.fish_bundle_names: with self.subTest(f"{bundle_name}"): self.assertIn(bundle_name, location_names) + for bundle_name in self.blacklist_bundles: + with self.subTest(f"{bundle_name}"): + self.assertNotIn(bundle_name, location_names) + + +class TestMemeBundles(SVTestBase): + options = { + BundleRandomization.internal_name: BundleRandomization.option_meme, + BundlePrice.internal_name: BundlePrice.option_maximum, + } + + def test_can_complete_all_bundles_with_all_state(self): + self.collect_everything() + for location_name in self.get_real_location_names(): + if location_name not in location_table or LocationTags.MEME_BUNDLE not in location_table[location_name].tags: + continue + with self.subTest(f"{location_name}"): + self.assert_can_reach_location(location_name) + + def test_specific_bundles_are_not_affected_by_price(self): + all_bundles = [bundle for bundle_room in self.world.modified_bundles for bundle in bundle_room.bundles] + bundles_by_name = {bundle.name: bundle for bundle in all_bundles} + self.check_price(bundles_by_name, MemeBundleName.death, 1, 1, 1) + self.check_price(bundles_by_name, MemeBundleName.communism, 1, 1, 1) + self.check_price(bundles_by_name, MemeBundleName.amons_fall, 7, 7, 1) + self.check_price(bundles_by_name, MemeBundleName.rick, 1, 1, 1) + self.check_price(bundles_by_name, MemeBundleName.obelisks, 8, 8) + self.check_price(bundles_by_name, MemeBundleName.eg, 8, 2, 57) + self.check_price(bundles_by_name, MemeBundleName.chaos_emerald, 7, 7, 1) + self.check_price(bundles_by_name, MemeBundleName.honorable, 2, 1) + self.check_price(bundles_by_name, MemeBundleName.snitch, 1, 1, 1) + self.check_price(bundles_by_name, MemeBundleName.commitment, 4, 4) + self.check_price(bundles_by_name, MemeBundleName.journalist, 1, 1, 1) + self.check_price(bundles_by_name, MemeBundleName.off_your_back, 6, 6, 1) + self.check_price(bundles_by_name, MemeBundleName.sisyphus, 12, 12, 1) + + def check_price(self, bundles: Dict[str, Bundle], bundle_name: str, expected_items: int, expected_required_items: int, stack_amount: Optional[int] = None): + if bundle_name not in bundles: + return + with self.subTest(bundle_name): + bundle = bundles[bundle_name] + self.assertEqual(len(bundle.items), expected_items) + self.assertEqual(bundle.number_required, expected_required_items) + if stack_amount is not None: + for item in bundle.items: + self.assertEqual(item.amount, stack_amount) + + +class TestMemeBundleContent(SVTestCase): + def test_all_meme_bundles_are_included(self): + all_meme_bundles_in_cc = {bundle.name for bundle in all_cc_meme_bundles} + for meme_bundle_name in all_meme_bundle_names: + if meme_bundle_name == MemeBundleName.investment: + continue + with self.subTest(meme_bundle_name): + self.assertIn(meme_bundle_name, all_meme_bundles_in_cc) + + +class TestScamBundleWhitelist(SVTestBase): + options = { + BundleRandomization: BundleRandomization.option_meme, + BundleWhitelist: frozenset({MemeBundleName.scam}) + } + + def test_scam_bundle_is_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertIn(MemeBundleName.scam, location_names) + + def test_investment_bundle_is_not_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn(MemeBundleName.investment, location_names) + + +class TestInvestmentBundleWhitelist(SVTestBase): + options = { + BundleRandomization: BundleRandomization.option_meme, + BundleWhitelist: frozenset({MemeBundleName.investment}) + } + + def test_scam_bundle_is_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertIn(MemeBundleName.scam, location_names) + + def test_investment_bundle_is_not_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn(MemeBundleName.investment, location_names) + + +class TestScamBundleBlacklist(SVTestBase): + options = { + BundleRandomization: BundleRandomization.option_meme, + BundleBlacklist: frozenset({MemeBundleName.scam}) + } + + def test_scam_bundle_is_not_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn(MemeBundleName.scam, location_names) + + def test_investment_bundle_is_not_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn(MemeBundleName.investment, location_names) + + +class TestInvestmentBundleBlacklist(SVTestBase): + options = { + BundleRandomization: BundleRandomization.option_meme, + BundleBlacklist: frozenset({MemeBundleName.investment}) + } + + def test_scam_bundle_is_not_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn(MemeBundleName.scam, location_names) + + def test_investment_bundle_is_not_there(self): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn(MemeBundleName.investment, location_names) diff --git a/worlds/stardew_valley/test/TestCrops.py b/worlds/stardew_valley/test/TestCrops.py index bf8f4f719e8a..b72e917edde4 100644 --- a/worlds/stardew_valley/test/TestCrops.py +++ b/worlds/stardew_valley/test/TestCrops.py @@ -17,7 +17,7 @@ def test_need_greenhouse_for_cactus(self): self.multiworld.state.collect(self.create_item(Seed.cactus)) self.multiworld.state.collect(self.create_item(Building.shipping_bin)) - self.multiworld.state.collect(self.create_item(Transportation.desert_obelisk)) + self.multiworld.state.collect(self.create_item(Transportation.bus_repair)) self.assert_cannot_reach_location(harvest_cactus_fruit) self.multiworld.state.collect(self.create_item(Region.greenhouse)) diff --git a/worlds/stardew_valley/test/TestData.py b/worlds/stardew_valley/test/TestData.py index 86550705b917..fe1420bc18b2 100644 --- a/worlds/stardew_valley/test/TestData.py +++ b/worlds/stardew_valley/test/TestData.py @@ -1,8 +1,27 @@ import unittest +from collections import Counter +from typing import List +from .. import Group +from ..content.content_packs import all_content_pack_names from ..items import load_item_csv -from ..locations import load_location_csv -from ..options import Mods +from ..locations import load_location_csv, LocationTags +from ..strings.trap_names import all_traps + + +def print_lists_difference(list1: List[str], list2: List[str], list1_name: str = "List 1", list2_name: str = "List 2"): + for item in list1: + if item not in list2: + print(f"{item} is in {list1_name} but not in {list2_name}") + for item in list2: + if item not in list1: + print(f"{item} is in {list2_name} but not in {list1_name}") + + +def print_duplicates(items: List[str]): + for item in items: + if items.count(item) > 1: + print(f"{item} is in the list {items.count(item)} times") class TestCsvIntegrity(unittest.TestCase): @@ -16,18 +35,22 @@ def test_items_integrity(self): with self.subTest("Test all ids are unique"): all_ids = [item.code_without_offset for item in items] unique_ids = set(all_ids) - self.assertEqual(len(all_ids), len(unique_ids)) + self.assertEqual(len(all_ids), len(unique_ids), f"Some ids are duplicated: {[item for item, count in Counter(all_ids).items() if count > 1]}") with self.subTest("Test all names are unique"): all_names = [item.name for item in items] unique_names = set(all_names) self.assertEqual(len(all_names), len(unique_names)) - with self.subTest("Test all mod names are valid"): - mod_names = {item.mod_name for item in items} - for mod_name in mod_names: - if mod_name: - self.assertIn(mod_name, Mods.valid_keys) + with self.subTest("Test all content packs are valid"): + content_packs = {content_pack for item in items for content_pack in item.content_packs} + for content_pack in content_packs: + self.assertIn(content_pack, all_content_pack_names) + + with self.subTest("Test all traps are in string"): + traps = [item.name for item in items if Group.TRAP in item.groups and Group.DEPRECATED not in item.groups] + for trap in traps: + self.assertIn(trap, all_traps) def test_locations_integrity(self): locations = load_location_csv() @@ -47,7 +70,12 @@ def test_locations_integrity(self): self.assertEqual(len(all_names), len(unique_names)) with self.subTest("Test all mod names are valid"): - mod_names = {location.mod_name for location in locations} - for mod_name in mod_names: - if mod_name: - self.assertIn(mod_name, Mods.valid_keys) + content_packs = {content_pack for location in locations for content_pack in location.content_packs} + for content_pack in content_packs: + self.assertIn(content_pack, all_content_pack_names) + + with self.subTest("Test all craftsanity locations are either craft or recipe"): + for location in locations: + if LocationTags.CRAFTSANITY not in location.tags: + continue + self.assertTrue(LocationTags.CRAFTSANITY_CRAFT in location.tags or LocationTags.CRAFTSANITY_RECIPE in location.tags) diff --git a/worlds/stardew_valley/test/TestDynamicGoals.py b/worlds/stardew_valley/test/TestDynamicGoals.py index 23f453e9f073..11330a4a6897 100644 --- a/worlds/stardew_valley/test/TestDynamicGoals.py +++ b/worlds/stardew_valley/test/TestDynamicGoals.py @@ -22,11 +22,14 @@ def collect_fishing_abilities(tester: SVTestBase): tester.multiworld.state.collect(tester.world.create_item("Mining Level"), prevent_sweep=False) for i in range(17): tester.multiworld.state.collect(tester.world.create_item("Progressive Mine Elevator"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Shipping Bin"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Fishing Mastery"), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item("Landslide Removed"), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Spring"), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Summer"), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Fall"), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Winter"), prevent_sweep=False) - tester.multiworld.state.collect(tester.world.create_item(Transportation.desert_obelisk), prevent_sweep=False) + tester.multiworld.state.collect(tester.world.create_item(Transportation.bus_repair), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Beach Bridge"), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Railroad Boulder Removed"), prevent_sweep=False) tester.multiworld.state.collect(tester.world.create_item("Island North Turtle"), prevent_sweep=False) @@ -43,7 +46,7 @@ def create_and_collect_fishing_access_items(tester: SVTestBase) -> List[Tuple[St items = [(create_and_collect(tester, Wallet.dark_talisman), Fish.void_salmon), (create_and_collect(tester, Wallet.rusty_key), Fish.slimejack), (create_and_collect(tester, "Progressive Mine Elevator"), Fish.lava_eel), - (create_and_collect(tester, Transportation.island_obelisk), Fish.lionfish), + (create_and_collect(tester, Transportation.boat_repair), Fish.lionfish), (create_and_collect(tester, "Island Resort"), Fish.stingray)] return items diff --git a/worlds/stardew_valley/test/TestEatsanity.py b/worlds/stardew_valley/test/TestEatsanity.py new file mode 100644 index 000000000000..23ebe396ad45 --- /dev/null +++ b/worlds/stardew_valley/test/TestEatsanity.py @@ -0,0 +1,395 @@ +import unittest + +from .bases import SVTestBase +from .. import SeasonRandomization +from ..options import ExcludeGingerIsland, Eatsanity, Chefsanity, QuestLocations, Secretsanity +from ..strings.ap_names.ap_option_names import EatsanityOptionName, SecretsanityOptionName + + +class SVEatsanityTestBase(SVTestBase): + expected_eating_locations: set[str] = set() + unexpected_eating_locations: set[str] = set() + expected_eating_items: set[str] = set() + unexpected_eating_items: set[str] = set() + + @classmethod + def setUpClass(cls) -> None: + if cls is SVEatsanityTestBase: + raise unittest.SkipTest("Base tests disabled") + + super().setUpClass() + + def test_eatsanity_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for location in self.expected_eating_locations: + self.assertIn(location, location_names, f"{location} should be in the location names") + for location in self.unexpected_eating_locations: + self.assertNotIn(location, location_names, f"{location} should not be in the location names") + + def test_eatsanity_items(self): + item_names = {item.name for item in self.multiworld.get_items()} + for item in self.expected_eating_items: + self.assertIn(item, item_names, f"{item} should be in the item names") + for item in self.unexpected_eating_items: + self.assertNotIn(item, item_names, f"{item} should not be in the item names") + + +class TestEatsanityNone(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: Eatsanity.preset_none, + } + unexpected_eating_locations = { + "Eat Parsnip", + "Drink Parsnip", + "Eat Vinegar", + "Drink Vinegar", + "Eat Carp", + "Drink Pina Colada", + "Eat Pufferfish", + "Eat Tortilla", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + +class TestEatsanityCrops(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.crops}), + } + expected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + } + unexpected_eating_locations = { + "Drink Parsnip", + "Eat Vinegar", + "Drink Vinegar", + "Eat Carp", + "Drink Pina Colada", + "Eat Pufferfish", + "Eat Tortilla", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + def test_need_crop_to_eat_it(self): + crops = {"Apple": "Apple Sapling", "Yam": "Yam Seeds", "Parsnip": "Parsnip Seeds"} + self.collect("Fall") + self.collect("Spring") + self.collect("Shipping Bin") + self.collect_months(2) + for crop in crops: + with self.subTest(f"Need {crops[crop]} to eat {crop}"): + location = self.world.get_location(f"Eat {crop}") + self.assert_cannot_reach_location(location) + self.collect(crops[crop]) + self.assert_can_reach_location(location) + + +class TestEatsanityCooking(SVEatsanityTestBase): + options = { + SeasonRandomization: SeasonRandomization.option_randomized, + Chefsanity: Chefsanity.preset_all, + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.cooking}), + "start_inventory": {"Winter": 1} + } + expected_eating_locations = { + "Eat Vegetable Medley", + "Eat Tortilla", + "Eat Pepper Poppers", + } + unexpected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Drink Parsnip", + "Eat Vinegar", + "Drink Vinegar", + "Eat Carp", + "Drink Pina Colada", + "Eat Pufferfish", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + def test_need_recipe_and_ingredients_to_cook(self): + location = self.world.get_location(f"Eat Vegetable Medley") + required_items = ["Vegetable Medley Recipe", "Progressive House", "Tomato Seeds", "Summer", "Beet Seeds", "Fall", "Bus Repair"] + unique_items = list(set(required_items)) + required_items = [self.create_item(item) for item in required_items] + self.collect(required_items) + self.assert_can_reach_location(location) + for item_name in unique_items: + with self.subTest(f"Requires {item_name} to {location.name}"): + item_to_remove = next(item for item in required_items if item.name == item_name) + self.assert_can_reach_location(location) + self.remove(item_to_remove) + self.assert_cannot_reach_location(location) + self.collect(item_to_remove) + + +class TestEatsanityFish(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.fish}), + } + expected_eating_locations = { + "Eat Carp", + "Eat Bullhead", + } + unexpected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Drink Parsnip", + "Eat Vinegar", + "Drink Vinegar", + "Drink Pina Colada", + "Eat Pufferfish", + "Eat Tortilla", + "Eat Pepper Poppers", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + +class TestEatsanityArtisan(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.artisan}), + } + expected_eating_locations = { + "Drink Vinegar", + "Drink Wine", + } + unexpected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Drink Parsnip", + "Eat Vinegar", + "Drink Pina Colada", + "Eat Pufferfish", + "Eat Tortilla", + "Eat Pepper Poppers", + "Eat Carp", + "Eat Bullhead", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + +class TestEatsanityShop(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.shop}), + } + expected_eating_locations = { + "Drink Pina Colada", + "Eat Stardrop", + } + unexpected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Drink Parsnip", + "Drink Vinegar", + "Drink Wine", + "Eat Vinegar", + "Eat Pufferfish", + "Eat Tortilla", + "Eat Pepper Poppers", + "Eat Carp", + "Eat Bullhead", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + def test_need_stardrop_to_eat_it(self): + location = self.world.get_location(f"Eat Stardrop") + self.assert_cannot_reach_location(location) + self.collect("Stardrop") + self.assert_can_reach_location(location) + + +class TestEatsanityPoisonousFish(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.poisonous, EatsanityOptionName.fish}), + } + expected_eating_locations = { + "Eat Pufferfish", + "Eat Carp", + "Eat Bullhead", + } + unexpected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Drink Parsnip", + "Drink Vinegar", + "Drink Wine", + "Eat Vinegar", + "Eat Tortilla", + "Eat Pepper Poppers", + "Drink Pina Colada", + "Eat Stardrop", + "Eat Red Mushroom" + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + +class TestEatsanityPoisonousArtisan(SVEatsanityTestBase): + options = { + SeasonRandomization: SeasonRandomization.option_disabled, + QuestLocations: -1, + Secretsanity: frozenset([]), + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.artisan, EatsanityOptionName.poisonous}), + } + expected_eating_locations = { + "Drink Vinegar", + "Drink Wine", + "Drink Iridium Snake Milk", + "Eat Void Egg", + } + unexpected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Drink Parsnip", + "Eat Vinegar", + "Drink Pina Colada", + "Eat Pufferfish", + "Eat Tortilla", + "Eat Pepper Poppers", + "Eat Carp", + "Eat Bullhead", + } + unexpected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } + + def test_need_lots_of_things_for_iridium_snake_milk(self): + location = self.world.get_location(f"Drink Iridium Snake Milk") + required_items = ["Desert Obelisk", "Skull Key", "Progressive House", *["Progressive Pickaxe"]*2, + *["Progressive Weapon"]*4, *["Mining Level"]*8, *["Combat Level"]*8] + unique_items = list(set(required_items)) + required_items = [self.create_item(item) for item in required_items] + self.collect(required_items) + self.assert_can_reach_location(location) + for item_name in unique_items: + with self.subTest(f"Requires {item_name} to {location.name}"): + item_to_remove = next(item for item in required_items if item.name == item_name) + self.assert_can_reach_location(location) + self.remove(item_to_remove) + self.assert_cannot_reach_location(location) + self.collect(item_to_remove) + + +class TestEatsanityPoisonousWithQuests(SVEatsanityTestBase): + options = { + QuestLocations: 0, + Secretsanity: frozenset([SecretsanityOptionName.secret_notes]), + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.artisan, EatsanityOptionName.poisonous}), + } + + def test_need_only_iridium_snake_milk_to_drink_it(self): + location = self.world.get_location(f"Drink Iridium Snake Milk") + self.assert_cannot_reach_location(location) + self.collect("Iridium Snake Milk") + self.assert_can_reach_location(location) + + +class TestEatsanityEnzymeEffects(SVEatsanityTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Eatsanity: frozenset({EatsanityOptionName.crops, EatsanityOptionName.fish, EatsanityOptionName.lock_effects}), + } + expected_eating_locations = { + "Eat Parsnip", + "Eat Yam", + "Eat Summer Spangle", + "Eat Apple", + "Eat Sweet Pea", + "Eat Carp", + "Eat Bullhead", + } + unexpected_eating_locations = { + "Drink Parsnip", + "Drink Vinegar", + "Drink Wine", + "Eat Vinegar", + "Eat Tortilla", + "Eat Pepper Poppers", + "Drink Pina Colada", + "Eat Stardrop", + "Eat Red Mushroom" + "Eat Pufferfish", + } + expected_eating_items = { + "Stamina Enzyme", + "Health Enzyme", + "Mining Enzyme", + "Luck Enzyme", + "Attack Enzyme", + } \ No newline at end of file diff --git a/worlds/stardew_valley/test/TestFill.py b/worlds/stardew_valley/test/TestFill.py index 2205c49cdfbb..63afd98d52a7 100644 --- a/worlds/stardew_valley/test/TestFill.py +++ b/worlds/stardew_valley/test/TestFill.py @@ -8,6 +8,7 @@ class TestMinLocationsMaxItems(WorldAssertMixin, SVTestBase): options = minimal_locations_maximal_items() + @property def run_default_tests(self) -> bool: return True @@ -24,6 +25,7 @@ class TestSpecificSeedForTroubleshooting(WorldAssertMixin, SVTestBase): } seed = 65453499742665118161 + @property def run_default_tests(self) -> bool: return True diff --git a/worlds/stardew_valley/test/TestFishsanity.py b/worlds/stardew_valley/test/TestFishsanity.py index 953255c4d077..b6f7cce15301 100644 --- a/worlds/stardew_valley/test/TestFishsanity.py +++ b/worlds/stardew_valley/test/TestFishsanity.py @@ -398,6 +398,7 @@ class TestFishsanityMasterAnglerSVEWithoutQuests(WorldAssertMixin, SVTestBase): Mods: (ModNames.sve,), } + @property def run_default_tests(self) -> bool: return True diff --git a/worlds/stardew_valley/test/TestGeneration.py b/worlds/stardew_valley/test/TestGeneration.py index 7b1535676d27..8848f19d85af 100644 --- a/worlds/stardew_valley/test/TestGeneration.py +++ b/worlds/stardew_valley/test/TestGeneration.py @@ -2,11 +2,14 @@ from BaseClasses import ItemClassification, Item from .bases import SVTestBase -from .. import location_table, options, items +from .. import location_table, options, items, StartWithoutOptionName +from ..content.content_packs import vanilla_content_pack_names from ..items import Group, ItemData, item_data from ..locations import LocationTags from ..options import Friendsanity, SpecialOrderLocations, Shipsanity, Chefsanity, SeasonRandomization, Craftsanity, ExcludeGingerIsland, SkillProgression, \ - Booksanity, Walnutsanity + Booksanity, Walnutsanity, Secretsanity, Moviesanity +from ..options.options import IncludeEndgameLocations, Eatsanity +from ..strings.ap_names.transport_names import Transportation from ..strings.region_names import Region @@ -17,7 +20,7 @@ def get_all_permanent_progression_items() -> List[ItemData]: item for item in item_data.all_items if ItemClassification.progression in item.classification - if item.mod_name is None + if item.content_packs.issubset(vanilla_content_pack_names) if item.name not in {event.name for event in item_data.events} if item.name not in {deprecated.name for deprecated in items.items_by_group[Group.DEPRECATED]} if item.name not in {season.name for season in items.items_by_group[Group.SEASON]} @@ -35,10 +38,14 @@ class TestBaseItemGeneration(SVTestBase): SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi, Friendsanity.internal_name: Friendsanity.option_all_with_marriage, Shipsanity.internal_name: Shipsanity.option_everything, - Chefsanity.internal_name: Chefsanity.option_all, + Chefsanity.internal_name: Chefsanity.preset_all, Craftsanity.internal_name: Craftsanity.option_all, Booksanity.internal_name: Booksanity.option_all, Walnutsanity.internal_name: Walnutsanity.preset_all, + Moviesanity.internal_name: Moviesanity.option_all_movies_and_all_loved_snacks, + Eatsanity.internal_name: Eatsanity.preset_all, + Secretsanity.internal_name: Secretsanity.preset_all, + IncludeEndgameLocations.internal_name: IncludeEndgameLocations.option_true, } def test_all_progression_items_are_added_to_the_pool(self): @@ -78,10 +85,14 @@ class TestNoGingerIslandItemGeneration(SVTestBase): SkillProgression.internal_name: SkillProgression.option_progressive_with_masteries, Friendsanity.internal_name: Friendsanity.option_all_with_marriage, Shipsanity.internal_name: Shipsanity.option_everything, - Chefsanity.internal_name: Chefsanity.option_all, + Chefsanity.internal_name: Chefsanity.preset_all, Craftsanity.internal_name: Craftsanity.option_all, ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, Booksanity.internal_name: Booksanity.option_all, + Moviesanity.internal_name: Moviesanity.option_all_movies_and_all_loved_snacks, + Eatsanity.internal_name: Eatsanity.preset_all, + Secretsanity.internal_name: Secretsanity.preset_all, + IncludeEndgameLocations.internal_name: IncludeEndgameLocations.option_true, } def test_all_progression_items_except_island_are_added_to_the_pool(self): @@ -122,6 +133,7 @@ def test_does_not_create_exactly_two_items(self): class TestProgressiveElevator(SVTestBase): options = { + options.StartWithout: frozenset({StartWithoutOptionName.landslide}), options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, options.ToolProgression.internal_name: options.ToolProgression.option_progressive, options.SkillProgression.internal_name: options.SkillProgression.option_progressive, @@ -140,12 +152,13 @@ def test_given_elevator_to_floor_105_when_find_another_elevator_then_has_access_ self.assert_can_reach_region(Region.mines_floor_120) def generate_items_for_mine_115(self) -> List[Item]: + landslide = self.get_item_by_name("Landslide Removed") pickaxes = [self.get_item_by_name("Progressive Pickaxe")] * 2 elevators = [self.get_item_by_name("Progressive Mine Elevator")] * 21 swords = [self.get_item_by_name("Progressive Sword")] * 3 combat_levels = [self.get_item_by_name("Combat Level")] * 4 mining_levels = [self.get_item_by_name("Mining Level")] * 4 - return [*combat_levels, *mining_levels, *elevators, *pickaxes, *swords] + return [landslide, *combat_levels, *mining_levels, *elevators, *pickaxes, *swords] def generate_items_for_extra_mine_levels(self, weapon_name: str) -> List[Item]: last_pickaxe = self.get_item_by_name("Progressive Pickaxe") @@ -159,9 +172,11 @@ def generate_items_for_extra_mine_levels(self, weapon_name: str) -> List[Item]: class TestSkullCavernLogic(SVTestBase): options = { + options.StartWithout: frozenset({StartWithoutOptionName.landslide}), options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, options.ToolProgression.internal_name: options.ToolProgression.option_progressive, options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, } def test_given_access_to_floor_115_when_find_more_tools_then_has_access_to_skull_cavern_25(self): @@ -189,31 +204,36 @@ def test_given_access_to_floor_115_when_find_more_tools_then_has_access_to_skull self.assert_can_reach_region(Region.skull_cavern_75) def generate_items_for_mine_115(self) -> List[Item]: + landslide = self.get_item_by_name("Landslide Removed") pickaxes = [self.get_item_by_name("Progressive Pickaxe")] * 2 swords = [self.get_item_by_name("Progressive Sword")] * 3 combat_levels = [self.get_item_by_name("Combat Level")] * 4 mining_levels = [self.get_item_by_name("Mining Level")] * 4 - bus = self.get_item_by_name("Bus Repair") + bus = self.get_item_by_name(Transportation.bus_repair) skull_key = self.get_item_by_name("Skull Key") - return [*combat_levels, *mining_levels, *pickaxes, *swords, bus, skull_key] + return [landslide, *combat_levels, *mining_levels, *pickaxes, *swords, bus, skull_key] def generate_items_for_skull_50(self) -> List[Item]: + landslide = self.get_item_by_name("Landslide Removed") pickaxes = [self.get_item_by_name("Progressive Pickaxe")] * 3 swords = [self.get_item_by_name("Progressive Sword")] * 4 combat_levels = [self.get_item_by_name("Combat Level")] * 6 mining_levels = [self.get_item_by_name("Mining Level")] * 6 - bus = self.get_item_by_name("Bus Repair") + bus = self.get_item_by_name(Transportation.bus_repair) skull_key = self.get_item_by_name("Skull Key") - return [*combat_levels, *mining_levels, *pickaxes, *swords, bus, skull_key] + farm_house = self.get_item_by_name("Progressive House") + return [landslide, *combat_levels, *mining_levels, *pickaxes, *swords, bus, skull_key, farm_house] def generate_items_for_skull_100(self) -> List[Item]: + landslide = self.get_item_by_name("Landslide Removed") pickaxes = [self.get_item_by_name("Progressive Pickaxe")] * 4 swords = [self.get_item_by_name("Progressive Sword")] * 5 combat_levels = [self.get_item_by_name("Combat Level")] * 8 mining_levels = [self.get_item_by_name("Mining Level")] * 8 - bus = self.get_item_by_name("Bus Repair") + bus = self.get_item_by_name(Transportation.bus_repair) skull_key = self.get_item_by_name("Skull Key") - return [*combat_levels, *mining_levels, *pickaxes, *swords, bus, skull_key] + farm_house = self.get_item_by_name("Progressive House") + return [landslide, *combat_levels, *mining_levels, *pickaxes, *swords, bus, skull_key, farm_house] class TestShipsanityNone(SVTestBase): @@ -221,10 +241,6 @@ class TestShipsanityNone(SVTestBase): Shipsanity.internal_name: Shipsanity.option_none } - def run_default_tests(self) -> bool: - # None is default - return False - def test_no_shipsanity_locations(self): for location in self.get_real_locations(): with self.subTest(location.name): @@ -327,6 +343,34 @@ def test_include_island_fish_shipsanity_locations(self): self.assertIn("Shipsanity: Son of Crimsonfish", location_names) +class TestShipsanityCropsAndFish(SVTestBase): + options = { + Shipsanity.internal_name: Shipsanity.option_crops_and_fish, + ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false, + SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi + } + + def test_only_crops_and_fish_shipsanity_locations(self): + for location in self.get_real_locations(): + if LocationTags.SHIPSANITY in location_table[location.name].tags: + with self.subTest(location.name): + is_fish = LocationTags.SHIPSANITY_FISH in location_table[location.name].tags + is_crop = LocationTags.SHIPSANITY_CROP in location_table[location.name].tags + self.assertTrue(is_fish or is_crop) + + def test_include_specific_shipsanity_locations(self): + location_names = [location.name for location in self.multiworld.get_locations(self.player)] + self.assertIn("Shipsanity: Blue Discus", location_names) + self.assertIn("Shipsanity: Glacierfish Jr.", location_names) + self.assertIn("Shipsanity: Perch", location_names) + self.assertIn("Shipsanity: Powdermelon", location_names) + self.assertIn("Shipsanity: Starfruit", location_names) + self.assertIn("Shipsanity: Taro Root", location_names) + self.assertNotIn("Shipsanity: Iron Bar", location_names) + self.assertNotIn("Shipsanity: Wine", location_names) + self.assertNotIn("Shipsanity: Tea Set", location_names) + + class TestShipsanityFishExcludeIsland(SVTestBase): options = { Shipsanity.internal_name: Shipsanity.option_fish, diff --git a/worlds/stardew_valley/test/TestItemLink.py b/worlds/stardew_valley/test/TestItemLink.py index c3029b60704d..7bdff07fc132 100644 --- a/worlds/stardew_valley/test/TestItemLink.py +++ b/worlds/stardew_valley/test/TestItemLink.py @@ -1,15 +1,18 @@ from .bases import SVTestBase from .. import options, item_table, Group -max_iterations = 2000 +max_iterations = 4000 +# Success Rate: (1 - ((1 - (1 / 199)) ^ 4000)) ^ 199 -> 0.99999964737880935363435882766624 -> 99.999965% class TestItemLinksEverythingIncluded(SVTestBase): options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium} + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium, + options.AllowedFillerItems.internal_name: options.AllowedFillerItems.preset_all, + } def test_filler_of_all_types_generated(self): - max_number_filler = 114 + max_number_filler = 199 filler_generated = [] at_least_one_trap = False at_least_one_island = False @@ -33,10 +36,12 @@ def test_filler_of_all_types_generated(self): class TestItemLinksNoIsland(SVTestBase): options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium} + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium, + options.AllowedFillerItems.internal_name: options.AllowedFillerItems.preset_all, + } def test_filler_has_no_island_but_has_traps(self): - max_number_filler = 109 + max_number_filler = 192 filler_generated = [] at_least_one_trap = False for i in range(0, max_iterations): @@ -57,10 +62,12 @@ def test_filler_has_no_island_but_has_traps(self): class TestItemLinksNoTraps(SVTestBase): options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_no_traps} + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_no_traps, + options.AllowedFillerItems.internal_name: options.AllowedFillerItems.preset_all, + } def test_filler_has_no_traps_but_has_island(self): - max_number_filler = 99 + max_number_filler = 176 filler_generated = [] at_least_one_island = False for i in range(0, max_iterations): @@ -81,10 +88,12 @@ def test_filler_has_no_traps_but_has_island(self): class TestItemLinksNoTrapsAndIsland(SVTestBase): options = {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.TrapDifficulty.internal_name: options.TrapDifficulty.option_no_traps} + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_no_traps, + options.AllowedFillerItems.internal_name: options.AllowedFillerItems.preset_all, + } def test_filler_generated_without_island_or_traps(self): - max_number_filler = 94 + max_number_filler = 169 filler_generated = [] for i in range(0, max_iterations): filler = self.multiworld.worlds[1].get_filler_item_name() diff --git a/worlds/stardew_valley/test/TestItems.py b/worlds/stardew_valley/test/TestItems.py index d4fa9e832a8e..03e1ef74783e 100644 --- a/worlds/stardew_valley/test/TestItems.py +++ b/worlds/stardew_valley/test/TestItems.py @@ -1,6 +1,6 @@ from BaseClasses import MultiWorld, get_seed, ItemClassification from .bases import SVTestCase, solo_multiworld, setup_solo_multiworld -from .options.presets import allsanity_no_mods_6_x_x, get_minsanity_options +from .options.presets import get_minsanity_options, allsanity_no_mods_7_x_x from .. import StardewValleyWorld from ..items import Group, item_table from ..options import Friendsanity, SeasonRandomization, Museumsanity, Shipsanity, Goal @@ -44,15 +44,15 @@ def test_babies_come_in_all_shapes_and_sizes(self): self.assertEqual(len(baby_permutations), 4) def test_correct_number_of_stardrops(self): - allsanity_options = allsanity_no_mods_6_x_x() + allsanity_options = allsanity_no_mods_7_x_x() with solo_multiworld(allsanity_options) as (multiworld, _): stardrop_items = [item for item in multiworld.get_items() if item.name == "Stardrop"] self.assertEqual(len(stardrop_items), 7) def test_no_duplicate_rings(self): - allsanity_options = allsanity_no_mods_6_x_x() + allsanity_options = allsanity_no_mods_7_x_x() with solo_multiworld(allsanity_options) as (multiworld, _): - ring_items = [item.name for item in multiworld.get_items() if Group.RING in item_table[item.name].groups] + ring_items = [item.name for item in multiworld.get_items() if Group.FILLER_RING in item_table[item.name].groups] self.assertEqual(len(ring_items), len(set(ring_items))) def test_can_start_in_any_season(self): @@ -75,7 +75,7 @@ def test_can_start_in_any_season(self): class TestStartInventoryFillersAreProperlyExcluded(SVTestCase): def test_given_maximum_one_resource_pack_in_start_inventory_when_create_items_then_item_is_properly_excluded(self): assert item_table[Wallet.key_to_the_town].classification == ItemClassification.useful \ - and {Group.MAXIMUM_ONE, Group.RESOURCE_PACK_USEFUL}.issubset(item_table[Wallet.key_to_the_town].groups), \ + and {Group.MAXIMUM_ONE, Group.FILLER_QUALITY_OF_LIFE}.issubset(item_table[Wallet.key_to_the_town].groups), \ "'Key to the Town' is no longer suitable to test this usecase." options = { diff --git a/worlds/stardew_valley/test/TestLogic.py b/worlds/stardew_valley/test/TestLogic.py index 047e0226c6e0..10d048cd5528 100644 --- a/worlds/stardew_valley/test/TestLogic.py +++ b/worlds/stardew_valley/test/TestLogic.py @@ -4,13 +4,16 @@ from BaseClasses import MultiWorld from .assertion import RuleAssertMixin -from .bases import setup_solo_multiworld -from .options.presets import allsanity_mods_6_x_x, minimal_locations_maximal_items +from .bases import setup_solo_multiworld, skip_long_tests +from .options.presets import minimal_locations_maximal_items, allsanity_mods_7_x_x from .. import StardewValleyWorld -from ..data.bundle_data import all_bundle_items_except_money +from ..data.bundles_data.bundle_data import all_bundle_items_except_money from ..logic.logic import StardewLogic from ..options import BundleRandomization +if skip_long_tests(): + raise unittest.SkipTest("Long tests disabled") + def collect_all(mw): for item in mw.get_items(): @@ -98,7 +101,7 @@ def test_given_location_rule_then_can_be_resolved(self): class TestAllSanityLogic(LogicTestBase): - options = allsanity_mods_6_x_x() + options = allsanity_mods_7_x_x() @unittest.skip("This test does not pass because some content is still not in content packs.") diff --git a/worlds/stardew_valley/test/TestMovies.py b/worlds/stardew_valley/test/TestMovies.py new file mode 100644 index 000000000000..3ad092bd5a2a --- /dev/null +++ b/worlds/stardew_valley/test/TestMovies.py @@ -0,0 +1,85 @@ +from .bases import SVTestBase +from .. import SeasonRandomization +from ..data.movies import movies_by_name +from ..options import Moviesanity + + +class MovieTestBase(SVTestBase): + + def test_all_movies_require_theater_and_season(self): + if Moviesanity.internal_name not in self.options or self.options[Moviesanity.internal_name] == Moviesanity.option_none: + return + self.collect_lots_of_money(0.5) + [self.collect(snack) for snack in ["Movie Drinks", "Movie Sweet Snacks", "Movie Salty Snacks"]] + theater_items = [self.create_item("Progressive Movie Theater"), self.create_item("Progressive Movie Theater")] + [self.remove_one_by_name(season) for season in ["Spring", "Summer", "Fall", "Winter"]] + for movie_name in movies_by_name: + movie = movies_by_name[movie_name] + movie_location = f"Watch {movie_name}" + self.collect(movie.season) + with self.subTest(f"{movie_location} requires two movie theaters"): + self.assert_cannot_reach_location(movie_location) + self.collect(theater_items[0]) + self.assert_cannot_reach_location(movie_location) + self.collect(theater_items[1]) + self.assert_can_reach_location(movie_location) + self.remove_one_by_name(movie.season) + with self.subTest(f"{movie_location} requires {movie.season}"): + self.assert_cannot_reach_location(movie_location) + self.collect(movie.season) + self.assert_can_reach_location(movie_location) + self.remove(theater_items) + self.remove_one_by_name(movie.season) + + +class TestOneMovie(SVTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Moviesanity.internal_name: Moviesanity.option_one + } + + def test_all_movies_require_theater_and_season(self): + self.collect_lots_of_money(0.5) + theater_items = [self.create_item("Progressive Movie Theater"), self.create_item("Progressive Movie Theater")] + movie_location = f"Watch A Movie" + with self.subTest(f"{movie_location} requires two movie theaters"): + self.assert_cannot_reach_location(movie_location) + self.collect(theater_items[0]) + self.assert_cannot_reach_location(movie_location) + self.collect(theater_items[1]) + self.assert_can_reach_location(movie_location) + + +class TestAllMovies(MovieTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Moviesanity.internal_name: Moviesanity.option_all_movies + } + + +class TestAllMoviesLoved(MovieTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Moviesanity.internal_name: Moviesanity.option_all_movies_loved + } + + +class TestAllMoviesAndAllSnacks(MovieTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Moviesanity.internal_name: Moviesanity.option_all_movies_and_all_snacks + } + + +class TestAllMoviesWithLovedSnack(MovieTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Moviesanity.internal_name: Moviesanity.option_all_movies_with_loved_snack + } + + +class TestAllMoviesAndAllLovedSnacks(MovieTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Moviesanity.internal_name: Moviesanity.option_all_movies_and_all_loved_snacks + } \ No newline at end of file diff --git a/worlds/stardew_valley/test/TestNumberLocations.py b/worlds/stardew_valley/test/TestNumberLocations.py index dd57a5e39b7b..b21488733bb1 100644 --- a/worlds/stardew_valley/test/TestNumberLocations.py +++ b/worlds/stardew_valley/test/TestNumberLocations.py @@ -1,8 +1,10 @@ +from BaseClasses import ItemClassification from .bases import SVTestBase -from .options.presets import default_6_x_x, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x_exclude_disabled, get_minsanity_options, \ - minimal_locations_maximal_items, minimal_locations_maximal_items_with_island +from .options.presets import default_7_x_x, allsanity_no_mods_7_x_x, get_minsanity_options, \ + minimal_locations_maximal_items, minimal_locations_maximal_items_with_island, allsanity_mods_7_x_x_exclude_disabled from .. import location_table from ..items import Group, item_table +from ..items.item_data import FILLER_GROUPS class TestLocationGeneration(SVTestBase): @@ -19,7 +21,7 @@ def test_minimal_location_maximal_items_still_valid(self): valid_locations = self.get_real_locations() number_locations = len(valid_locations) number_items = len([item for item in self.multiworld.itempool - if Group.RESOURCE_PACK not in item_table[item.name].groups and Group.TRAP not in item_table[item.name].groups]) + if all(filler_group not in item_table[item.name].groups for filler_group in FILLER_GROUPS) and Group.TRAP not in item_table[item.name].groups]) print(f"Stardew Valley - Minimum Locations: {number_locations}, Maximum Items: {number_items} [ISLAND EXCLUDED]") self.assertGreaterEqual(number_locations, number_items) @@ -31,7 +33,7 @@ def test_minimal_location_maximal_items_with_island_still_valid(self): valid_locations = self.get_real_locations() number_locations = len(valid_locations) number_items = len([item for item in self.multiworld.itempool - if Group.RESOURCE_PACK not in item_table[item.name].groups and Group.TRAP not in item_table[item.name].groups]) + if all(filler_group not in item_table[item.name].groups for filler_group in FILLER_GROUPS) and Group.TRAP not in item_table[item.name].groups and (item.classification & ItemClassification.progression)]) print(f"Stardew Valley - Minimum Locations: {number_locations}, Maximum Items: {number_items} [ISLAND INCLUDED]") self.assertGreaterEqual(number_locations, number_items) @@ -39,24 +41,24 @@ def test_minimal_location_maximal_items_with_island_still_valid(self): class TestMinSanityHasAllExpectedLocations(SVTestBase): options = get_minsanity_options() - def test_minsanity_has_fewer_than_locations(self): - expected_locations = 85 + def test_minsanity_has_few_locations(self): + fewest_allowed_locations = 90 real_locations = self.get_real_locations() number_locations = len(real_locations) print(f"Stardew Valley - Minsanity Locations: {number_locations}") - self.assertLessEqual(number_locations, expected_locations) - if number_locations != expected_locations: - print(f"\tDisappeared Locations Detected!" + self.assertGreaterEqual(number_locations, fewest_allowed_locations) + if number_locations < fewest_allowed_locations: + print(f"\tMinsanity too many locations detected" f"\n\tPlease update test_minsanity_has_fewer_than_locations" - f"\n\t\tExpected: {expected_locations}" + f"\n\t\tMinimum: {fewest_allowed_locations}" f"\n\t\tActual: {number_locations}") class TestDefaultSettingsHasAllExpectedLocations(SVTestBase): - options = default_6_x_x() + options = default_7_x_x() def test_default_settings_has_exactly_locations(self): - expected_locations = 491 + expected_locations = 475 real_locations = self.get_real_locations() number_locations = len(real_locations) print(f"Stardew Valley - Default options locations: {number_locations}") @@ -68,10 +70,10 @@ def test_default_settings_has_exactly_locations(self): class TestAllSanitySettingsHasAllExpectedLocations(SVTestBase): - options = allsanity_no_mods_6_x_x() + options = allsanity_no_mods_7_x_x() def test_allsanity_without_mods_has_at_least_locations(self): - expected_locations = 2256 + expected_locations = 2812 real_locations = self.get_real_locations() number_locations = len(real_locations) print(f"Stardew Valley - Allsanity Locations without mods: {number_locations}") @@ -84,10 +86,10 @@ def test_allsanity_without_mods_has_at_least_locations(self): class TestAllSanityWithModsSettingsHasAllExpectedLocations(SVTestBase): - options = allsanity_mods_6_x_x_exclude_disabled() + options = allsanity_mods_7_x_x_exclude_disabled() def test_allsanity_with_mods_has_at_least_locations(self): - expected_locations = 2908 + expected_locations = 3180 # It was 3473 before disabling SVE real_locations = self.get_real_locations() number_locations = len(real_locations) print(f"Stardew Valley - Allsanity Locations with all mods: {number_locations}") diff --git a/worlds/stardew_valley/test/TestOptions.py b/worlds/stardew_valley/test/TestOptions.py deleted file mode 100644 index 738753fe8362..000000000000 --- a/worlds/stardew_valley/test/TestOptions.py +++ /dev/null @@ -1,201 +0,0 @@ -import itertools -from typing import ClassVar - -from BaseClasses import ItemClassification -from test.param import classvar_matrix -from .assertion import WorldAssertMixin -from .bases import SVTestCase, SVTestBase, solo_multiworld -from .options.option_names import all_option_choices -from .options.presets import allsanity_no_mods_6_x_x, allsanity_mods_6_x_x -from .. import items_by_group, Group -from ..locations import locations_by_tag, LocationTags, location_table -from ..options import ExcludeGingerIsland, ToolProgression, Goal, SeasonRandomization, TrapDifficulty, SpecialOrderLocations, ArcadeMachineLocations -from ..strings.goal_names import Goal as GoalName -from ..strings.season_names import Season -from ..strings.special_order_names import SpecialOrder -from ..strings.tool_names import ToolMaterial, Tool, APTool - -SEASONS = {Season.spring, Season.summer, Season.fall, Season.winter} -TOOLS = {"Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can", "Fishing Rod"} - - -@classvar_matrix(option_and_choice=all_option_choices) -class TestGenerateDynamicOptions(WorldAssertMixin, SVTestCase): - option_and_choice: ClassVar[tuple[str, str]] - - def test_given_option_and_choice_when_generate_then_basic_checks(self): - option, choice = self.option_and_choice - world_options = {option: choice} - with solo_multiworld(world_options) as (multiworld, stardew_world): - self.assert_basic_checks(multiworld) - - -@classvar_matrix(goal_and_location=[ - ("community_center", GoalName.community_center), - ("grandpa_evaluation", GoalName.grandpa_evaluation), - ("bottom_of_the_mines", GoalName.bottom_of_the_mines), - ("cryptic_note", GoalName.cryptic_note), - ("master_angler", GoalName.master_angler), - ("complete_collection", GoalName.complete_museum), - ("full_house", GoalName.full_house), - ("perfection", GoalName.perfection), -]) -class TestGoal(SVTestCase): - goal_and_location: ClassVar[tuple[str, str]] - - def test_given_goal_when_generate_then_victory_is_in_correct_location(self): - goal, location = self.goal_and_location - world_options = {Goal.internal_name: goal} - with solo_multiworld(world_options) as (multi_world, _): - victory = multi_world.find_item("Victory", 1) - self.assertEqual(victory.name, location) - - -class TestSeasonRandomization(SVTestCase): - def test_given_disabled_when_generate_then_all_seasons_are_precollected(self): - world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_disabled} - with solo_multiworld(world_options) as (multi_world, _): - precollected_items = {item.name for item in multi_world.precollected_items[1]} - self.assertTrue(all([season in precollected_items for season in SEASONS])) - - def test_given_randomized_when_generate_then_all_seasons_are_in_the_pool_or_precollected(self): - world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_randomized} - with solo_multiworld(world_options) as (multi_world, _): - precollected_items = {item.name for item in multi_world.precollected_items[1]} - items = {item.name for item in multi_world.get_items()} | precollected_items - self.assertTrue(all([season in items for season in SEASONS])) - self.assertEqual(len(SEASONS.intersection(precollected_items)), 1) - - def test_given_progressive_when_generate_then_3_progressive_seasons_are_in_the_pool(self): - world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_progressive} - with solo_multiworld(world_options) as (multi_world, _): - items = [item.name for item in multi_world.get_items()] - self.assertEqual(items.count(Season.progressive), 3) - - -class TestToolProgression(SVTestBase): - options = { - ToolProgression.internal_name: ToolProgression.option_progressive, - } - - def test_given_progressive_when_generate_then_tool_upgrades_are_locations(self): - locations = set(self.get_real_location_names()) - for material, tool in itertools.product(ToolMaterial.tiers.values(), - [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can]): - if material == ToolMaterial.basic: - continue - self.assertIn(f"{material} {tool} Upgrade", locations) - self.assertIn("Purchase Training Rod", locations) - self.assertIn("Bamboo Pole Cutscene", locations) - self.assertIn("Purchase Fiberglass Rod", locations) - self.assertIn("Purchase Iridium Rod", locations) - - def test_given_progressive_when_generate_then_only_3_trash_can_are_progressive(self): - trash_cans = self.get_items_by_name(APTool.trash_can) - progressive_count = sum([1 for item in trash_cans if item.classification == ItemClassification.progression]) - useful_count = sum([1 for item in trash_cans if item.classification == ItemClassification.useful]) - - self.assertEqual(progressive_count, 3) - self.assertEqual(useful_count, 1) - - -@classvar_matrix(option_and_choice=all_option_choices) -class TestGenerateAllOptionsWithExcludeGingerIsland(WorldAssertMixin, SVTestCase): - option_and_choice: ClassVar[tuple[str, str]] - - def test_given_choice_when_generate_exclude_ginger_island_then_ginger_island_is_properly_excluded(self): - option, option_choice = self.option_and_choice - - if option == ExcludeGingerIsland.internal_name: - self.skipTest("ExcludeGingerIsland is forced to true") - - world_options = { - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, - option: option_choice - } - - with solo_multiworld(world_options) as (multiworld, stardew_world): - - if stardew_world.options.exclude_ginger_island != ExcludeGingerIsland.option_true: - self.skipTest("Some options, like goals, will force Ginger island back in the game. We want to skip testing those.") - - self.assert_basic_checks(multiworld) - self.assert_no_ginger_island_content(multiworld) - - -class TestTraps(SVTestCase): - def test_given_no_traps_when_generate_then_no_trap_in_pool(self): - world_options = allsanity_no_mods_6_x_x().copy() - world_options[TrapDifficulty.internal_name] = TrapDifficulty.option_no_traps - with solo_multiworld(world_options) as (multi_world, _): - trap_items = [item_data.name for item_data in items_by_group[Group.TRAP]] - multiworld_items = [item.name for item in multi_world.get_items()] - - for item in trap_items: - with self.subTest(f"{item}"): - self.assertNotIn(item, multiworld_items) - - def test_given_traps_when_generate_then_all_traps_in_pool(self): - trap_option = TrapDifficulty - for value in trap_option.options: - if value == "no_traps": - continue - world_options = allsanity_mods_6_x_x() - world_options.update({TrapDifficulty.internal_name: trap_option.options[value]}) - with solo_multiworld(world_options) as (multi_world, _): - trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if - Group.DEPRECATED not in item_data.groups and item_data.mod_name is None] - multiworld_items = [item.name for item in multi_world.get_items()] - for item in trap_items: - with self.subTest(f"Option: {value}, Item: {item}"): - self.assertIn(item, multiworld_items) - - -class TestSpecialOrders(SVTestCase): - def test_given_disabled_then_no_order_in_pool(self): - world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_vanilla} - with solo_multiworld(world_options) as (multi_world, _): - locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table} - for location_name in locations_in_pool: - location = location_table[location_name] - self.assertNotIn(LocationTags.SPECIAL_ORDER_BOARD, location.tags) - self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags) - - def test_given_board_only_then_no_qi_order_in_pool(self): - world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board} - with solo_multiworld(world_options) as (multi_world, _): - - locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table} - for location_name in locations_in_pool: - location = location_table[location_name] - self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags) - - for board_location in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: - if board_location.mod_name: - continue - self.assertIn(board_location.name, locations_in_pool) - - def test_given_board_and_qi_then_all_orders_in_pool(self): - world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi, - ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_victories, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false} - with solo_multiworld(world_options) as (multi_world, _): - - locations_in_pool = {location.name for location in multi_world.get_locations()} - for qi_location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI]: - if qi_location.mod_name: - continue - self.assertIn(qi_location.name, locations_in_pool) - - for board_location in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: - if board_location.mod_name: - continue - self.assertIn(board_location.name, locations_in_pool) - - def test_given_board_and_qi_without_arcade_machines_then_lets_play_a_game_not_in_pool(self): - world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi, - ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false} - with solo_multiworld(world_options) as (multi_world, _): - locations_in_pool = {location.name for location in multi_world.get_locations()} - self.assertNotIn(SpecialOrder.lets_play_a_game, locations_in_pool) diff --git a/worlds/stardew_valley/test/TestStartInventory.py b/worlds/stardew_valley/test/TestStartInventory.py index 43ee0e132961..8bb3450416b4 100644 --- a/worlds/stardew_valley/test/TestStartInventory.py +++ b/worlds/stardew_valley/test/TestStartInventory.py @@ -24,7 +24,7 @@ class TestStartInventoryAllsanity(WorldAssertMixin, SVTestBase): options.Monstersanity.internal_name: options.Monstersanity.option_one_per_category, options.Shipsanity.internal_name: options.Shipsanity.option_crops, options.Cooksanity.internal_name: options.Cooksanity.option_queen_of_sauce, - options.Chefsanity.internal_name: 0b1001, + options.Chefsanity.internal_name: options.Chefsanity.preset_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, options.Friendsanity.internal_name: options.Friendsanity.option_bachelors, options.FriendsanityHeartSize.internal_name: 3, diff --git a/worlds/stardew_valley/test/TestTraps.py b/worlds/stardew_valley/test/TestTraps.py index 130674a35da4..f3c2c0bde552 100644 --- a/worlds/stardew_valley/test/TestTraps.py +++ b/worlds/stardew_valley/test/TestTraps.py @@ -4,6 +4,7 @@ from .bases import SVTestBase from .. import options, items_by_group, Group from ..options import TrapDistribution +from ..strings.ap_names.ap_option_names import EatsanityOptionName default_distribution = {trap.name: TrapDistribution.default_weight for trap in items_by_group[Group.TRAP] if Group.DEPRECATED not in trap.groups} threshold_difference = 2 @@ -19,7 +20,7 @@ class TestTrapDifficultyCanRemoveAllTraps(WorldAssertMixin, SVTestBase): options.Shipsanity.internal_name: options.Shipsanity.option_everything, options.Cooksanity.internal_name: options.Cooksanity.option_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, - options.Mods.internal_name: frozenset(options.Mods.valid_keys), + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), options.TrapDifficulty.internal_name: options.TrapDifficulty.option_no_traps, } @@ -41,7 +42,7 @@ class TestDefaultDistributionHasAllTraps(WorldAssertMixin, SVTestBase): options.Shipsanity.internal_name: options.Shipsanity.option_everything, options.Cooksanity.internal_name: options.Cooksanity.option_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, - options.Mods.internal_name: frozenset(options.Mods.valid_keys), + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium, } @@ -56,16 +57,25 @@ def test_all_traps_in_item_pool(self): class TestDistributionIsRespectedAllTraps(WorldAssertMixin, SVTestBase): options = { + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_four_extra, options.QuestLocations.internal_name: 56, + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, options.Fishsanity.internal_name: options.Fishsanity.option_all, options.Museumsanity.internal_name: options.Museumsanity.option_all, options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, options.Shipsanity.internal_name: options.Shipsanity.option_everything, options.Cooksanity.internal_name: options.Cooksanity.option_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, - options.Mods.internal_name: frozenset(options.Mods.valid_keys), + options.Eatsanity.internal_name: frozenset([EatsanityOptionName.shop, EatsanityOptionName.fish, EatsanityOptionName.artisan, EatsanityOptionName.crops, EatsanityOptionName.cooking, EatsanityOptionName.poisonous]), + options.Booksanity.internal_name: options.Booksanity.option_all, + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies_and_all_loved_snacks, + options.Secretsanity.internal_name: frozenset(options.Secretsanity.valid_keys), + options.Hatsanity.internal_name: options.Hatsanity.preset_all, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_true, + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), options.TrapDifficulty.internal_name: options.TrapDifficulty.option_medium, - options.TrapDistribution.internal_name: default_distribution | {"Nudge Trap": 100, "Bark Trap": 1, "Meow Trap": 1000, "Shuffle Trap": 0} + options.TrapDistribution.internal_name: {"Nudge Trap": 100, "Bark Trap": 1, "Meow Trap": 1000, "Shuffle Trap": 0} } @classmethod diff --git a/worlds/stardew_valley/test/TestWalnutsanity.py b/worlds/stardew_valley/test/TestWalnutsanity.py index 7111174d2630..64e141b27a83 100644 --- a/worlds/stardew_valley/test/TestWalnutsanity.py +++ b/worlds/stardew_valley/test/TestWalnutsanity.py @@ -45,6 +45,7 @@ class TestWalnutsanityNone(SVWalnutsanityTestBase): def test_logic_received_walnuts(self): # You need to receive 0, and collect 40 + self.collect("Wizard Invitation") self.collect("Island Obelisk") self.collect("Island West Turtle") self.collect("Progressive House") @@ -96,13 +97,14 @@ def test_field_office_locations_require_professor_snail(self): location_names = ["Walnutsanity: Complete Large Animal Collection", "Walnutsanity: Complete Snake Collection", "Walnutsanity: Complete Mummified Frog Collection", "Walnutsanity: Complete Mummified Bat Collection", "Walnutsanity: Purple Flowers Island Survey", "Walnutsanity: Purple Starfish Island Survey", ] + self.collect("Wizard Invitation") self.collect("Island Obelisk") self.collect("Island North Turtle") self.collect("Island West Turtle") self.collect("Island Resort") self.collect("Dig Site Bridge") self.collect("Progressive House") - self.collect("Progressive Pan") + self.collect("Progressive Pan", 4) self.collect("Progressive Fishing Rod") self.collect("Progressive Watering Can") self.collect("Progressive Pickaxe", 4) @@ -155,6 +157,7 @@ class TestWalnutsanityPuzzlesAndBushes(SVWalnutsanityTestBase): def test_logic_received_walnuts(self): # You need to receive 25, and collect 15 + self.collect("Wizard Invitation") self.collect("Island Obelisk") self.collect("Island West Turtle") self.collect("5 Golden Walnuts", 5) @@ -220,6 +223,7 @@ class TestWalnutsanityAll(SVWalnutsanityTestBase): def test_logic_received_walnuts(self): # You need to receive 40, and collect 4 + self.collect("Wizard Invitation") self.collect("Island Obelisk") self.collect("Island West Turtle") self.assert_cannot_reach_location(Transportation.parrot_express) diff --git a/worlds/stardew_valley/test/assertion/mod_assert.py b/worlds/stardew_valley/test/assertion/mod_assert.py index baba9bbaf856..9dcc3ee02a36 100644 --- a/worlds/stardew_valley/test/assertion/mod_assert.py +++ b/worlds/stardew_valley/test/assertion/mod_assert.py @@ -1,28 +1,30 @@ -from typing import Union, Iterable +from typing import Iterable from unittest import TestCase from BaseClasses import MultiWorld from ... import item_table, location_table +from ...content.content_packs import vanilla_content_pack_names from ...mods.mod_data import ModNames class ModAssertMixin(TestCase): - def assert_stray_mod_items(self, chosen_mods: Union[Iterable[str], str], multiworld: MultiWorld): - if isinstance(chosen_mods, str): - chosen_mods = [chosen_mods] + def assert_stray_mod_items(self, chosen_content_packs: Iterable[str] | str, multiworld: MultiWorld): + if isinstance(chosen_content_packs, str): + chosen_content_packs = vanilla_content_pack_names | {chosen_content_packs} else: - chosen_mods = list(chosen_mods) + chosen_content_packs = vanilla_content_pack_names | set(chosen_content_packs) - if ModNames.jasper in chosen_mods: + if ModNames.jasper in chosen_content_packs: # Jasper is a weird case because it shares NPC w/ SVE... - chosen_mods.append(ModNames.sve) + chosen_content_packs |= {ModNames.sve} for multiworld_item in multiworld.get_items(): item = item_table[multiworld_item.name] - self.assertTrue(item.mod_name is None or item.mod_name in chosen_mods, - f"Item {item.name} has is from mod {item.mod_name}. Allowed mods are {chosen_mods}.") + self.assertTrue(item.content_packs.issubset(chosen_content_packs), + f"Item {item.name} requires content packs {item.content_packs}. Allowed mods are {chosen_content_packs}.") for multiworld_location in multiworld.get_locations(): if multiworld_location.address is None: continue location = location_table[multiworld_location.name] - self.assertTrue(location.mod_name is None or location.mod_name in chosen_mods) + self.assertTrue(location.content_packs.issubset(chosen_content_packs), + f"Location {location.name} requires content packs {location.content_packs}. Allowed mods are {chosen_content_packs}.") diff --git a/worlds/stardew_valley/test/bases.py b/worlds/stardew_valley/test/bases.py index 4370c05d7b2c..485a42c7d3ba 100644 --- a/worlds/stardew_valley/test/bases.py +++ b/worlds/stardew_valley/test/bases.py @@ -1,13 +1,16 @@ import itertools import logging +import math import os import threading import typing import unittest from collections.abc import Iterable from contextlib import contextmanager +from copy import deepcopy +from typing import Optional, Dict, Union, Any, List, Iterable -from BaseClasses import get_seed, MultiWorld, Location, Item, Region, CollectionState, Entrance +from BaseClasses import get_seed, MultiWorld, Location, Item, Region, Entrance, CollectionState from test.bases import WorldTestBase from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld from worlds.AutoWorld import call_all @@ -77,7 +80,7 @@ def world_setup(self, *args, **kwargs): world = self.multiworld.worlds[self.player] self.original_state = self.multiworld.state.copy() - self.original_itempool = self.multiworld.itempool.copy() + self.original_itempool = deepcopy(self.multiworld.itempool) self.unfilled_locations = self.multiworld.get_unfilled_locations(1) if self.constructed: self.world = world # noqa @@ -99,7 +102,8 @@ def run_default_tests(self) -> bool: def collect_months(self, months: int) -> None: real_total_prog_items = self.world.total_progression_items percent = months * MONTH_COEFFICIENT - self.collect("Stardrop", real_total_prog_items * 100 // percent) + number_stardrops = math.ceil(real_total_prog_items * (percent / 100)) + self.collect("Stardrop", number_stardrops) self.world.total_progression_items = real_total_prog_items def collect_lots_of_money(self, percent: float = 0.25): @@ -112,12 +116,12 @@ def collect_all_the_money(self): self.collect_lots_of_money(0.95) def collect_everything(self): - non_event_items = [item for item in self.multiworld.get_items() if item.code] + non_event_items = [i for i in self.multiworld.get_items() if i.advancement and i.code] for item in non_event_items: self.multiworld.state.collect(item) def collect_all_except(self, item_to_not_collect: str): - non_event_items = [item for item in self.multiworld.get_items() if item.code] + non_event_items = [i for i in self.multiworld.get_items() if i.advancement and i.code] for item in non_event_items: if item.name != item_to_not_collect: self.multiworld.state.collect(item) @@ -189,6 +193,16 @@ def assert_cannot_reach_region(self, region: Region | str, state: CollectionStat state = self.multiworld.state return super().assert_cannot_reach_region(region, state) + def assert_can_reach_region(self, region: Region | str, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_can_reach_region(region, state) + + def assert_cannot_reach_region(self, region: Region | str, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_cannot_reach_region(region, state) + def assert_can_reach_entrance(self, entrance: Entrance | str, state: CollectionState | None = None) -> None: if state is None: state = self.multiworld.state @@ -211,7 +225,7 @@ def solo_multiworld(world_options: dict[str | type[StardewValleyOption], typing. try: multiworld.lock.acquire() original_state = multiworld.state.copy() - original_itempool = multiworld.itempool.copy() + original_itempool = deepcopy(multiworld.itempool) unfilled_locations = multiworld.get_unfilled_locations(1) yield multiworld, typing.cast(StardewValleyWorld, multiworld.worlds[1]) diff --git a/worlds/stardew_valley/test/content/__init__.py b/worlds/stardew_valley/test/content/__init__.py index 626277ca7206..a672723c9a45 100644 --- a/worlds/stardew_valley/test/content/__init__.py +++ b/worlds/stardew_valley/test/content/__init__.py @@ -10,8 +10,10 @@ feature.cropsanity.CropsanityDisabled(), feature.fishsanity.FishsanityNone(), feature.friendsanity.FriendsanityNone(), + feature.hatsanity.HatsanityNone(), + feature.museumsanity.MuseumsanityNone(), feature.skill_progression.SkillProgressionVanilla(), - feature.tool_progression.ToolProgressionVanilla() + feature.tool_progression.ToolProgressionVanilla(), ) diff --git a/worlds/stardew_valley/test/content/feature/TestToolProgression.py b/worlds/stardew_valley/test/content/feature/TestToolProgression.py index 618c78dd7a92..f815c434e67a 100644 --- a/worlds/stardew_valley/test/content/feature/TestToolProgression.py +++ b/worlds/stardew_valley/test/content/feature/TestToolProgression.py @@ -1,7 +1,7 @@ import unittest from ....content import choose_tool_progression -from ....options import ToolProgression, SkillProgression +from ....options import ToolProgression, SkillProgression, StartWithout from ....strings.tool_names import Tool @@ -10,8 +10,9 @@ class TestToolDistribution(unittest.TestCase): def test_given_vanilla_tool_progression_when_create_feature_then_only_one_scythe_is_randomized(self): tool_progression = ToolProgression(ToolProgression.option_vanilla) skill_progression = SkillProgression.from_text("random") + start_without = StartWithout(StartWithout.preset_none) - feature = choose_tool_progression(tool_progression, skill_progression) + feature = choose_tool_progression(tool_progression, skill_progression, start_without) self.assertEqual(feature.tool_distribution, { Tool.scythe: 1, @@ -20,8 +21,9 @@ def test_given_vanilla_tool_progression_when_create_feature_then_only_one_scythe def test_given_progressive_tool_when_create_feature_then_all_tool_upgrades_are_randomized(self): tool_progression = ToolProgression(ToolProgression.option_progressive) skill_progression = SkillProgression(SkillProgression.option_progressive) + start_without = StartWithout(StartWithout.preset_none) - feature = choose_tool_progression(tool_progression, skill_progression) + feature = choose_tool_progression(tool_progression, skill_progression, start_without) self.assertEqual(feature.tool_distribution, { Tool.scythe: 1, @@ -37,8 +39,9 @@ def test_given_progressive_tool_when_create_feature_then_all_tool_upgrades_are_r def test_given_progressive_tool_and_skill_masteries_when_create_feature_then_additional_scythe_and_fishing_rod_are_randomized(self): tool_progression = ToolProgression(ToolProgression.option_progressive) skill_progression = SkillProgression(SkillProgression.option_progressive_with_masteries) + start_without = StartWithout(StartWithout.preset_none) - feature = choose_tool_progression(tool_progression, skill_progression) + feature = choose_tool_progression(tool_progression, skill_progression, start_without) self.assertEqual(feature.tool_distribution, { Tool.scythe: 2, diff --git a/worlds/stardew_valley/test/long/TestModsLong.py b/worlds/stardew_valley/test/long/TestModsLong.py index d14af8bc5501..c03df3fb1cc9 100644 --- a/worlds/stardew_valley/test/long/TestModsLong.py +++ b/worlds/stardew_valley/test/long/TestModsLong.py @@ -8,7 +8,7 @@ from ..bases import skip_long_tests, SVTestCase, solo_multiworld from ..options.option_names import all_option_choices from ... import options -from ...mods.mod_data import ModNames +from ...mods.mod_data import ModNames, mod_combination_is_valid from ...options.options import all_mods @@ -36,8 +36,11 @@ class TestGenerateModsPairs(WorldAssertMixin, ModAssertMixin, SVTestCase): mod_pair: ClassVar[tuple[str, str]] def test_given_mod_pairs_when_generate_then_basic_checks(self): + mods = frozenset(self.mod_pair) + if not mod_combination_is_valid(mods): + return world_options = { - options.Mods.internal_name: frozenset(self.mod_pair) + options.Mods.internal_name: mods } with solo_multiworld(world_options, world_caching=False) as (multiworld, _): @@ -77,7 +80,7 @@ def test_given_no_quest_all_mods_when_generate_with_all_goals_then_basic_checks( options.Goal.internal_name: self.goal, option: choice, options.QuestLocations.internal_name: -1, - options.Mods.internal_name: frozenset(options.Mods.valid_keys), + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), } with solo_multiworld(world_options, world_caching=False) as (multiworld, _): diff --git a/worlds/stardew_valley/test/long/TestOptionsLong.py b/worlds/stardew_valley/test/long/TestOptionsLong.py index 3c9690e2e6bf..d04e80a03b7c 100644 --- a/worlds/stardew_valley/test/long/TestOptionsLong.py +++ b/worlds/stardew_valley/test/long/TestOptionsLong.py @@ -15,8 +15,8 @@ class TestDynamicOptionDebug(WorldAssertMixin, SVTestCase): def test_option_pair_debug(self): option_dict = { - options.Goal.internal_name: options.Goal.option_cryptic_note, - options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_fewer, + options.Hatsanity.internal_name: options.Hatsanity.option_post_perfection, } for i in range(1): seed = get_seed(76312028554502615508) diff --git a/worlds/stardew_valley/test/mods/TestBiggerBackpack.py b/worlds/stardew_valley/test/mods/TestBiggerBackpack.py index 8ec2e539b7e1..b057fe05672d 100644 --- a/worlds/stardew_valley/test/mods/TestBiggerBackpack.py +++ b/worlds/stardew_valley/test/mods/TestBiggerBackpack.py @@ -1,11 +1,12 @@ from ..bases import SVTestBase from ...mods.mod_data import ModNames -from ...options import Mods, BackpackProgression +from ...options import Mods, BackpackProgression, BackpackSize class TestBiggerBackpackVanilla(SVTestBase): options = { BackpackProgression.internal_name: BackpackProgression.option_vanilla, + BackpackSize.internal_name: BackpackSize.option_12, Mods.internal_name: ModNames.big_backpack } @@ -24,7 +25,8 @@ def test_no_backpack(self): class TestBiggerBackpackProgressive(SVTestBase): options = { BackpackProgression.internal_name: BackpackProgression.option_progressive, - Mods.internal_name: ModNames.big_backpack + BackpackSize.internal_name: BackpackSize.option_12, + Mods.internal_name: ModNames.big_backpack, } def test_backpack(self): @@ -42,6 +44,7 @@ def test_backpack(self): class TestBiggerBackpackEarlyProgressive(TestBiggerBackpackProgressive): options = { BackpackProgression.internal_name: BackpackProgression.option_early_progressive, + BackpackSize.internal_name: BackpackSize.option_12, Mods.internal_name: ModNames.big_backpack } @@ -50,3 +53,99 @@ def test_backpack(self): with self.subTest(check="is early"): self.assertIn("Progressive Backpack", self.multiworld.early_items[1]) + + +class TestBiggerBackpackSplit1(SVTestBase): + options = {BackpackProgression.internal_name: BackpackProgression.option_progressive, + BackpackSize.internal_name: BackpackSize.option_1, + Mods.internal_name: ModNames.big_backpack} + + def test_backpack(self): + with self.subTest(check="has items"): + item_names = [item.name for item in self.multiworld.get_items()] + self.assertEqual(item_names.count("Progressive Backpack"), 36) + + with self.subTest(check="has locations"): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + self.assertNotIn("Premium Pack", location_names) + for i in range(1, 13): + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + self.assertIn(f"Premium Pack {i}", location_names) + + +class TestBackpackSplit2(SVTestBase): + options = {BackpackProgression.internal_name: BackpackProgression.option_progressive, + BackpackSize.internal_name: BackpackSize.option_2, + Mods.internal_name: ModNames.big_backpack} + + def test_backpack(self): + with self.subTest(check="has items"): + item_names = [item.name for item in self.multiworld.get_items()] + self.assertEqual(item_names.count("Progressive Backpack"), 18) + + with self.subTest(check="has locations"): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + self.assertNotIn("Premium Pack", location_names) + for i in range(1, 7): + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + self.assertIn(f"Premium Pack {i}", location_names) + for i in range(7, 13): + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + self.assertNotIn(f"Premium Pack {i}", location_names) + + +class TestBackpackSplit4(SVTestBase): + options = {BackpackProgression.internal_name: BackpackProgression.option_progressive, + BackpackSize.internal_name: BackpackSize.option_4, + Mods.internal_name: ModNames.big_backpack} + + def test_backpack(self): + with self.subTest(check="has items"): + item_names = [item.name for item in self.multiworld.get_items()] + self.assertEqual(item_names.count("Progressive Backpack"), 9) + + with self.subTest(check="has locations"): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + self.assertNotIn("Premium Pack", location_names) + for i in range(1, 4): + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + self.assertIn(f"Premium Pack {i}", location_names) + for i in range(4, 13): + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + self.assertNotIn(f"Premium Pack {i}", location_names) + + +class TestBackpackSplit6(SVTestBase): + options = {BackpackProgression.internal_name: BackpackProgression.option_progressive, + BackpackSize.internal_name: BackpackSize.option_6, + Mods.internal_name: ModNames.big_backpack} + + def test_backpack(self): + with self.subTest(check="has items"): + item_names = [item.name for item in self.multiworld.get_items()] + self.assertEqual(item_names.count("Progressive Backpack"), 6) + + with self.subTest(check="has locations"): + location_names = {location.name for location in self.multiworld.get_locations()} + self.assertNotIn("Large Pack", location_names) + self.assertNotIn("Deluxe Pack", location_names) + self.assertNotIn("Premium Pack", location_names) + for i in range(1, 3): + self.assertIn(f"Large Pack {i}", location_names) + self.assertIn(f"Deluxe Pack {i}", location_names) + self.assertIn(f"Premium Pack {i}", location_names) + for i in range(3, 13): + self.assertNotIn(f"Large Pack {i}", location_names) + self.assertNotIn(f"Deluxe Pack {i}", location_names) + self.assertNotIn(f"Premium Pack {i}", location_names) diff --git a/worlds/stardew_valley/test/mods/TestModdedBooksanity.py b/worlds/stardew_valley/test/mods/TestModdedBooksanity.py new file mode 100644 index 000000000000..2da2c4d63f0f --- /dev/null +++ b/worlds/stardew_valley/test/mods/TestModdedBooksanity.py @@ -0,0 +1,185 @@ +from ..bases import SVTestBase +from ...options import ExcludeGingerIsland, Booksanity, Shipsanity, Mods, all_mods_except_invalid_combinations +from ...strings.ap_names.mods.mod_items import ModBooks + +ModSkillBooks = [ModBooks.digging_like_worms] +ModPowerBooks = [] + + +class TestModBooksanityNone(SVTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Shipsanity: Shipsanity.option_everything, + Booksanity: Booksanity.option_none, + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), + } + + def test_no_mod_power_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertNotIn(f"Read {book}", location_names) + + def test_no_mod_skill_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModSkillBooks: + with self.subTest(book): + self.assertNotIn(f"Read {book}", location_names) + + def test_no_power_items(self): + item_names = {location.name for location in self.multiworld.get_items()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertNotIn(f"Power: {book}", item_names) + + def test_can_ship_all_mod_books(self): + self.collect_everything() + shipsanity_prefix = "Shipsanity: " + for location in self.multiworld.get_locations(): + if not location.name.startswith(shipsanity_prefix): + continue + + item_to_ship = location.name[len(shipsanity_prefix):] + if item_to_ship not in ModPowerBooks and item_to_ship not in ModSkillBooks: + continue + + with self.subTest(location.name): + self.assert_can_reach_location(location) + + +class TestModBooksanityPowers(SVTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Shipsanity: Shipsanity.option_everything, + Booksanity: Booksanity.option_power, + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), + } + + def test_all_modp_ower_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertIn(f"Read {book}", location_names) + + def test_no_mod_skill_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModSkillBooks: + with self.subTest(book): + self.assertNotIn(f"Read {book}", location_names) + + def test_all_power_items(self): + item_names = {location.name for location in self.multiworld.get_items()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertIn(f"Power: {book}", item_names) + + def test_can_ship_all_books(self): + self.collect_everything() + shipsanity_prefix = "Shipsanity: " + for location in self.multiworld.get_locations(): + if not location.name.startswith(shipsanity_prefix): + continue + + item_to_ship = location.name[len(shipsanity_prefix):] + if item_to_ship not in ModPowerBooks and item_to_ship not in ModSkillBooks: + continue + + with self.subTest(location.name): + self.assert_can_reach_location(location) + + +class TestBooksanityPowersAndSkills(SVTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Shipsanity: Shipsanity.option_everything, + Booksanity: Booksanity.option_power_skill, + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), + } + + def test_all_mod_power_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertIn(f"Read {book}", location_names) + + def test_all_mod_skill_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModSkillBooks: + with self.subTest(book): + self.assertIn(f"Read {book}", location_names) + + def test_all_power_items(self): + item_names = {location.name for location in self.multiworld.get_items()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertIn(f"Power: {book}", item_names) + + def test_can_ship_all_books(self): + self.collect_everything() + shipsanity_prefix = "Shipsanity: " + for location in self.multiworld.get_locations(): + if not location.name.startswith(shipsanity_prefix): + continue + + item_to_ship = location.name[len(shipsanity_prefix):] + if item_to_ship not in ModPowerBooks and item_to_ship not in ModSkillBooks: + continue + + with self.subTest(location.name): + self.assert_can_reach_location(location) + + +class TestBooksanityAll(SVTestBase): + options = { + ExcludeGingerIsland: ExcludeGingerIsland.option_false, + Shipsanity: Shipsanity.option_everything, + Booksanity: Booksanity.option_all, + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), + } + + def test_digging_like_worms_require_2_levels(self): + read_location = self.world.get_location("Read Digging Like Worms") + ship_location = self.world.get_location("Shipsanity: Digging Like Worms") + self.collect("Shipping Bin") + self.collect_months(2) + + self.assert_cannot_reach_location(read_location) + self.assert_cannot_reach_location(ship_location) + + self.collect("Archaeology Level") + self.collect("Archaeology Level") + + self.assert_can_reach_location(read_location) + self.assert_can_reach_location(ship_location) + + def test_all_mod_power_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertIn(f"Read {book}", location_names) + + def test_all_mod_skill_books_locations(self): + location_names = {location.name for location in self.multiworld.get_locations()} + for book in ModSkillBooks: + with self.subTest(book): + self.assertIn(f"Read {book}", location_names) + + def test_all_power_items(self): + item_names = {location.name for location in self.multiworld.get_items()} + for book in ModPowerBooks: + with self.subTest(book): + self.assertIn(f"Power: {book}", item_names) + + def test_can_ship_all_books(self): + self.collect_everything() + shipsanity_prefix = "Shipsanity: " + for location in self.multiworld.get_locations(): + if not location.name.startswith(shipsanity_prefix): + continue + + item_to_ship = location.name[len(shipsanity_prefix):] + if item_to_ship not in ModPowerBooks and item_to_ship not in ModSkillBooks: + continue + + with self.subTest(location.name): + self.assert_can_reach_location(location) diff --git a/worlds/stardew_valley/test/mods/TestMods.py b/worlds/stardew_valley/test/mods/TestMods.py index 8cff10b4fc3b..a447f0677450 100644 --- a/worlds/stardew_valley/test/mods/TestMods.py +++ b/worlds/stardew_valley/test/mods/TestMods.py @@ -1,66 +1,47 @@ -from typing import ClassVar - -from test.param import classvar_matrix from ..TestGeneration import get_all_permanent_progression_items from ..assertion import ModAssertMixin, WorldAssertMixin from ..bases import SVTestCase, SVTestBase, solo_multiworld -from ..options.presets import allsanity_mods_6_x_x -from ... import options, Group -from ...mods.mod_data import ModNames +from ..options.presets import allsanity_mods_7_x_x +from ... import options +from ...items import Group +from ...mods.mod_data import invalid_mod_combinations from ...options.options import all_mods class TestCanGenerateAllsanityWithMods(WorldAssertMixin, ModAssertMixin, SVTestCase): def test_allsanity_all_mods_when_generate_then_basic_checks(self): - with solo_multiworld(allsanity_mods_6_x_x()) as (multi_world, _): + with solo_multiworld(allsanity_mods_7_x_x()) as (multi_world, _): self.assert_basic_checks(multi_world) def test_allsanity_all_mods_exclude_island_when_generate_then_basic_checks(self): - world_options = allsanity_mods_6_x_x() + world_options = allsanity_mods_7_x_x() world_options.update({options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true}) with solo_multiworld(world_options) as (multi_world, _): self.assert_basic_checks(multi_world) -@classvar_matrix(mod=all_mods) class TestCanGenerateWithEachMod(WorldAssertMixin, ModAssertMixin, SVTestCase): - mod: ClassVar[str] + mods = all_mods def test_given_single_mods_when_generate_then_basic_checks(self): - world_options = { - options.Mods: self.mod, - options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false - } - with solo_multiworld(world_options) as (multi_world, _): - self.assert_basic_checks(multi_world) - self.assert_stray_mod_items(self.mod, multi_world) - - -@classvar_matrix(mod=all_mods.difference([ - ModNames.ginger, ModNames.distant_lands, ModNames.skull_cavern_elevator, ModNames.wellwick, ModNames.magic, ModNames.binning_skill, ModNames.big_backpack, - ModNames.luck_skill, ModNames.tractor, ModNames.shiko, ModNames.archaeology, ModNames.delores, ModNames.socializing_skill, ModNames.cooking_skill -])) -class TestCanGenerateEachModWithEntranceRandomizationBuildings(WorldAssertMixin, SVTestCase): - """The following tests validate that ER still generates winnable and logically-sane games with given mods. - Mods that do not interact with entrances are skipped - Not all ER settings are tested, because 'buildings' is, essentially, a superset of all others - """ - mod: ClassVar[str] - - def test_given_mod_when_generate_then_basic_checks(self) -> None: - world_options = { - options.EntranceRandomization: options.EntranceRandomization.option_buildings, - options.Mods: self.mod, - options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false - } - with solo_multiworld(world_options, world_caching=False) as (multi_world, _): - self.assert_basic_checks(multi_world) + for invalid_combination in invalid_mod_combinations: + for mod in invalid_combination: + mods = set(self.mods).difference(invalid_combination) + mods.add(mod) + with self.subTest(f"Can generate with mods: {mods}"): + world_options = { + options.Mods: frozenset(mods), + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false + } + with solo_multiworld(world_options) as (multi_world, _): + self.assert_basic_checks(multi_world) + self.assert_stray_mod_items(mods, multi_world) class TestBaseLocationDependencies(SVTestBase): options = { - options.Mods.internal_name: frozenset(options.Mods.valid_keys), + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), options.ToolProgression.internal_name: options.ToolProgression.option_progressive, options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized } @@ -74,11 +55,15 @@ class TestBaseItemGeneration(SVTestBase): options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, options.Shipsanity.internal_name: options.Shipsanity.option_everything, - options.Chefsanity.internal_name: options.Chefsanity.option_all, + options.Chefsanity.internal_name: options.Chefsanity.preset_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, options.Booksanity.internal_name: options.Booksanity.option_all, options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, - options.Mods.internal_name: frozenset(options.Mods.valid_keys) + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies_and_all_loved_snacks, + options.Eatsanity.internal_name: options.Eatsanity.preset_all, + options.Secretsanity.internal_name: options.Secretsanity.preset_all, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_true, + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), } def test_all_progression_items_are_added_to_the_pool(self): @@ -95,11 +80,15 @@ class TestNoGingerIslandModItemGeneration(SVTestBase): options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, options.Shipsanity.internal_name: options.Shipsanity.option_everything, - options.Chefsanity.internal_name: options.Chefsanity.option_all, + options.Chefsanity.internal_name: options.Chefsanity.preset_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, options.Booksanity.internal_name: options.Booksanity.option_all, + options.Secretsanity.internal_name: options.Secretsanity.preset_all, + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies_and_all_loved_snacks, + options.Eatsanity.internal_name: options.Eatsanity.preset_all, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.Mods.internal_name: frozenset(options.Mods.valid_keys) + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_true, + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), } def test_all_progression_items_except_island_are_added_to_the_pool(self): @@ -117,7 +106,7 @@ class TestVanillaLogicAlternativeWhenQuestsAreNotRandomized(WorldAssertMixin, SV """We often forget to add an alternative rule that works when quests are not randomized. When this happens, some Location are not reachable because they depend on items that are only added to the pool when quests are randomized. """ - options = allsanity_mods_6_x_x() | { + options = allsanity_mods_7_x_x() | { options.QuestLocations.internal_name: options.QuestLocations.special_range_names["none"], options.Goal.internal_name: options.Goal.option_perfection, } diff --git a/worlds/stardew_valley/test/mods/TestModsFill.py b/worlds/stardew_valley/test/mods/TestModsFill.py index 334a4ff9e47d..821007aee4be 100644 --- a/worlds/stardew_valley/test/mods/TestModsFill.py +++ b/worlds/stardew_valley/test/mods/TestModsFill.py @@ -7,22 +7,14 @@ class TestNoGingerIslandCraftingRecipesAreRequired(SVTestBase): options.Goal.internal_name: options.Goal.option_craft_master, options.Craftsanity.internal_name: options.Craftsanity.option_all, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.Mods.internal_name: frozenset(options.Mods.valid_keys) + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), } - @property - def run_default_tests(self) -> bool: - return True - class TestNoGingerIslandCookingRecipesAreRequired(SVTestBase): options = { options.Goal.internal_name: options.Goal.option_gourmet_chef, options.Cooksanity.internal_name: options.Cooksanity.option_all, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, - options.Mods.internal_name: frozenset(options.Mods.valid_keys) + options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations), } - - @property - def run_default_tests(self) -> bool: - return True diff --git a/worlds/stardew_valley/test/mods/TestSVE.py b/worlds/stardew_valley/test/mods/TestSVE.py index a6b6f6a3dc99..a3ff2474a520 100644 --- a/worlds/stardew_valley/test/mods/TestSVE.py +++ b/worlds/stardew_valley/test/mods/TestSVE.py @@ -2,6 +2,7 @@ from ... import options from ...mods.mod_data import ModNames from ...strings.ap_names.mods.mod_items import SVEQuestItem +from ...strings.ap_names.transport_names import Transportation from ...strings.quest_names import ModQuest from ...strings.region_names import SVERegion @@ -9,15 +10,15 @@ class TestAuroraVineyard(SVTestBase): options = { options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.Mods.internal_name: frozenset({ModNames.sve}) + options.Mods.internal_name: frozenset({ModNames.sve}), } def test_need_tablet_to_do_quest(self): self.collect("Starfruit Seeds") - self.collect("Bus Repair") + self.collect(Transportation.bus_repair) self.collect("Shipping Bin") self.collect("Summer") - location_name = ModQuest.AuroraVineyard + location_name = f"Quest: {ModQuest.AuroraVineyard}" self.assert_cannot_reach_location(location_name, self.multiworld.state) self.collect(SVEQuestItem.aurora_vineyard_tablet) self.assert_can_reach_location(location_name, self.multiworld.state) diff --git a/worlds/stardew_valley/test/options/option_names.py b/worlds/stardew_valley/test/options/option_names.py index 07fa42b50862..45ad633c70ec 100644 --- a/worlds/stardew_valley/test/options/option_names.py +++ b/worlds/stardew_valley/test/options/option_names.py @@ -1,13 +1,24 @@ import random +from typing import Iterable from Options import NamedRange, Option, Range from ... import StardewValleyWorld from ...options import StardewValleyOption -options_to_exclude = {"profit_margin", "starting_money", "multiple_day_sleep_enabled", "multiple_day_sleep_cost", +options_to_exclude = {"profit_margin", "starting_money", + "multiple_day_sleep_enabled", "multiple_day_sleep_cost", "experience_multiplier", "friendship_multiplier", "debris_multiplier", - "quick_start", "gifting", "gift_tax", - "progression_balancing", "accessibility", "start_inventory", "start_hints", "death_link"} + "quick_start", "gifting", + "movement_buff_number", "enabled_filler_buffs", "trap_difficulty", "trap_distribution", + "bundle_plando", "trap_items", + "progression_balancing", "accessibility", + "start_inventory", "local_items", "non_local_items", "exclude_locations", "priority_locations", + "start_hints", "start_location_hints", "item_links", "plando_items", + "death_link", + "jojapocalypse", "joja_start_price", "joja_end_price", "joja_pricing_pattern", "joja_purchases_for_membership", "joja_are_you_sure"} + +for option in options_to_exclude: + assert option in StardewValleyWorld.options_dataclass.type_hints.keys(), f"Excluding an option that doesn't exist: {option}" options_to_include: list[type[StardewValleyOption | Option]] = [ option @@ -20,7 +31,10 @@ def get_option_choices(option: type[Option]) -> dict[str, int]: if issubclass(option, NamedRange): return option.special_range_names if issubclass(option, Range): - return {f"{val}": val for val in range(option.range_start, option.range_end + 1)} + range_size = option.range_end - option.range_start + max_steps = 10 + step = max(1, range_size // max_steps) + return {f"{val}": val for val in range(option.range_start, option.range_end + 1, step)} elif option.options: return option.options return {} @@ -40,12 +54,18 @@ def generate_random_world_options(seed: int) -> dict[str, int]: return world_options -all_option_choices = [ - (option.internal_name, value) - for option in options_to_include - if option.options - for value in get_option_choices(option) - if option.default != get_option_choices(option)[value] -] +all_option_choices = [] +for option in options_to_include: + if option.options: + option_choices = get_option_choices(option) + for choice_name, choice_value in option_choices.items(): + if option.default != choice_value: + all_option_choices.append((option.internal_name, choice_name)) assert all_option_choices + + +def get_all_option_choices(extra_ignored: Iterable[str] = None): + if extra_ignored is None: + return all_option_choices + return [option_choice for option_choice in all_option_choices if option_choice[0] not in extra_ignored] diff --git a/worlds/stardew_valley/test/options/presets.py b/worlds/stardew_valley/test/options/presets.py index 86b21c693e69..92aab191dec9 100644 --- a/worlds/stardew_valley/test/options/presets.py +++ b/worlds/stardew_valley/test/options/presets.py @@ -1,10 +1,12 @@ from ... import options +from ...strings.ap_names.ap_option_names import EatsanityOptionName def default_6_x_x(): return { options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.default, options.BackpackProgression.internal_name: options.BackpackProgression.default, + options.BackpackSize.internal_name: options.BackpackSize.option_12, options.Booksanity.internal_name: options.Booksanity.default, options.BuildingProgression.internal_name: options.BuildingProgression.default, options.BundlePrice.internal_name: options.BundlePrice.default, @@ -13,7 +15,9 @@ def default_6_x_x(): options.Cooksanity.internal_name: options.Cooksanity.default, options.Craftsanity.internal_name: options.Craftsanity.default, options.Cropsanity.internal_name: options.Cropsanity.default, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, options.ElevatorProgression.internal_name: options.ElevatorProgression.default, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, options.EntranceRandomization.internal_name: options.EntranceRandomization.default, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.default, options.FarmType.internal_name: options.FarmType.default, @@ -22,19 +26,22 @@ def default_6_x_x(): options.Friendsanity.internal_name: options.Friendsanity.default, options.FriendsanityHeartSize.internal_name: options.FriendsanityHeartSize.default, options.Goal.internal_name: options.Goal.default, + options.Hatsanity.internal_name: options.Hatsanity.preset_none, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, options.Mods.internal_name: options.Mods.default, options.Monstersanity.internal_name: options.Monstersanity.default, + options.Moviesanity.internal_name: options.Moviesanity.option_none, options.Museumsanity.internal_name: options.Museumsanity.default, options.NumberOfMovementBuffs.internal_name: options.NumberOfMovementBuffs.default, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, options.QuestLocations.internal_name: options.QuestLocations.default, options.SeasonRandomization.internal_name: options.SeasonRandomization.default, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, options.Shipsanity.internal_name: options.Shipsanity.default, options.SkillProgression.internal_name: options.SkillProgression.default, options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.default, options.ToolProgression.internal_name: options.ToolProgression.default, - options.TrapItems.internal_name: options.TrapItems.default, - options.Walnutsanity.internal_name: options.Walnutsanity.default + options.TrapDifficulty.internal_name: options.TrapDifficulty.default, + options.Walnutsanity.internal_name: options.Walnutsanity.default, } @@ -42,15 +49,18 @@ def allsanity_no_mods_6_x_x(): return { options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_12, options.Booksanity.internal_name: options.Booksanity.option_all, options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, options.BundlePrice.internal_name: options.BundlePrice.option_expensive, options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic, - options.Chefsanity.internal_name: options.Chefsanity.option_all, + options.Chefsanity.internal_name: options.Chefsanity.preset_all, options.Cooksanity.internal_name: options.Cooksanity.option_all, options.Craftsanity.internal_name: options.Craftsanity.option_all, options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, options.FarmType.internal_name: options.FarmType.option_standard, @@ -59,31 +69,134 @@ def allsanity_no_mods_6_x_x(): options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, options.FriendsanityHeartSize.internal_name: 1, options.Goal.internal_name: options.Goal.option_perfection, + options.Hatsanity.internal_name: options.Hatsanity.preset_none, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, options.Mods.internal_name: frozenset(), options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, + options.Moviesanity.internal_name: options.Moviesanity.option_none, options.Museumsanity.internal_name: options.Museumsanity.option_all, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, options.NumberOfMovementBuffs.internal_name: 12, options.QuestLocations.internal_name: 56, options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, options.Shipsanity.internal_name: options.Shipsanity.option_everything, options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, options.ToolProgression.internal_name: options.ToolProgression.option_progressive, options.TrapDifficulty.internal_name: options.TrapDifficulty.option_nightmare, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_all + options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, } def allsanity_mods_6_x_x_exclude_disabled(): allsanity = allsanity_no_mods_6_x_x() - allsanity.update({options.Mods.internal_name: frozenset(options.enabled_mods)}) + allsanity.update({options.Mods.internal_name: frozenset(options.enabled_mods_except_invalid_combinations)}) return allsanity def allsanity_mods_6_x_x(): allsanity = allsanity_no_mods_6_x_x() - allsanity.update({options.Mods.internal_name: frozenset(options.all_mods)}) + allsanity.update({options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations)}) + return allsanity + + +def default_7_x_x(): + return { + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.default, + options.BackpackProgression.internal_name: options.BackpackProgression.default, + options.BackpackSize.internal_name: options.BackpackSize.default, + options.Booksanity.internal_name: options.Booksanity.default, + options.BuildingProgression.internal_name: options.BuildingProgression.default, + options.BundlePerRoom.internal_name: options.BundlePerRoom.default, + options.BundlePrice.internal_name: options.BundlePrice.default, + options.BundleRandomization.internal_name: options.BundleRandomization.default, + options.Chefsanity.internal_name: options.Chefsanity.default, + options.Cooksanity.internal_name: options.Cooksanity.default, + options.Craftsanity.internal_name: options.Craftsanity.default, + options.Cropsanity.internal_name: options.Cropsanity.default, + options.Eatsanity.internal_name: options.Eatsanity.default, + options.ElevatorProgression.internal_name: options.ElevatorProgression.default, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.default, + options.EntranceRandomization.internal_name: options.EntranceRandomization.default, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.default, + options.FestivalLocations.internal_name: options.FestivalLocations.default, + options.Fishsanity.internal_name: options.Fishsanity.default, + options.Friendsanity.internal_name: options.Friendsanity.default, + options.FriendsanityHeartSize.internal_name: options.FriendsanityHeartSize.default, + options.Goal.internal_name: options.Goal.default, + options.Hatsanity.internal_name: options.Hatsanity.default, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.default, + options.Mods.internal_name: options.Mods.default, + options.Monstersanity.internal_name: options.Monstersanity.default, + options.Moviesanity.internal_name: options.Moviesanity.option_none, + options.Museumsanity.internal_name: options.Museumsanity.default, + options.NumberOfMovementBuffs.internal_name: options.NumberOfMovementBuffs.default, + options.QuestLocations.internal_name: options.QuestLocations.default, + options.SeasonRandomization.internal_name: options.SeasonRandomization.default, + options.Secretsanity.internal_name: options.Secretsanity.default, + options.Shipsanity.internal_name: options.Shipsanity.default, + options.SkillProgression.internal_name: options.SkillProgression.default, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.default, + options.ToolProgression.internal_name: options.ToolProgression.default, + options.TrapDifficulty.internal_name: options.TrapDifficulty.default, + options.Walnutsanity.internal_name: options.Walnutsanity.default, + } + + +def allsanity_no_mods_7_x_x(): + return { + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, + options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.BackpackSize.internal_name: options.BackpackSize.option_1, + options.Booksanity.internal_name: options.Booksanity.option_all, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_extra, + options.BundlePrice.internal_name: options.BundlePrice.option_expensive, + options.BundleRandomization.internal_name: options.BundleRandomization.option_thematic, + options.Chefsanity.internal_name: options.Chefsanity.preset_all, + options.Cooksanity.internal_name: options.Cooksanity.option_all, + options.Craftsanity.internal_name: options.Craftsanity.option_all, + options.Cropsanity.internal_name: options.Cropsanity.option_enabled, + options.Eatsanity.internal_name: options.Eatsanity.preset_all, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.FarmType.internal_name: options.FarmType.option_beach, # There is one extra fishing secret on the beach farm + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, + options.Fishsanity.internal_name: options.Fishsanity.option_all, + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 1, + options.Goal.internal_name: options.Goal.option_perfection, + options.Hatsanity.internal_name: options.Hatsanity.preset_all, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_true, + options.Mods.internal_name: frozenset(), + options.Monstersanity.internal_name: options.Monstersanity.option_progressive_goals, + options.Moviesanity.internal_name: options.Moviesanity.option_all_movies_and_all_loved_snacks, + options.Museumsanity.internal_name: options.Museumsanity.option_all, + options.NumberOfMovementBuffs.internal_name: 12, + options.QuestLocations.internal_name: 56, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Secretsanity.internal_name: options.Secretsanity.preset_all, + options.Shipsanity.internal_name: options.Shipsanity.option_everything, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.StartWithout.internal_name: options.StartWithout.preset_all, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive, + options.TrapDifficulty.internal_name: options.TrapDifficulty.option_nightmare, + options.Walnutsanity.internal_name: options.Walnutsanity.preset_all, + } + + +def allsanity_mods_7_x_x(): + allsanity = allsanity_no_mods_7_x_x() + allsanity.update({options.Mods.internal_name: frozenset(options.all_mods_except_invalid_combinations)}) + return allsanity + + +def allsanity_mods_7_x_x_exclude_disabled(): + allsanity = allsanity_no_mods_7_x_x() + allsanity.update({options.Mods.internal_name: frozenset(options.enabled_mods_except_invalid_combinations)}) return allsanity @@ -91,15 +204,19 @@ def get_minsanity_options(): return { options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla, + options.BackpackSize.internal_name: options.BackpackSize.option_12, options.Booksanity.internal_name: options.Booksanity.option_none, options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_fewer, options.BundlePrice.internal_name: options.BundlePrice.option_very_cheap, options.BundleRandomization.internal_name: options.BundleRandomization.option_vanilla, - options.Chefsanity.internal_name: options.Chefsanity.option_none, + options.Chefsanity.internal_name: options.Chefsanity.preset_none, options.Cooksanity.internal_name: options.Cooksanity.option_none, options.Craftsanity.internal_name: options.Craftsanity.option_none, options.Cropsanity.internal_name: options.Cropsanity.option_disabled, + options.Eatsanity.internal_name: options.Eatsanity.preset_none, options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_none, options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, options.FarmType.internal_name: options.FarmType.option_meadowlands, @@ -108,19 +225,21 @@ def get_minsanity_options(): options.Friendsanity.internal_name: options.Friendsanity.option_none, options.FriendsanityHeartSize.internal_name: 8, options.Goal.internal_name: options.Goal.option_bottom_of_the_mines, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, options.Mods.internal_name: frozenset(), options.Monstersanity.internal_name: options.Monstersanity.option_none, + options.Moviesanity.internal_name: options.Moviesanity.option_none, options.Museumsanity.internal_name: options.Museumsanity.option_none, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_none, options.NumberOfMovementBuffs.internal_name: 0, options.QuestLocations.internal_name: -1, options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, options.Shipsanity.internal_name: options.Shipsanity.option_none, options.SkillProgression.internal_name: options.SkillProgression.option_vanilla, options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla, options.ToolProgression.internal_name: options.ToolProgression.option_vanilla, options.TrapDifficulty.internal_name: options.TrapDifficulty.option_no_traps, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_none + options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, } @@ -128,15 +247,19 @@ def minimal_locations_maximal_items(): min_max_options = { options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla, + options.BackpackSize.internal_name: options.BackpackSize.option_12, options.Booksanity.internal_name: options.Booksanity.option_none, options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla, + options.BundlePerRoom.internal_name: options.BundlePerRoom.option_two_fewer, options.BundlePrice.internal_name: options.BundlePrice.option_expensive, options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled, - options.Chefsanity.internal_name: options.Chefsanity.option_none, + options.Chefsanity.internal_name: options.Chefsanity.preset_none, options.Cooksanity.internal_name: options.Cooksanity.option_none, options.Craftsanity.internal_name: options.Craftsanity.option_none, options.Cropsanity.internal_name: options.Cropsanity.option_disabled, + options.Eatsanity.internal_name: frozenset([EatsanityOptionName.lock_effects]), options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, + options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, options.FarmType.internal_name: options.FarmType.option_meadowlands, @@ -145,19 +268,22 @@ def minimal_locations_maximal_items(): options.Friendsanity.internal_name: options.Friendsanity.option_none, options.FriendsanityHeartSize.internal_name: 8, options.Goal.internal_name: options.Goal.option_craft_master, + options.IncludeEndgameLocations.internal_name: options.IncludeEndgameLocations.option_false, options.Mods.internal_name: frozenset(), options.Monstersanity.internal_name: options.Monstersanity.option_none, + options.Moviesanity.internal_name: options.Moviesanity.option_none, options.Museumsanity.internal_name: options.Museumsanity.option_none, - options.EnabledFillerBuffs.internal_name: options.EnabledFillerBuffs.preset_all, options.NumberOfMovementBuffs.internal_name: 12, options.QuestLocations.internal_name: -1, options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Secretsanity.internal_name: options.Secretsanity.preset_none, options.Shipsanity.internal_name: options.Shipsanity.option_none, options.SkillProgression.internal_name: options.SkillProgression.option_vanilla, options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_vanilla, + options.StartWithout.internal_name: options.StartWithout.preset_all, options.ToolProgression.internal_name: options.ToolProgression.option_vanilla, options.TrapDifficulty.internal_name: options.TrapDifficulty.option_nightmare, - options.Walnutsanity.internal_name: options.Walnutsanity.preset_none + options.Walnutsanity.internal_name: options.Walnutsanity.preset_none, } return min_max_options diff --git a/worlds/stardew_valley/test/performance/TestPerformance.py b/worlds/stardew_valley/test/performance/TestPerformance.py index 2951e6d00a70..e52ed8f743ca 100644 --- a/worlds/stardew_valley/test/performance/TestPerformance.py +++ b/worlds/stardew_valley/test/performance/TestPerformance.py @@ -9,10 +9,8 @@ from Fill import distribute_items_restrictive, balance_multiworld_progression from worlds import AutoWorld from ..bases import SVTestCase, setup_multiworld -from ..options.presets import default_6_x_x, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x, minimal_locations_maximal_items - -assert default_6_x_x -assert allsanity_no_mods_6_x_x +from ..options.presets import default_6_x_x, allsanity_no_mods_6_x_x, minimal_locations_maximal_items, allsanity_mods_7_x_x, allsanity_no_mods_7_x_x, \ + default_7_x_x default_number_generations = 25 acceptable_deviation = 4 @@ -99,7 +97,7 @@ def performance_test_multiworld(self, options): acceptable_average_time = self.acceptable_time_per_player * amount_of_players total_time = 0 all_times = [] - seeds = [get_seed() for _ in range(self.number_generations)] if not self.fixed_seed else [85635032403287291967] * self.number_generations + seeds = [get_seed() for _ in range(self.number_generations)] if not self.fixed_seed else [74898321050311118687] * self.number_generations for i, seed in enumerate(seeds): with self.subTest(f"Seed: {seed}"): @@ -134,11 +132,39 @@ def size_name(number_players): return f"{number_players}-player" -class TestDefaultOptions(SVPerformanceTestCase): +class TestDefaultOptions6xx(SVPerformanceTestCase): acceptable_time_per_player = 2 options = default_6_x_x() results = [] + def test_solo(self): + number_players = 1 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + def test_duo(self): + number_players = 2 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + @unittest.skip + def test_5_player(self): + number_players = 5 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + @unittest.skip + def test_10_player(self): + number_players = 10 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + +class TestDefaultOptions7xx(SVPerformanceTestCase): + acceptable_time_per_player = 2 + options = default_7_x_x() + results = [] + def test_solo(self): number_players = 1 multiworld_options = [self.options] * number_players @@ -187,11 +213,40 @@ def test_10_player(self): self.performance_test_multiworld(multiworld_options) -class TestAllsanityWithoutMods(SVPerformanceTestCase): +class TestAllsanityWithoutMods6xx(SVPerformanceTestCase): acceptable_time_per_player = 10 options = allsanity_no_mods_6_x_x() results = [] + def test_solo(self): + number_players = 1 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + @unittest.skip + def test_duo(self): + number_players = 2 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + @unittest.skip + def test_5_player(self): + number_players = 5 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + @unittest.skip + def test_10_player(self): + number_players = 10 + multiworld_options = [self.options] * number_players + self.performance_test_multiworld(multiworld_options) + + +class TestAllsanityWithoutMods7xx(SVPerformanceTestCase): + acceptable_time_per_player = 10 + options = allsanity_no_mods_7_x_x() + results = [] + def test_solo(self): number_players = 1 multiworld_options = [self.options] * number_players @@ -215,9 +270,9 @@ def test_10_player(self): self.performance_test_multiworld(multiworld_options) -class TestAllsanityWithMods(SVPerformanceTestCase): +class TestAllsanityWithMods7xx(SVPerformanceTestCase): acceptable_time_per_player = 25 - options = allsanity_mods_6_x_x() + options = allsanity_mods_7_x_x() results = [] @unittest.skip diff --git a/worlds/stardew_valley/test/regions/TestEntranceClassifications.py b/worlds/stardew_valley/test/regions/TestEntranceClassifications.py index 4bc13cb51cf8..426523bac5ec 100644 --- a/worlds/stardew_valley/test/regions/TestEntranceClassifications.py +++ b/worlds/stardew_valley/test/regions/TestEntranceClassifications.py @@ -7,6 +7,9 @@ class EntranceRandomizationAssertMixin: def assert_non_progression_are_all_accessible_with_empty_inventory(self: SVTestBase): + # You need a tiny bit of money, for Jojamart specifically, because of a safeguard in case you get an early theater + self.collect("Shipping Bin") + self.collect_months(1) all_connections = create_all_connections(self.world.content.registered_packs) non_progression_connections = [connection for connection in all_connections.values() if RandomizationFlag.BIT_NON_PROGRESSION in connection.flag] @@ -29,7 +32,7 @@ def test_non_progression_are_all_accessible_with_empty_inventory(self): class TestModdedEntranceClassifications(EntranceRandomizationAssertMixin, SVTestBase): options = { options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false, - options.Mods: frozenset(options.Mods.valid_keys) + options.Mods: frozenset(options.all_mods_except_invalid_combinations), } def test_non_progression_are_all_accessible_with_empty_inventory(self): diff --git a/worlds/stardew_valley/test/regions/TestEntranceRandomization.py b/worlds/stardew_valley/test/regions/TestEntranceRandomization.py index 15c46637ab05..c9b07cbc0505 100644 --- a/worlds/stardew_valley/test/regions/TestEntranceRandomization.py +++ b/worlds/stardew_valley/test/regions/TestEntranceRandomization.py @@ -4,7 +4,7 @@ from BaseClasses import get_seed, MultiWorld, Entrance from ..assertion import WorldAssertMixin -from ..bases import SVTestCase, solo_multiworld +from ..bases import SVTestCase, solo_multiworld, setup_solo_multiworld from ... import options from ...mods.mod_data import ModNames from ...options import EntranceRandomization, ExcludeGingerIsland, SkillProgression @@ -59,67 +59,24 @@ def test_when_prepare_mod_data_then_swapped_connections_contains_both_directions self.assertEqual({"A to B": "A to C", "C to A": "B to A", "C to D": "C to A", "A to C": "D to C"}, swapped_connections) -class TestEntranceRandoCreatesValidWorlds(WorldAssertMixin, SVTestCase): +class TestCanGenerateEachModWithEntranceRandomizationBuildings(WorldAssertMixin, SVTestCase): + """The following tests validate that ER still generates winnable and logically-sane games with given mods. + Mods that do not interact with entrances are skipped + Not all ER settings are tested, because 'buildings' is, essentially, a superset of all others + """ + mods = all_mods.difference([ + ModNames.ginger, ModNames.distant_lands, ModNames.skull_cavern_elevator, ModNames.wellwick, ModNames.magic, + ModNames.binning_skill, ModNames.big_backpack, ModNames.luck_skill, ModNames.tractor, ModNames.shiko, ModNames.archaeology, + ModNames.delores, ModNames.socializing_skill, ModNames.cooking_skill + ]) - # The following tests validate that ER still generates winnable and logically-sane games with given mods. - # Mods that do not interact with entrances are skipped - # Not all ER settings are tested, because 'buildings' is, essentially, a superset of all others - def test_ginger_island_excluded_buildings(self): + def test_given_mod_when_generate_then_basic_checks(self) -> None: world_options = { options.EntranceRandomization: options.EntranceRandomization.option_buildings, - options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_true - } - with solo_multiworld(world_options) as (multi_world, _): - self.assert_basic_checks(multi_world) - - def test_deepwoods_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.deepwoods, options.EntranceRandomization.option_buildings) - - def test_juna_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.juna, options.EntranceRandomization.option_buildings) - - def test_jasper_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.jasper, options.EntranceRandomization.option_buildings) - - def test_alec_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.alec, options.EntranceRandomization.option_buildings) - - def test_yoba_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.yoba, options.EntranceRandomization.option_buildings) - - def test_eugene_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.eugene, options.EntranceRandomization.option_buildings) - - def test_ayeisha_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.ayeisha, options.EntranceRandomization.option_buildings) - - def test_riley_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.riley, options.EntranceRandomization.option_buildings) - - def test_sve_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.sve, options.EntranceRandomization.option_buildings) - - def test_alecto_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.alecto, options.EntranceRandomization.option_buildings) - - def test_lacey_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.lacey, options.EntranceRandomization.option_buildings) - - def test_boarding_house_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(ModNames.boarding_house, options.EntranceRandomization.option_buildings) - - def test_all_mods_entrance_randomization_buildings(self): - self.perform_basic_checks_on_mod_with_er(all_mods, options.EntranceRandomization.option_buildings) - - def perform_basic_checks_on_mod_with_er(self, mods: str | set[str], er_option: int) -> None: - if isinstance(mods, str): - mods = {mods} - world_options = { - options.EntranceRandomization: er_option, - options.Mods: frozenset(mods), + options.Mods: frozenset(self.mods), options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false } - with solo_multiworld(world_options) as (multi_world, _): + with solo_multiworld(world_options, world_caching=False) as (multi_world, _): self.assert_basic_checks(multi_world) @@ -165,3 +122,28 @@ def explore_regions_up_to_blockers(blocked_entrances: Collection[str], multiworl regions_to_explore.append(exit_.connected_region) return explored_regions + + +class TestEntranceRandoSpecificCases(SVTestCase): + def test_pierre_can_be_randomized_in_the_desert(self): + world_options = { + options.EntranceRandomization: EntranceRandomization.option_buildings, + options.ExcludeGingerIsland: ExcludeGingerIsland.option_true, + } + + multiworld = setup_solo_multiworld(world_options, _steps=["generate_early", "create_regions", "create_items", "set_rules"]) + world = multiworld.worlds[1] + world.random = Mock() + + def sort_entrances_to_place_pierre_and_oasis_first(entrances: list[Entrance]) -> None: + # This completely on the fact that + # 1. GER calls `shuffle` on the list of entrances and exits; + # 2. Both Pierre's and Oasis are not dead end so they are randomized in the first batch of entrances. + # Might break if the implementation changes :) + entrances.sort(key=lambda x: 0 if "Desert to Oasis" in x.name or "Pierre's General Store to Town" in x.name else 1) + + world.random.shuffle = sort_entrances_to_place_pierre_and_oasis_first + + world.connect_entrances() + + self.assertEqual("Desert", multiworld.get_region("Pierre's General Store", 1).entrances[0].parent_region.name) diff --git a/worlds/stardew_valley/test/regions/TestRandomizationFlag.py b/worlds/stardew_valley/test/regions/TestRandomizationFlag.py index 6a01ef07e96d..52f4d91f94ba 100644 --- a/worlds/stardew_valley/test/regions/TestRandomizationFlag.py +++ b/worlds/stardew_valley/test/regions/TestRandomizationFlag.py @@ -2,6 +2,7 @@ from ..options.utils import fill_dataclass_with_default from ... import create_content, options +from ...options import SkillProgression from ...regions.entrance_rando import create_player_randomization_flag from ...regions.model import RandomizationFlag, ConnectionData @@ -68,7 +69,8 @@ def test_given_entrance_randomization_choice_when_create_player_randomization_fl (options.EntranceRandomization.option_buildings, RandomizationFlag.BIT_BUILDINGS), (options.EntranceRandomization.option_chaos, RandomizationFlag.BIT_BUILDINGS), ): - player_options = fill_dataclass_with_default({options.EntranceRandomization: entrance_randomization_choice}) + player_options = fill_dataclass_with_default({options.EntranceRandomization: entrance_randomization_choice, + options.SkillProgression: SkillProgression.option_progressive_with_masteries}) content = create_content(player_options) flag = create_player_randomization_flag(player_options.entrance_randomization, content) diff --git a/worlds/stardew_valley/test/regions/TestRegionConnections.py b/worlds/stardew_valley/test/regions/TestRegionConnections.py index f20ef7943c90..0b45e7d5bdb9 100644 --- a/worlds/stardew_valley/test/regions/TestRegionConnections.py +++ b/worlds/stardew_valley/test/regions/TestRegionConnections.py @@ -42,7 +42,7 @@ def test_connection_lead_somewhere(self): class TestModsConnections(unittest.TestCase): options = { options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false, - options.Mods: frozenset(options.Mods.valid_keys) + options.Mods: frozenset(options.all_mods_except_invalid_combinations), } content = create_content(fill_dataclass_with_default(options)) all_regions_by_name = create_all_regions(content.registered_packs) @@ -54,13 +54,13 @@ def test_region_exits_lead_somewhere(self): if MergeFlag.REMOVE_EXITS in region.flag: continue - with self.subTest(mod=mod_region_data.mod_name, region=region.name): + with self.subTest(mod=mod_region_data.content_pack, region=region.name): for exit_ in region.exits: self.assertIn(exit_, self.all_connections_by_name, f"{region.name} is leading to {exit_} but it does not exist.") def test_connection_lead_somewhere(self): for mod_region_data in region_data_by_content_pack.values(): for connection in mod_region_data.connections: - with self.subTest(mod=mod_region_data.mod_name, connection=connection.name): + with self.subTest(mod=mod_region_data.content_pack, connection=connection.name): self.assertIn(connection.destination, self.all_regions_by_name, f"{connection.name} is leading to {connection.destination} but it does not exist.") diff --git a/worlds/stardew_valley/test/rules/TestBooks.py b/worlds/stardew_valley/test/rules/TestBooks.py index eb26b2744492..1575a1851163 100644 --- a/worlds/stardew_valley/test/rules/TestBooks.py +++ b/worlds/stardew_valley/test/rules/TestBooks.py @@ -8,9 +8,13 @@ class TestBooksLogic(SVTestBase): } def test_can_get_mapping_cave_systems_with_weapon_and_time(self): - self.collect_months(12) + self.collect_lots_of_money(0.95) + self.collect("Progressive Bookseller Days", 2) + self.collect("Bookseller Stock: Progressive Rare Books", 2) self.assert_cannot_reach_location("Read Mapping Cave Systems") + self.collect("Landslide Removed") + self.collect("Progressive Pickaxe") self.collect("Progressive Mine Elevator") self.collect("Progressive Mine Elevator") self.collect("Progressive Mine Elevator") diff --git a/worlds/stardew_valley/test/rules/TestBuildings.py b/worlds/stardew_valley/test/rules/TestBuildings.py index 0b1f41d2c56b..f2cd918ae76b 100644 --- a/worlds/stardew_valley/test/rules/TestBuildings.py +++ b/worlds/stardew_valley/test/rules/TestBuildings.py @@ -1,45 +1,57 @@ from ..bases import SVTestBase -from ...options import BuildingProgression, FarmType +from ... import SeasonRandomization, StartWithoutOptionName +from ...options import BuildingProgression, FarmType, ToolProgression, StartWithout class TestBuildingLogic(SVTestBase): options = { + StartWithout.internal_name: frozenset({StartWithoutOptionName.landslide}), + SeasonRandomization.internal_name: SeasonRandomization.option_disabled, FarmType.internal_name: FarmType.option_standard, BuildingProgression.internal_name: BuildingProgression.option_progressive, + ToolProgression.internal_name: ToolProgression.option_progressive, } def test_coop_blueprint(self): - self.assert_cannot_reach_location("Coop Blueprint") + location = "Coop Blueprint" + self.assert_cannot_reach_location(location) + self.collect("Landslide Removed") self.collect_lots_of_money() - self.assert_can_reach_location("Coop Blueprint") + self.assert_can_reach_location(location) def test_big_coop_blueprint(self): - self.assert_cannot_reach_location("Big Coop Blueprint") + location = "Big Coop Blueprint" + self.assert_cannot_reach_location(location) + self.collect("Landslide Removed") self.collect_lots_of_money() - self.assert_cannot_reach_location("Big Coop Blueprint") + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Progressive Coop")) - self.assert_can_reach_location("Big Coop Blueprint") + self.assert_can_reach_location(location) def test_deluxe_coop_blueprint(self): - self.assert_cannot_reach_location("Deluxe Coop Blueprint") + location = "Deluxe Coop Blueprint" + self.assert_cannot_reach_location(location) + self.collect("Landslide Removed") self.collect_lots_of_money() - self.assert_cannot_reach_location("Deluxe Coop Blueprint") + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Progressive Coop")) - self.assert_cannot_reach_location("Deluxe Coop Blueprint") + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Progressive Coop")) - self.assert_can_reach_location("Deluxe Coop Blueprint") + self.assert_can_reach_location(location) def test_big_shed_blueprint(self): - self.assert_cannot_reach_location("Big Shed Blueprint") + location = "Big Shed Blueprint" + self.assert_cannot_reach_location(location) + self.collect("Landslide Removed") self.collect_lots_of_money() - self.assert_cannot_reach_location("Big Shed Blueprint") + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Progressive Shed")) - self.assert_can_reach_location("Big Shed Blueprint") + self.assert_can_reach_location(location) diff --git a/worlds/stardew_valley/test/rules/TestBundles.py b/worlds/stardew_valley/test/rules/TestBundles.py index 357269a25ba0..837cace32c7b 100644 --- a/worlds/stardew_valley/test/rules/TestBundles.py +++ b/worlds/stardew_valley/test/rules/TestBundles.py @@ -1,11 +1,13 @@ from ..bases import SVTestBase -from ... import options -from ...options import BundleRandomization +from ... import options, SeasonRandomization, StartWithoutOptionName +from ...options import BundleRandomization, StartWithout from ...strings.bundle_names import BundleName class TestBundlesLogic(SVTestBase): options = { + StartWithout.internal_name: frozenset({StartWithoutOptionName.community_center, StartWithoutOptionName.buildings}), + options.SeasonRandomization: SeasonRandomization.option_disabled, options.BundleRandomization: BundleRandomization.option_vanilla, options.BundlePrice: options.BundlePrice.default, } @@ -13,52 +15,64 @@ class TestBundlesLogic(SVTestBase): def test_vault_2500g_bundle(self): self.assert_cannot_reach_location("2,500g Bundle") + self.collect("Community Center Key") + self.collect("Forest Magic") + self.assert_cannot_reach_location("2,500g Bundle") self.collect_lots_of_money() self.assert_can_reach_location("2,500g Bundle") class TestRemixedBundlesLogic(SVTestBase): options = { + StartWithout.internal_name: frozenset({StartWithoutOptionName.community_center}), + options.SeasonRandomization: SeasonRandomization.option_disabled, options.BundleRandomization: BundleRandomization.option_remixed, options.BundlePrice: options.BundlePrice.default, - options.BundlePlando: frozenset({BundleName.sticky}) + options.BundleWhitelist: frozenset({BundleName.sticky}) } def test_sticky_bundle_has_grind_rules(self): self.assert_cannot_reach_location("Sticky Bundle") + self.collect("Community Center Key") + self.collect("Forest Magic") + self.assert_cannot_reach_location("Sticky Bundle") self.collect_all_the_money() self.assert_can_reach_location("Sticky Bundle") -class TestRaccoonBundlesLogic(SVTestBase): - options = { - options.BundleRandomization: BundleRandomization.option_vanilla, - options.BundlePrice: options.BundlePrice.option_normal, - options.Craftsanity: options.Craftsanity.option_all, - } - seed = 2 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles - - def test_raccoon_bundles_rely_on_previous_ones(self): - self.collect("Progressive Raccoon", 6) - self.collect("Progressive Mine Elevator", 24) - self.collect("Mining Level", 12) - self.collect("Combat Level", 12) - self.collect("Progressive Axe", 4) - self.collect("Progressive Pickaxe", 4) - self.collect("Progressive Weapon", 4) - self.collect("Dehydrator Recipe") - self.collect("Mushroom Boxes") - self.collect("Progressive Fishing Rod", 4) - self.collect("Fishing Level", 10) - self.collect("Furnace Recipe") - - # The first raccoon bundle is a fishing one - self.assert_cannot_reach_location("Raccoon Request 1") - # The third raccoon bundle is a foraging one - self.assert_cannot_reach_location("Raccoon Request 3") - - self.collect("Fish Smoker Recipe") +# The Randomness changed - self.assert_can_reach_location("Raccoon Request 1") - self.assert_can_reach_location("Raccoon Request 3") +# class TestRaccoonBundlesLogic(SVTestBase): +# options = { +# options.BundleRandomization: BundleRandomization.option_vanilla, +# options.BundlePrice: options.BundlePrice.option_normal, +# options.Craftsanity: options.Craftsanity.option_all, +# } +# seed = 2 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles +# +# def test_raccoon_bundles_rely_on_previous_ones(self): +# self.collect("Forest Magic") +# self.collect("Landslide Removed") +# self.collect("Progressive Raccoon", 6) +# self.collect("Progressive Mine Elevator", 24) +# self.collect("Mining Level", 12) +# self.collect("Combat Level", 12) +# self.collect("Progressive Axe", 4) +# self.collect("Progressive Pickaxe", 4) +# self.collect("Progressive Weapon", 4) +# self.collect("Dehydrator Recipe") +# self.collect("Mushroom Boxes") +# self.collect("Progressive Fishing Rod", 4) +# self.collect("Fishing Level", 10) +# self.collect("Furnace Recipe") +# +# # The first raccoon bundle is a fishing one +# self.assert_cannot_reach_location("Raccoon Request 1") +# # The third raccoon bundle is a foraging one +# self.assert_cannot_reach_location("Raccoon Request 3") +# +# self.collect("Fish Smoker Recipe") +# +# self.assert_can_reach_location("Raccoon Request 1") +# self.assert_can_reach_location("Raccoon Request 3") diff --git a/worlds/stardew_valley/test/rules/TestCatalogues.py b/worlds/stardew_valley/test/rules/TestCatalogues.py new file mode 100644 index 000000000000..5b51a6d97498 --- /dev/null +++ b/worlds/stardew_valley/test/rules/TestCatalogues.py @@ -0,0 +1,36 @@ +from ..bases import SVTestBase +from ... import options + + +class TestWizardCatalogueWithoutEndgameLocations(SVTestBase): + options = { + options.SeasonRandomization: options.SeasonRandomization.option_disabled, + options.Cropsanity: options.Cropsanity.option_enabled, + options.EntranceRandomization: options.EntranceRandomization.option_disabled, + options.IncludeEndgameLocations: options.IncludeEndgameLocations.option_false, + } + + def test_doesnt_need_catalogue_to_buy_catalogue(self): + item = "Ruby Crystal Ball" + self.collect("Rusty Key") + self.collect_all_the_money() + + self.assert_rule_true(self.world.logic.has(item)) + + +class TestWizardCatalogueWithEndgameLocations(SVTestBase): + options = { + options.SeasonRandomization: options.SeasonRandomization.option_disabled, + options.Cropsanity: options.Cropsanity.option_enabled, + options.EntranceRandomization: options.EntranceRandomization.option_disabled, + options.IncludeEndgameLocations: options.IncludeEndgameLocations.option_true, + } + + def test_need_catalogue_to_buy_catalogue(self): + item = "Ruby Crystal Ball" + self.collect("Rusty Key") + self.collect_all_the_money() + + self.assert_rule_false(self.world.logic.has(item)) + self.collect("Wizard Catalogue") + self.assert_rule_true(self.world.logic.has(item)) diff --git a/worlds/stardew_valley/test/rules/TestCookingRecipes.py b/worlds/stardew_valley/test/rules/TestCookingRecipes.py index b468a72d4117..f7e36199a908 100644 --- a/worlds/stardew_valley/test/rules/TestCookingRecipes.py +++ b/worlds/stardew_valley/test/rules/TestCookingRecipes.py @@ -1,15 +1,38 @@ from ..bases import SVTestBase from ... import options -from ...options import BuildingProgression, ExcludeGingerIsland, Chefsanity +from ...content.content_packs import ginger_island_content_pack +from ...data.recipe_data import all_cooking_recipes + + +class TestRecipeContainingGingerIslandIngredientsAreTaggedWithGingerIslandContentPack(SVTestBase): + options = { + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_true, + options.Mods: frozenset(options.all_mods_except_invalid_combinations), + } + + def test_recipe_without_content_pack_contains_only_pure_vanilla_ingredients(self): + for item in self.multiworld.get_items(): + self.multiworld.state.collect(item, prevent_sweep=True) + + logic = self.world.logic + + for recipe in all_cooking_recipes: + if recipe.content_pack is ginger_island_content_pack.name or recipe.content_pack not in self.world.options.mods: + continue + + with self.subTest(recipe.meal): + for item in recipe.ingredients: + rule = logic.has(item) + self.assert_rule_true(rule, self.multiworld.state) class TestRecipeLearnLogic(SVTestBase): options = { - BuildingProgression.internal_name: BuildingProgression.option_progressive, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.Cooksanity.internal_name: options.Cooksanity.option_all, - Chefsanity.internal_name: Chefsanity.option_none, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + options.BuildingProgression: options.BuildingProgression.option_progressive, + options.Cropsanity: options.Cropsanity.option_enabled, + options.Cooksanity: options.Cooksanity.option_all, + options.Chefsanity: options.Chefsanity.preset_none, + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_true, } def test_can_learn_qos_recipe(self): @@ -29,11 +52,12 @@ def test_can_learn_qos_recipe(self): class TestRecipeReceiveLogic(SVTestBase): options = { - BuildingProgression.internal_name: BuildingProgression.option_progressive, - options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - options.Cooksanity.internal_name: options.Cooksanity.option_all, - Chefsanity.internal_name: Chefsanity.option_all, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + options.StartWithout: options.StartWithout.preset_none, + options.BuildingProgression: options.BuildingProgression.option_progressive, + options.Cropsanity: options.Cropsanity.option_enabled, + options.Cooksanity: options.Cooksanity.option_all, + options.Chefsanity: options.Chefsanity.preset_all, + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_true, } def test_can_learn_qos_recipe(self): diff --git a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py index f875dc539d46..0ea1b290924e 100644 --- a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py +++ b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py @@ -1,36 +1,43 @@ from ..bases import SVTestBase -from ... import options +from ... import options, StartWithoutOptionName from ...data.craftable_data import all_crafting_recipes_by_name -from ...options import BuildingProgression, ExcludeGingerIsland, Craftsanity, SeasonRandomization +from ...options import StartWithout class TestCraftsanityLogic(SVTestBase): options = { - BuildingProgression.internal_name: BuildingProgression.option_progressive, + StartWithout.internal_name: frozenset({StartWithoutOptionName.buildings}), + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_disabled, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, options.Cropsanity.internal_name: options.Cropsanity.option_enabled, - Craftsanity.internal_name: Craftsanity.option_all, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + options.Craftsanity.internal_name: options.Craftsanity.option_all, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, } def test_can_craft_recipe(self): + location = "Craft Marble Brazier" + self.collect(self.create_item("Landslide Removed")) self.collect([self.create_item("Progressive Pickaxe")] * 4) self.collect([self.create_item("Progressive Fishing Rod")] * 4) self.collect([self.create_item("Progressive Sword")] * 4) self.collect([self.create_item("Progressive Mine Elevator")] * 24) + self.collect([self.create_item("Progressive Pan")] * 4) self.collect([self.create_item("Mining Level")] * 10) self.collect([self.create_item("Combat Level")] * 10) self.collect([self.create_item("Fishing Level")] * 10) self.collect_all_the_money() - self.assert_cannot_reach_location("Craft Marble Brazier") + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Marble Brazier Recipe")) - self.assert_can_reach_location("Craft Marble Brazier") + self.assert_can_reach_location(location) def test_can_learn_crafting_recipe(self): - self.assert_cannot_reach_location("Marble Brazier Recipe") + location = "Marble Brazier Recipe" + self.assert_cannot_reach_location(location) self.collect_lots_of_money() - self.assert_can_reach_location("Marble Brazier Recipe") + self.assert_can_reach_location(location) def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] @@ -47,8 +54,9 @@ def test_can_craft_festival_recipe(self): self.assert_rule_true(rule) def test_require_furnace_recipe_for_smelting_checks(self): - locations = ["Craft Furnace", "Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"] + locations = ["Craft Furnace", "Quest: Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"] rules = [self.world.logic.region.can_reach_location(location) for location in locations] + self.collect(self.create_item("Landslide Removed")) self.collect([self.create_item("Progressive Pickaxe")] * 4) self.collect([self.create_item("Progressive Fishing Rod")] * 4) self.collect([self.create_item("Progressive Sword")] * 4) @@ -66,11 +74,11 @@ def test_require_furnace_recipe_for_smelting_checks(self): class TestCraftsanityWithFestivalsLogic(SVTestBase): options = { - BuildingProgression.internal_name: BuildingProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, options.Cropsanity.internal_name: options.Cropsanity.option_enabled, options.FestivalLocations.internal_name: options.FestivalLocations.option_easy, - Craftsanity.internal_name: Craftsanity.option_all, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + options.Craftsanity.internal_name: options.Craftsanity.option_all, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, } def test_can_craft_festival_recipe(self): @@ -90,12 +98,13 @@ def test_can_craft_festival_recipe(self): class TestNoCraftsanityLogic(SVTestBase): options = { - BuildingProgression.internal_name: BuildingProgression.option_progressive, - SeasonRandomization.internal_name: SeasonRandomization.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive, options.Cropsanity.internal_name: options.Cropsanity.option_enabled, options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, - Craftsanity.internal_name: Craftsanity.option_none, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, } def test_can_craft_recipe(self): @@ -115,8 +124,9 @@ def test_can_craft_festival_recipe(self): self.assert_rule_true(rule) def test_requires_mining_levels_for_smelting_checks(self): - locations = ["Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"] + locations = ["Quest: Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"] rules = [self.world.logic.region.can_reach_location(location) for location in locations] + self.collect(self.create_item("Landslide Removed")) self.collect([self.create_item("Progressive Pickaxe")] * 4) self.collect([self.create_item("Progressive Fishing Rod")] * 4) self.collect([self.create_item("Progressive Sword")] * 4) @@ -134,11 +144,11 @@ def test_requires_mining_levels_for_smelting_checks(self): class TestNoCraftsanityWithFestivalsLogic(SVTestBase): options = { - BuildingProgression.internal_name: BuildingProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, options.Cropsanity.internal_name: options.Cropsanity.option_enabled, options.FestivalLocations.internal_name: options.FestivalLocations.option_easy, - Craftsanity.internal_name: Craftsanity.option_none, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + options.Craftsanity.internal_name: options.Craftsanity.option_none, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, } def test_can_craft_festival_recipe(self): diff --git a/worlds/stardew_valley/test/rules/TestFishing.py b/worlds/stardew_valley/test/rules/TestFishing.py index 22e6321a7a93..647d3c761651 100644 --- a/worlds/stardew_valley/test/rules/TestFishing.py +++ b/worlds/stardew_valley/test/rules/TestFishing.py @@ -1,10 +1,14 @@ from ..bases import SVTestBase -from ...options import SeasonRandomization, Fishsanity, ExcludeGingerIsland, SkillProgression, ToolProgression, ElevatorProgression, SpecialOrderLocations +from ... import StartWithoutOptionName +from ...options import SeasonRandomization, Fishsanity, ExcludeGingerIsland, SkillProgression, ToolProgression, ElevatorProgression, SpecialOrderLocations, \ + StartWithout +from ...strings.ap_names.transport_names import Transportation from ...strings.fish_names import Fish class TestNeedRegionToCatchFish(SVTestBase): options = { + StartWithout: frozenset({StartWithoutOptionName.landslide, StartWithoutOptionName.community_center}), SeasonRandomization.internal_name: SeasonRandomization.option_disabled, ElevatorProgression.internal_name: ElevatorProgression.option_vanilla, SkillProgression.internal_name: SkillProgression.option_vanilla, @@ -18,43 +22,42 @@ def test_catch_fish_requires_region_unlock(self): fish_and_items = { Fish.crimsonfish: ["Beach Bridge"], Fish.void_salmon: ["Railroad Boulder Removed", "Dark Talisman"], - Fish.woodskip: ["Progressive Axe", "Progressive Axe", "Progressive Weapon"], # For the ores to get the axe upgrades + Fish.woodskip: ["Progressive Axe", "Progressive Axe"], Fish.mutant_carp: ["Rusty Key"], Fish.slimejack: ["Railroad Boulder Removed", "Rusty Key"], - Fish.lionfish: ["Boat Repair"], - Fish.blue_discus: ["Island Obelisk", "Island West Turtle"], - Fish.stingray: ["Boat Repair", "Island Resort"], - Fish.ghostfish: ["Progressive Weapon"], - Fish.stonefish: ["Progressive Weapon"], - Fish.ice_pip: ["Progressive Weapon", "Progressive Weapon", "Progressive Pickaxe", "Progressive Pickaxe"], - Fish.lava_eel: ["Progressive Weapon", "Progressive Weapon", "Progressive Weapon", "Progressive Pickaxe", "Progressive Pickaxe", "Progressive Pickaxe"], - Fish.sandfish: ["Bus Repair"], - Fish.scorpion_carp: ["Desert Obelisk"], + Fish.lionfish: [Transportation.boat_repair], + Fish.blue_discus: ["Wizard Invitation", "Island Obelisk", "Island West Turtle"], + Fish.stingray: [Transportation.boat_repair, "Island Resort"], + Fish.ghostfish: ["Landslide Removed", "Progressive Weapon"], + Fish.stonefish: ["Landslide Removed", "Progressive Weapon"], + Fish.ice_pip: ["Landslide Removed", "Progressive Weapon", "Progressive Weapon", "Progressive Pickaxe"], + Fish.lava_eel: ["Landslide Removed", "Progressive Weapon", "Progressive Weapon", "Progressive Weapon", "Progressive Pickaxe", "Progressive Pickaxe"], + Fish.sandfish: [Transportation.bus_repair], + Fish.scorpion_carp: ["Wizard Invitation", "Desert Obelisk"], # Starting the extended family quest requires having caught all the legendaries before, so they all have the rules of every other legendary - Fish.son_of_crimsonfish: ["Beach Bridge", "Island Obelisk", "Island West Turtle", "Qi Walnut Room", "Rusty Key"], - Fish.radioactive_carp: ["Beach Bridge", "Rusty Key", "Boat Repair", "Island West Turtle", "Qi Walnut Room"], - Fish.glacierfish_jr: ["Beach Bridge", "Island Obelisk", "Island West Turtle", "Qi Walnut Room", "Rusty Key"], - Fish.legend_ii: ["Beach Bridge", "Island Obelisk", "Island West Turtle", "Qi Walnut Room", "Rusty Key"], - Fish.ms_angler: ["Beach Bridge", "Island Obelisk", "Island West Turtle", "Qi Walnut Room", "Rusty Key"], + Fish.son_of_crimsonfish: ["Beach Bridge", Transportation.boat_repair, "Island West Turtle", "Qi Walnut Room", "Rusty Key"], + Fish.radioactive_carp: ["Beach Bridge", "Rusty Key", Transportation.boat_repair, "Island West Turtle", "Qi Walnut Room"], + Fish.glacierfish_jr: ["Beach Bridge", Transportation.boat_repair, "Island West Turtle", "Qi Walnut Room", "Rusty Key"], + Fish.legend_ii: ["Beach Bridge", "Wizard Invitation", "Island Obelisk", "Island West Turtle", "Qi Walnut Room", "Rusty Key"], + Fish.ms_angler: ["Beach Bridge", "Wizard Invitation", "Island Obelisk", "Island West Turtle", "Qi Walnut Room", "Rusty Key"], } self.collect("Progressive Fishing Rod", 4) - self.original_state = self.multiworld.state.copy() + self.collect_all_the_money() for fish in fish_and_items: with self.subTest(f"Region rules for {fish}"): - self.collect_all_the_money() item_names = fish_and_items[fish] - self.assert_cannot_reach_location(f"Fishsanity: {fish}") - items = [] - for item_name in item_names: - items.append(self.collect(item_name)) + location = f"Fishsanity: {fish}" + self.assert_cannot_reach_location(location) + items = [self.create_item(item_name) for item_name in item_names] + for item in items: + self.collect(item) with self.subTest(f"{fish} can be reached with {item_names}"): - self.assert_can_reach_location(f"Fishsanity: {fish}") + self.assert_can_reach_location(location) for item_required in items: - self.multiworld.state = self.original_state.copy() with self.subTest(f"{fish} requires {item_required.name}"): - for item_to_collect in items: - if item_to_collect.name != item_required.name: - self.collect(item_to_collect) - self.assert_cannot_reach_location(f"Fishsanity: {fish}") - - self.multiworld.state = self.original_state.copy() + self.remove(item_required) + self.assert_cannot_reach_location(location) + self.collect(item_required) + self.assert_can_reach_location(location) + for item in items: + self.remove(item) diff --git a/worlds/stardew_valley/test/rules/TestFriendship.py b/worlds/stardew_valley/test/rules/TestFriendship.py index dc5935580ad0..ab80ae51b4fa 100644 --- a/worlds/stardew_valley/test/rules/TestFriendship.py +++ b/worlds/stardew_valley/test/rules/TestFriendship.py @@ -11,6 +11,7 @@ class TestFriendsanityDatingRules(SVTestBase): def test_earning_dating_heart_requires_dating(self): self.collect_all_the_money() + self.collect(self.create_item("Landslide Removed")) self.multiworld.state.collect(self.create_item("Fall")) self.multiworld.state.collect(self.create_item("Beach Bridge")) self.multiworld.state.collect(self.create_item("Progressive House")) @@ -47,8 +48,10 @@ def assert_can_reach_heart_up_to(self, npc: str, max_reachable: int, step: int): for i in range(1, max_reachable + 1): if i % step != 0 and i != 14: continue - self.assert_can_reach_location(f"{prefix}{npc} {i}{suffix}") + location = f"{prefix}{npc} {i}{suffix}" + self.assert_can_reach_location(location) for i in range(max_reachable + 1, 14 + 1): if i % step != 0 and i != 14: continue - self.assert_cannot_reach_location(f"{prefix}{npc} {i}{suffix}") + location = f"{prefix}{npc} {i}{suffix}" + self.assert_cannot_reach_location(location) diff --git a/worlds/stardew_valley/test/rules/TestHats.py b/worlds/stardew_valley/test/rules/TestHats.py new file mode 100644 index 000000000000..2179e3b0b79a --- /dev/null +++ b/worlds/stardew_valley/test/rules/TestHats.py @@ -0,0 +1,105 @@ +from ..bases import SVTestBase +from ..options.presets import allsanity_mods_7_x_x +from ... import locations_by_tag +from ...content.feature.hatsanity import to_location_name +from ...data.hats_data import Hats +from ...locations import LocationTags +from ...options import Hatsanity, SeasonRandomization, FestivalLocations, Shipsanity, Eatsanity, Cooksanity, Fishsanity, Craftsanity +from ...options.options import AllowedFillerItems +from ...strings.ap_names.ap_option_names import HatsanityOptionName, AllowedFillerOptionName + + +class TestHatsLogic(SVTestBase): + options = { + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + FestivalLocations.internal_name: FestivalLocations.option_hard, + Shipsanity.internal_name: Shipsanity.option_everything, + Hatsanity.internal_name: Hatsanity.preset_all, + "start_inventory": {"Fall": 1} + } + + def test_need_spring_to_grab_leprechaun_hat(self): + hat_location = to_location_name(Hats.leprechaun_hat) + self.assert_cannot_reach_location(hat_location) + self.collect("Spring") + self.assert_can_reach_location(hat_location) + + def test_need_pans_to_wear_pan_hats(self): + pan_hats = [Hats.copper_pan_hat, Hats.steel_pan_hat, Hats.gold_pan_hat, Hats.iridium_pan_hat] + locations = [to_location_name(hat) for hat in pan_hats] + for location in locations: + self.assert_cannot_reach_location(location) + self.collect("Progressive Pan") + self.assert_can_reach_location(location) + + def test_reach_frog_hat(self): + required_item_names = ["Progressive Fishing Rod", "Island Obelisk", "Island West Turtle", "Island Farmhouse"] + required_items = [self.create_item(item_name) for item_name in required_item_names] + location = to_location_name(Hats.frog_hat) + for required_item in required_items: + self.collect(required_item) + self.assert_can_reach_location(location) + for required_item in required_items: + with self.subTest(f"Requires {required_item.name} to {location}"): + self.remove(required_item) + self.assert_cannot_reach_location(location) + self.collect(required_item) + + def test_no_hats_in_item_pool(self): + item_pool = self.multiworld.itempool + for item in item_pool: + self.assertTrue("Hat" not in item.name) + self.assertTrue("Mask" not in item.name) + + +class TestNoHatsLogic(SVTestBase): + options = { + AllowedFillerItems.internal_name: frozenset({AllowedFillerOptionName.hats}), + SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + FestivalLocations.internal_name: FestivalLocations.option_hard, + Fishsanity.internal_name: Fishsanity.option_all, + Shipsanity.internal_name: Shipsanity.option_everything, + Cooksanity.internal_name: Cooksanity.option_all, + Craftsanity.internal_name: Craftsanity.option_all, + Eatsanity.internal_name: Eatsanity.preset_all, + Hatsanity.internal_name: Hatsanity.preset_none, + "start_inventory": {"Fall": 1} + } + + def test_there_are_hats_in_item_pool(self): + item_pool = [item.name for item in self.multiworld.itempool] + self.assertTrue("Leprechaun Hat" in item_pool) + self.assertTrue("Straw Hat" in item_pool) + self.assertTrue("Cone Hat" in item_pool) + self.assertTrue("Mummy Mask" in item_pool) + + +class TestHatLocations(SVTestBase): + options = allsanity_mods_7_x_x() + + def test_all_hat_locations_are_added(self): + location_names = [location.name for location in self.multiworld.get_locations()] + for hat_location in locations_by_tag[LocationTags.HATSANITY]: + with self.subTest(hat_location.name): + if hat_location.name == "Wear Panda Hat": + self.assertNotIn(hat_location.name, location_names, "The Panda Hat cannot be obtained on the standard edition of the game") + else: + self.assertIn(hat_location.name, location_names) + + +class TestHatsOptionSetIndependence(SVTestBase): + options = { + Hatsanity.internal_name: frozenset({HatsanityOptionName.difficult}), + } + + def test_only_difficult_hats_are_added(self): + difficult_hats = [Hats.magic_cowboy_hat, Hats.plum_chapeau, Hats.watermelon_band] + not_difficult_hats = [Hats.steel_pan_hat, Hats.party_hat_blue, Hats.frog_hat, Hats.cowboy, Hats.elegant_turban] + location_names = [location.name for location in self.multiworld.get_locations()] + for hat in difficult_hats: + with self.subTest(hat.name): + self.assertIn(to_location_name(hat), location_names) + for hat in not_difficult_hats: + with self.subTest(hat.name): + self.assertNotIn(to_location_name(hat), location_names) + diff --git a/worlds/stardew_valley/test/rules/TestMuseum.py b/worlds/stardew_valley/test/rules/TestMuseum.py index 1a22e8800c5d..ef5bad12ee98 100644 --- a/worlds/stardew_valley/test/rules/TestMuseum.py +++ b/worlds/stardew_valley/test/rules/TestMuseum.py @@ -1,4 +1,5 @@ from collections import Counter +from random import Random from unittest.mock import patch from ..bases import SVTestBase @@ -35,7 +36,7 @@ def __repr__(self): class TestMuseumsanityDisabledExcludesMuseumDonationsFromOtherLocations(SVTestBase): options = { - **presets.allsanity_mods_6_x_x(), + **presets.allsanity_mods_7_x_x(), options.Museumsanity.internal_name: options.Museumsanity.option_none } @@ -51,12 +52,20 @@ def test_museum_donations_are_never_required_in_any_locations(self): museum_logic.can_donate.return_value = DisabledMuseumRule() # Allowing calls to museum rules since a lot of other logic depends on it, for minerals for instance. museum_logic.can_find_museum_item.return_value = true_ + museum_logic.has_any_gem.return_value = true_ + museum_logic.has_all_gems.return_value = true_ regions = {region.name for region in self.multiworld.regions} self.world.logic = StardewLogic(self.player, self.world.options, self.world.content, regions) self.world.set_rules() self.collect_everything() - for location in self.get_real_locations(): + # Fixed seed for consistency + seed = 2184959 + random = Random(seed) + all_locations = self.get_real_locations() + # We test 1% of all locations, to be fast + locations_to_test = random.sample(all_locations, k=len(all_locations)//100) + for location in locations_to_test: with self.subTest(location.name): self.assert_can_reach_location(location) diff --git a/worlds/stardew_valley/test/rules/TestQuests.py b/worlds/stardew_valley/test/rules/TestQuests.py new file mode 100644 index 000000000000..45ebe3db9d15 --- /dev/null +++ b/worlds/stardew_valley/test/rules/TestQuests.py @@ -0,0 +1,60 @@ +from ..bases import SVTestBase +from ... import FarmType +from ...options import ToolProgression, QuestLocations, Secretsanity +from ...strings.ap_names.ap_option_names import SecretsanityOptionName + + +class TestQuestsLogic(SVTestBase): + options = { + QuestLocations.internal_name: 0, + ToolProgression.internal_name: ToolProgression.option_progressive, + } + + def test_giant_stump_requires_one_raccoon(self): + quest_name = "Quest: The Giant Stump" + quest_location = self.world.get_location(quest_name) + + self.assert_cannot_reach_location(quest_location) + + self.collect("Progressive Axe", 2) + self.assert_cannot_reach_location(quest_location) + + self.collect("Progressive Raccoon") + self.assert_can_reach_location(quest_location) + + +class TestQuestsOverrideBySecretNotesLogic(SVTestBase): + options = { + QuestLocations.internal_name: 0, + Secretsanity.internal_name: frozenset([SecretsanityOptionName.secret_notes]), + } + + def test_giant_stump_requires_one_raccoon(self): + location_names = [location.name for location in self.multiworld.get_locations()] + self.assertNotIn("Quest: Strange Note", location_names) + self.assertIn("Secret Note #23: Strange Note", location_names) + self.assertEqual(1, len([name for name in location_names if "Strange Note" in name])) + + +class TestRaisingAnimalsQuest(SVTestBase): + options = { + FarmType.internal_name: FarmType.option_standard, + QuestLocations.internal_name: 0, + } + + def test_only_raising_animals_on_standard(self): + location_names = [location.name for location in self.multiworld.get_locations()] + self.assertIn("Quest: Raising Animals", location_names) + self.assertNotIn("Quest: Feeding Animals", location_names) + + +class TestFeedingAnimalsQuest(SVTestBase): + options = { + FarmType.internal_name: FarmType.option_meadowlands, + QuestLocations.internal_name: 0, + } + + def test_only_feeding_animals_on_meadowlands(self): + location_names = [location.name for location in self.multiworld.get_locations()] + self.assertNotIn("Quest: Raising Animals", location_names) + self.assertIn("Quest: Feeding Animals", location_names) \ No newline at end of file diff --git a/worlds/stardew_valley/test/rules/TestShipping.py b/worlds/stardew_valley/test/rules/TestShipping.py index c1f29d934255..863817c63bed 100644 --- a/worlds/stardew_valley/test/rules/TestShipping.py +++ b/worlds/stardew_valley/test/rules/TestShipping.py @@ -1,6 +1,7 @@ from ..bases import SVTestBase +from ... import StartWithoutOptionName from ...locations import LocationTags, location_table -from ...options import BuildingProgression, Shipsanity +from ...options import BuildingProgression, Shipsanity, StartWithout class TestShipsanityNone(SVTestBase): @@ -62,6 +63,7 @@ def test_only_full_shipment_and_fish_shipsanity_locations(self): class TestShipsanityEverything(SVTestBase): options = { + StartWithout.internal_name: frozenset({StartWithoutOptionName.buildings}), Shipsanity.internal_name: Shipsanity.option_everything, BuildingProgression.internal_name: BuildingProgression.option_progressive } @@ -80,4 +82,4 @@ def test_all_shipsanity_locations_require_shipping_bin(self): self.collect(bin_item) self.assert_can_reach_location(location.name) - self.remove(bin_item) + self.remove(bin_item) diff --git a/worlds/stardew_valley/test/rules/TestSkills.py b/worlds/stardew_valley/test/rules/TestSkills.py index fd513a1becc7..848bc7ada714 100644 --- a/worlds/stardew_valley/test/rules/TestSkills.py +++ b/worlds/stardew_valley/test/rules/TestSkills.py @@ -1,6 +1,6 @@ from ..bases import SVTestBase from ... import HasProgressionPercent, StardewLogic -from ...options import ToolProgression, SkillProgression, Mods +from ...options import ToolProgression, SkillProgression, Mods, all_mods_except_invalid_combinations from ...strings.skill_names import all_skills, all_vanilla_skills, Skill @@ -25,7 +25,7 @@ def test_has_mastery_requires_month_equivalent_to_10_levels(self): class TestSkillProgressionProgressive(SVTestBase): options = { SkillProgression.internal_name: SkillProgression.option_progressive, - Mods.internal_name: frozenset(Mods.valid_keys), + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), } def test_all_skill_levels_require_previous_level(self): @@ -34,14 +34,14 @@ def test_all_skill_levels_require_previous_level(self): self.remove_by_name(f"{skill} Level") for level in range(1, 11): - location_name = f"Level {level} {skill}" + location = f"Level {level} {skill}" - with self.subTest(location_name): + with self.subTest(location): if level > 1: - self.assert_cannot_reach_location(location_name) + self.assert_cannot_reach_location(location) self.collect(f"{skill} Level") - self.assert_can_reach_location(location_name) + self.assert_can_reach_location(location) self.reset_collection_state() diff --git a/worlds/stardew_valley/test/rules/TestStateRules.py b/worlds/stardew_valley/test/rules/TestStateRules.py index 57573c7f8b55..acc70b06f311 100644 --- a/worlds/stardew_valley/test/rules/TestStateRules.py +++ b/worlds/stardew_valley/test/rules/TestStateRules.py @@ -1,10 +1,11 @@ from ..bases import SVTestBase -from ..options.presets import allsanity_mods_6_x_x +from ..options.presets import allsanity_mods_7_x_x from ...stardew_rule import HasProgressionPercent class TestHasProgressionPercentWithVictory(SVTestBase): - options = allsanity_mods_6_x_x() + options = allsanity_mods_7_x_x() + skip_default_tests = True def test_has_100_progression_percent_is_false_while_items_are_missing(self): has_100_progression_percent = HasProgressionPercent(1, 100) diff --git a/worlds/stardew_valley/test/rules/TestTools.py b/worlds/stardew_valley/test/rules/TestTools.py index 54b9ec8f2f26..76dea03fd649 100644 --- a/worlds/stardew_valley/test/rules/TestTools.py +++ b/worlds/stardew_valley/test/rules/TestTools.py @@ -2,16 +2,17 @@ from ..bases import SVTestBase from ... import options -from ...options import ToolProgression, SeasonRandomization +from ...options import ToolProgression, SeasonRandomization, Secretsanity from ...strings.entrance_names import Entrance from ...strings.region_names import Region -from ...strings.tool_names import Tool, ToolMaterial +from ...strings.tool_names import Tool, ToolMaterial, FishingRod class TestProgressiveToolsLogic(SVTestBase): options = { ToolProgression.internal_name: ToolProgression.option_progressive, SeasonRandomization.internal_name: SeasonRandomization.option_randomized, + Secretsanity.internal_name: Secretsanity.preset_simple, } def test_sturgeon(self): @@ -53,39 +54,40 @@ def test_sturgeon(self): def test_old_master_cannoli(self): self.multiworld.state.prog_items = {1: Counter()} - self.multiworld.state.collect(self.create_item("Progressive Axe")) - self.multiworld.state.collect(self.create_item("Progressive Axe")) - self.multiworld.state.collect(self.create_item("Summer")) + self.collect("Progressive Axe") + self.collect("Progressive Axe") + self.collect("Summer") self.collect_lots_of_money() - self.assert_cannot_reach_location("Old Master Cannoli") + location = "Secret: Old Master Cannoli" + self.assert_cannot_reach_location(location) fall = self.create_item("Fall") self.multiworld.state.collect(fall) - self.assert_cannot_reach_location("Old Master Cannoli") + self.assert_cannot_reach_location(location) tuesday = self.create_item("Traveling Merchant: Tuesday") self.multiworld.state.collect(tuesday) - self.assert_cannot_reach_location("Old Master Cannoli") + self.assert_cannot_reach_location(location) rare_seed = self.create_item("Rare Seed") self.multiworld.state.collect(rare_seed) - self.assert_can_reach_location("Old Master Cannoli") + self.assert_can_reach_location(location) self.remove(fall) - self.assert_cannot_reach_location("Old Master Cannoli") + self.assert_cannot_reach_location(location) self.remove(tuesday) green_house = self.create_item("Greenhouse") self.multiworld.state.collect(green_house) - self.assert_cannot_reach_location("Old Master Cannoli") + self.assert_cannot_reach_location(location) friday = self.create_item("Traveling Merchant: Friday") self.multiworld.state.collect(friday) - self.assert_can_reach_location("Old Master Cannoli") + self.assert_can_reach_location(location) self.remove(green_house) - self.assert_cannot_reach_location("Old Master Cannoli") + self.assert_cannot_reach_location(location) self.remove(friday) @@ -107,7 +109,7 @@ def test_cannot_get_any_tool_without_blacksmith_access(self): for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]: self.assert_rule_false(self.world.logic.tool.has_tool(tool, material)) - self.multiworld.state.collect(self.create_item(railroad_item)) + self.collect(railroad_item) for tool in [Tool.pickaxe, Tool.axe, Tool.hoe, Tool.trash_can, Tool.watering_can]: for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]: @@ -117,14 +119,16 @@ def test_cannot_get_fishing_rod_without_willy_access(self): railroad_item = "Railroad Boulder Removed" place_region_at_entrance(self.multiworld, self.player, Region.fish_shop, Entrance.enter_bathhouse_entrance) self.collect_all_except(railroad_item) + self.collect("Fishing Level", 10) + self.collect("Fishing Mastery") - for fishing_rod_level in [3, 4]: - self.assert_rule_false(self.world.logic.tool.has_fishing_rod(fishing_rod_level)) + for fishing_rod in [FishingRod.training, FishingRod.bamboo, FishingRod.fiberglass, FishingRod.iridium, FishingRod.advanced_iridium]: + self.assert_rule_false(self.world.logic.tool.has_fishing_rod(fishing_rod)) - self.multiworld.state.collect(self.create_item(railroad_item)) + self.collect(railroad_item) - for fishing_rod_level in [3, 4]: - self.assert_rule_true(self.world.logic.tool.has_fishing_rod(fishing_rod_level)) + for fishing_rod in [FishingRod.training, FishingRod.bamboo, FishingRod.fiberglass, FishingRod.iridium, FishingRod.advanced_iridium]: + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(fishing_rod)) def place_region_at_entrance(multiworld, player, region, entrance): @@ -135,3 +139,51 @@ def place_region_at_entrance(multiworld, player, region, entrance): region_to_switch = entrance_to_place_region.connected_region entrance_to_switch.connect(region_to_switch) entrance_to_place_region.connect(region_to_place) + + +class TestVanillaFishingRodsRequiresLevelsAndMasteries(SVTestBase): + options = { + options.SeasonRandomization: options.SeasonRandomization.option_disabled, + options.Cropsanity: options.Cropsanity.option_disabled, + options.SkillProgression: options.SkillProgression.option_progressive_with_masteries, + options.ToolProgression: options.ToolProgression.option_vanilla, + } + + def test_cannot_get_fishing_rods_without_their_unlock_conditions(self): + self.collect_lots_of_money() + + rods = [FishingRod.training, FishingRod.bamboo, FishingRod.fiberglass, FishingRod.iridium, FishingRod.advanced_iridium] + reachable = 2 + for i in range(len(rods)): + if i < reachable: + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(rods[i])) + else: + self.assert_rule_false(self.world.logic.tool.has_fishing_rod(rods[i])) + + self.collect("Fishing Level", 2) + reachable = 3 + for i in range(len(rods)): + if i < reachable: + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(rods[i])) + else: + self.assert_rule_false(self.world.logic.tool.has_fishing_rod(rods[i])) + + self.collect("Fishing Level", 4) + reachable = 4 + for i in range(len(rods)): + if i < reachable: + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(rods[i])) + else: + self.assert_rule_false(self.world.logic.tool.has_fishing_rod(rods[i])) + + self.collect("Fishing Level", 4) + reachable = 4 + for i in range(len(rods)): + if i < reachable: + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(rods[i])) + else: + self.assert_rule_false(self.world.logic.tool.has_fishing_rod(rods[i])) + + self.collect("Fishing Mastery") + for rod in rods: + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(rod)) diff --git a/worlds/stardew_valley/test/rules/TestTravelingMerchant.py b/worlds/stardew_valley/test/rules/TestTravelingMerchant.py index aa7f46d07b03..0f81f68a5754 100644 --- a/worlds/stardew_valley/test/rules/TestTravelingMerchant.py +++ b/worlds/stardew_valley/test/rules/TestTravelingMerchant.py @@ -1,10 +1,12 @@ from ..bases import SVTestBase -from ... import SeasonRandomization, EntranceRandomization +from ... import StartWithoutOptionName, SeasonRandomization, EntranceRandomization from ...locations import location_table, LocationTags +from ...options import StartWithout class TestTravelingMerchant(SVTestBase): options = { + StartWithout: frozenset({StartWithoutOptionName.buildings}), SeasonRandomization: SeasonRandomization.option_randomized_not_winter, EntranceRandomization: EntranceRandomization.option_disabled, } diff --git a/worlds/stardew_valley/test/rules/TestWeapons.py b/worlds/stardew_valley/test/rules/TestWeapons.py index e95e706ded12..6fec5b09a23d 100644 --- a/worlds/stardew_valley/test/rules/TestWeapons.py +++ b/worlds/stardew_valley/test/rules/TestWeapons.py @@ -1,6 +1,7 @@ from ..bases import SVTestBase from ... import options from ...options import ToolProgression +from ...strings.ap_names.transport_names import Transportation class TestWeaponsLogic(SVTestBase): @@ -10,6 +11,7 @@ class TestWeaponsLogic(SVTestBase): } def test_mine(self): + self.collect(self.create_item("Landslide Removed")) self.multiworld.state.collect(self.create_item("Progressive Pickaxe")) self.multiworld.state.collect(self.create_item("Progressive Pickaxe")) self.multiworld.state.collect(self.create_item("Progressive Pickaxe")) @@ -18,7 +20,7 @@ def test_mine(self): self.collect([self.create_item("Combat Level")] * 10) self.collect([self.create_item("Mining Level")] * 10) self.collect([self.create_item("Progressive Mine Elevator")] * 24) - self.multiworld.state.collect(self.create_item("Bus Repair")) + self.multiworld.state.collect(self.create_item(Transportation.bus_repair)) self.multiworld.state.collect(self.create_item("Skull Key")) self.give_item_and_check_reachable_mine("Progressive Sword", 1) diff --git a/worlds/stardew_valley/test/script/benchmark_generation.py b/worlds/stardew_valley/test/script/benchmark_generation.py new file mode 100644 index 000000000000..8a78875bf02c --- /dev/null +++ b/worlds/stardew_valley/test/script/benchmark_generation.py @@ -0,0 +1,251 @@ +""" +Generate a game while monitoring the location access rules calls and their performance. + +Run with `python -m worlds.stardew_valley.test.script.generate_monitor_rules --options minimal_locations_maximal_items` +""" + +import argparse +import gc +import time +from collections import defaultdict +from dataclasses import dataclass, field +from functools import wraps + +from BaseClasses import CollectionState, get_seed +from Fill import distribute_items_restrictive, FillError +from test.general import gen_steps +from worlds import AutoWorld +from ..bases import setup_solo_multiworld +from ..options import presets +from ... import StardewValleyWorld +from ...rules import StardewRuleCollector +from ...stardew_rule import StardewRule + + +@dataclass(frozen=True) +class RuleCall: + start_ns: int + end_ns: int + result: bool + + @property + def duration_ns(self) -> int: + return self.end_ns - self.start_ns + + +@dataclass(frozen=True) +class PerformanceMonitoringStardewRule(StardewRule): + location: str + delegate: StardewRule + calls: list[RuleCall] = field(default_factory=list) + + def __call__(self, state: CollectionState) -> bool: + start = time.perf_counter_ns() + result = self.delegate(state) + end = time.perf_counter_ns() + self.calls.append(RuleCall(start, end, result)) + return result + + def __and__(self, other: StardewRule): + raise NotImplementedError() + + def __or__(self, other: StardewRule): + raise NotImplementedError() + + def evaluate_while_simplifying(self, state: CollectionState) -> tuple[StardewRule, bool]: + raise NotImplementedError() + + +def write_results(monitored_rules: list[PerformanceMonitoringStardewRule], output_file: str) -> None: + with open(output_file, 'w+') as results_file: + + results_file.write("name,start_ns,end_ns,duration,result\n") + for rule in monitored_rules: + for call in rule.calls: + results_file.write(f"\"{rule.location}\",{call.start_ns},{call.end_ns},{call.end_ns - call.start_ns},{call.result}\n") + + +def generate_monitor_rules(): + parser = argparse.ArgumentParser() + parser.add_argument('--options', help="Define the option set to use, from the preset in test/__init__.py .", type=str, required=True) + parser.add_argument('--seed', help="Define the seed to use.", type=int) + script_args = parser.parse_args() + options_set = script_args.options + options = getattr(presets, options_set)() + fixed_seed = script_args.seed + + original_set_location_rule = StardewRuleCollector.set_location_rule + original_set_entrance_rule = StardewRuleCollector.set_entrance_rule + original_collect = StardewValleyWorld.collect + original_remove = StardewValleyWorld.remove + original_call_single = AutoWorld.call_single + original_sweep_for_advancements = CollectionState.sweep_for_advancements + original_update_reachable_regions = CollectionState.update_reachable_regions + + class Run: + def __init__(self, index: int, seed: int): + self.index = index + self.seed = seed + self.monitored_rules = [] + self.collects = [] + self.removes = [] + self.gen_steps = {} + self.sweep_for_advancements = [] + self.update_reachable_regions = [] + + def apply_patches(self): + @wraps(original_set_location_rule) + def patched_set_location_rule(self_, location_name: str, rule: StardewRule) -> None: + wrapped = PerformanceMonitoringStardewRule("[location] " + location_name, rule) + self.monitored_rules.append(wrapped) + original_set_location_rule(self_, location_name, wrapped) + + StardewRuleCollector.set_location_rule = patched_set_location_rule + + @wraps(original_set_entrance_rule) + def patched_set_entrance_rule(self_, entrance_name: str, rule: StardewRule) -> None: + wrapped = PerformanceMonitoringStardewRule("[entrance] " + entrance_name, rule) + self.monitored_rules.append(wrapped) + original_set_entrance_rule(self_, entrance_name, wrapped) + + StardewRuleCollector.set_entrance_rule = patched_set_entrance_rule + + @wraps(original_collect) + def patched_collect(*args, **kwargs): + start = time.perf_counter_ns() + result = original_collect(*args, **kwargs) + end = time.perf_counter_ns() + self.collects.append(end - start) + return result + + StardewValleyWorld.collect = patched_collect + + @wraps(original_remove) + def patched_remove(*args, **kwargs): + start = time.perf_counter_ns() + result = original_remove(*args, **kwargs) + end = time.perf_counter_ns() + self.removes.append(end - start) + return result + + StardewValleyWorld.remove = patched_remove + + @wraps(AutoWorld.call_single) + def patched_call_single(self_, method_name: str, *args, **kwargs): + start = time.perf_counter_ns() + result = original_call_single(self_, method_name, *args, **kwargs) + end = time.perf_counter_ns() + self.gen_steps[method_name] = end - start + return result + + AutoWorld.call_single = patched_call_single + + @wraps(CollectionState.sweep_for_advancements) + def patched_sweep_for_advancements(self_, *args, **kwargs): + start = time.perf_counter_ns() + original_sweep_for_advancements(self_, *args, **kwargs) + end = time.perf_counter_ns() + self.sweep_for_advancements.append(end - start) + + CollectionState.sweep_for_advancements = patched_sweep_for_advancements + + @wraps(CollectionState.update_reachable_regions) + def patched_update_reachable_regions(self_, *args, **kwargs): + start = time.perf_counter_ns() + original_update_reachable_regions(self_, *args, **kwargs) + end = time.perf_counter_ns() + self.update_reachable_regions.append(end - start) + + CollectionState.update_reachable_regions = patched_update_reachable_regions + + def run_one_generation(self) -> None: + multiworld = setup_solo_multiworld(options, self.seed, _cache={}) + + fill_start = time.perf_counter_ns() + distribute_items_restrictive(multiworld) + fill_end = time.perf_counter_ns() + self.gen_steps['fill'] = fill_end - fill_start + + # print(explain(multiworld.get_location('Raccoon Request 5', 1).access_rule.delegate, multiworld.state, mode=ExplainMode.CLIENT)) + + def print_results(self) -> None: + sorted_by_duration = sorted(self.monitored_rules, key=lambda r: sum(call.duration_ns for call in r.calls), reverse=True) + print(f"Top 10 slowest rules for run {self.index + 1} | seed [{self.seed}]:") + for rule in sorted_by_duration[:10]: + total_duration = sum(call.duration_ns for call in rule.calls) + print(f"{rule.location}: {total_duration / 1_000_000:.2f} ms over {len(rule.calls)} calls") + print(f"Total generation steps took {sum(step for step in self.gen_steps.values()) / 1_000_000_000:.2f} s.\n") + + run_count = 1 + runs = [] + + def run_multiple_generations(): + nonlocal run_count + for i in range(run_count): + seed = get_seed(fixed_seed) + print(f"Running generation {i + 1} with seed {seed}") + run = Run(i, seed) + run.apply_patches() + gc.collect() + try: + run.run_one_generation() + except FillError as e: + print(e) + run_count -= 1 + continue + run.print_results() + runs.append(run) + + def print_cumulative_results(): + grouped: dict[str, list[RuleCall]] = defaultdict(list) + for run in runs: + for rule in run.monitored_rules: + for call in rule.calls: + grouped[rule.location].append(call) + + sorted_by_duration = sorted(grouped.items(), key=lambda item: sum(rule_call.duration_ns for rule_call in item[1]), reverse=True) + print(f"\nCumulative results across all {run_count} runs:") + for location, calls in sorted_by_duration[:10]: + total_duration = sum(call.duration_ns for call in calls) + avg_duration = total_duration / len(calls) + print(f"{location}: {total_duration / 1_000_000:.2f} ms over {len(calls)} calls ({avg_duration / 1_000_000:.2f} ms on average)") + + sorted_by_avg_duration = sorted( + grouped.items(), + key=lambda item: sum(rule_call.duration_ns for rule_call in item[1]) / len(item[1]), + reverse=True + ) + print("\nTop 10 average durations across all runs:") + for location, calls in sorted_by_avg_duration[:10]: + total_duration = sum(call.duration_ns for call in calls) + avg_duration = total_duration / len(calls) + print(f"{location}: average {avg_duration / 1_000_000:.2f} ms over {len(calls)} calls ({total_duration / 1_000_000:.2f} ms total)") + + print("\nStats for collection state:") + total_rules = sum(call.duration_ns for run in runs for rule in run.monitored_rules for call in rule.calls) + print(f"Total evaluating rules: {total_rules / 1_000_000:.2f} ms across all runs") + total_collects = sum(sum(run.collects) for run in runs) + avg_collects = total_collects / sum(len(run.collects) for run in runs) + print(f"Total collects: {total_collects / 1_000_000:.2f} ms, average per collect: {avg_collects / 1_000_000:.4f} ms") + total_removes = sum(sum(run.removes) for run in runs) + avg_removes = total_removes / sum(len(run.removes) for run in runs) if total_removes else 0 + print(f"Total removes: {total_removes / 1_000_000:.2f} ms, average per remove: {avg_removes / 1_000_000:.4f} ms") + total_sweep = sum(sum(run.sweep_for_advancements) for run in runs) + avg_sweep = total_sweep / sum(len(run.sweep_for_advancements) for run in runs) if total_sweep else 0 + print(f"Total sweep for advancements: {total_sweep / 1_000_000:.2f} ms, average per sweep: {avg_sweep / 1_000_000:.4f} ms") + total_update_reachable = sum(sum(run.update_reachable_regions) for run in runs) + avg_update_reachable = total_update_reachable / sum(len(run.update_reachable_regions) for run in runs) if total_update_reachable else 0 + print(f"Total update reachable regions: {total_update_reachable / 1_000_000:.2f} ms, average per update: {avg_update_reachable / 1_000_000:.4f} ms") + + print("\nGeneration steps:") + for step in gen_steps + ('fill',): + total_time = sum(run.gen_steps.get(step, 0) for run in runs) + avg_time = total_time / run_count + print(f"{step}: {total_time / 1_000_000:.2f} ms total, {avg_time / 1_000_000:.4f} ms average") + + run_multiple_generations() + print_cumulative_results() + + +if __name__ == "__main__": + generate_monitor_rules() diff --git a/worlds/stardew_valley/test/script/benchmark_locations.py b/worlds/stardew_valley/test/script/benchmark_locations.py index 3dcfc4dbebfb..7029a3c739af 100644 --- a/worlds/stardew_valley/test/script/benchmark_locations.py +++ b/worlds/stardew_valley/test/script/benchmark_locations.py @@ -8,8 +8,6 @@ import collections import gc import logging -import os -import sys import time import typing @@ -119,22 +117,8 @@ def dif(self): def __exit__(self, exc_type, exc_val, exc_tb): if not self.end_timer: self.end_timer = time.perf_counter() - if self.logger: - self.logger.info(f"{self.dif:.4f} seconds in {self.name}.") - - -def change_home(): - """Allow scripts to run from "this" folder.""" - old_home = os.path.dirname(__file__) - sys.path.remove(old_home) - new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) - os.chdir(new_home) - sys.path.append(new_home) - # fallback to local import - sys.path.append(old_home) - - from Utils import local_path - local_path.cached_path = new_home + # if self.logger: + # self.logger.info(f"{self.dif:.4f} seconds in {self.name}.") if __name__ == "__main__": diff --git a/worlds/stardew_valley/test/stability/StabilityOutputScript.py b/worlds/stardew_valley/test/stability/StabilityOutputScript.py index 29fd90309511..71d1cc34b63b 100644 --- a/worlds/stardew_valley/test/stability/StabilityOutputScript.py +++ b/worlds/stardew_valley/test/stability/StabilityOutputScript.py @@ -1,8 +1,8 @@ import argparse import json +from worlds.stardew_valley.test.options.presets import allsanity_mods_7_x_x_exclude_disabled from ..bases import setup_solo_multiworld -from ..options.presets import allsanity_mods_6_x_x_exclude_disabled from ...options import FarmType, EntranceRandomization if __name__ == "__main__": @@ -12,7 +12,7 @@ args = parser.parse_args() seed = args.seed - options = allsanity_mods_6_x_x_exclude_disabled() + options = allsanity_mods_7_x_x_exclude_disabled() options[FarmType.internal_name] = FarmType.option_standard options[EntranceRandomization.internal_name] = EntranceRandomization.option_buildings multi_world = setup_solo_multiworld(options, seed=seed) diff --git a/worlds/stardew_valley/test/stability/TestUniversalTracker.py b/worlds/stardew_valley/test/stability/TestUniversalTracker.py index 301abfff22bc..aae4406eb9f7 100644 --- a/worlds/stardew_valley/test/stability/TestUniversalTracker.py +++ b/worlds/stardew_valley/test/stability/TestUniversalTracker.py @@ -2,7 +2,7 @@ from unittest.mock import Mock from ..bases import skip_long_tests, SVTestBase -from ..options.presets import allsanity_mods_6_x_x +from ..options.presets import allsanity_mods_7_x_x from ..options.utils import fill_namespace_with_default from ... import STARDEW_VALLEY from ...options import FarmType, BundleRandomization, EntranceRandomization @@ -10,7 +10,7 @@ @unittest.skipIf(skip_long_tests(), "Long tests disabled") class TestUniversalTrackerGenerationIsStable(SVTestBase): - options = allsanity_mods_6_x_x() + options = allsanity_mods_7_x_x() options.update({ EntranceRandomization.internal_name: EntranceRandomization.option_buildings, BundleRandomization.internal_name: BundleRandomization.option_shuffled, diff --git a/worlds/stardew_valley/test/test_options/TestDynamicGingerIslandOptions.py b/worlds/stardew_valley/test/test_options/TestDynamicGingerIslandOptions.py new file mode 100644 index 000000000000..8d8d5c4f1168 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestDynamicGingerIslandOptions.py @@ -0,0 +1,41 @@ +import unittest +from typing import ClassVar + +from test.param import classvar_matrix +from ..options.option_names import get_all_option_choices +from ...options import ExcludeGingerIsland, ArcadeMachineLocations, BackpackProgression, BackpackSize, \ + BundlePerRoom, BundlePrice, ElevatorProgression, FarmType, SeasonRandomization, FestivalLocations, Moviesanity, Museumsanity, ToolProgression +from ...test.assertion import WorldAssertMixin +from ...test.bases import SVTestCase, solo_multiworld, skip_long_tests + +if skip_long_tests(): + raise unittest.SkipTest("Long tests disabled") + +# These options affect logic, but are unrelated to any ginger island content, so pointless for this specific test class +extra_options_to_ignore = [ArcadeMachineLocations.internal_name, BackpackProgression.internal_name, BackpackSize.internal_name, BundlePerRoom.internal_name, + BundlePrice.internal_name, ElevatorProgression.internal_name, FarmType.internal_name, SeasonRandomization.internal_name, + FestivalLocations.internal_name, Moviesanity.internal_name, Museumsanity.internal_name, ToolProgression.internal_name] + + +@classvar_matrix(option_and_choice=get_all_option_choices(extra_options_to_ignore)) +class TestGenerateAllOptionsWithExcludeGingerIsland(WorldAssertMixin, SVTestCase): + option_and_choice: ClassVar[tuple[str, str]] + + def test_given_choice_when_generate_exclude_ginger_island_then_ginger_island_is_properly_excluded(self): + option, option_choice = self.option_and_choice + + if option == ExcludeGingerIsland.internal_name: + self.skipTest("ExcludeGingerIsland is forced to true") + + world_options = { + ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, + option: option_choice + } + + with solo_multiworld(world_options) as (multiworld, stardew_world): + + if stardew_world.options.exclude_ginger_island != ExcludeGingerIsland.option_true: + self.skipTest("Some options, like goals, will force Ginger island back in the game. We want to skip testing those.") + + self.assert_basic_checks(multiworld) + self.assert_no_ginger_island_content(multiworld) \ No newline at end of file diff --git a/worlds/stardew_valley/test/test_options/TestDynamicOptions.py b/worlds/stardew_valley/test/test_options/TestDynamicOptions.py new file mode 100644 index 000000000000..7770b4b38432 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestDynamicOptions.py @@ -0,0 +1,25 @@ +import unittest +from typing import ClassVar + +from test.param import classvar_matrix +from ..options.option_names import get_all_option_choices +from ...options import BackpackProgression, BackpackSize, BundlePerRoom, BundlePrice, FarmType +from ...test.assertion import WorldAssertMixin +from ...test.bases import SVTestCase, solo_multiworld, skip_long_tests + +if skip_long_tests(): + raise unittest.SkipTest("Long tests disabled") + +extra_options_to_ignore = [BackpackProgression.internal_name, BackpackSize.internal_name, BundlePerRoom.internal_name, + BundlePrice.internal_name, FarmType.internal_name] + + +@classvar_matrix(option_and_choice=get_all_option_choices(extra_options_to_ignore)) +class TestGenerateDynamicOptions(WorldAssertMixin, SVTestCase): + option_and_choice: ClassVar[tuple[str, str]] + + def test_given_option_and_choice_when_generate_then_basic_checks(self): + option, choice = self.option_and_choice + world_options = {option: choice} + with solo_multiworld(world_options) as (multiworld, stardew_world): + self.assert_basic_checks(multiworld) \ No newline at end of file diff --git a/worlds/stardew_valley/test/options/TestForcedOptions.py b/worlds/stardew_valley/test/test_options/TestForcedOptions.py similarity index 98% rename from worlds/stardew_valley/test/options/TestForcedOptions.py rename to worlds/stardew_valley/test/test_options/TestForcedOptions.py index c32def6c6ca8..357da631bce4 100644 --- a/worlds/stardew_valley/test/options/TestForcedOptions.py +++ b/worlds/stardew_valley/test/test_options/TestForcedOptions.py @@ -2,9 +2,9 @@ import unittest import Options as ap_options -from .utils import fill_dataclass_with_default -from ... import options +from ...options import options from ...options.forced_options import force_change_options_if_incompatible +from ...test.options.utils import fill_dataclass_with_default class TestGoalsRequiringAllLocationsOverrideAccessibility(unittest.TestCase): diff --git a/worlds/stardew_valley/test/test_options/TestGoal.py b/worlds/stardew_valley/test/test_options/TestGoal.py new file mode 100644 index 000000000000..2f0b3e7b8a45 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestGoal.py @@ -0,0 +1,27 @@ +from typing import ClassVar + +from test.param import classvar_matrix +from ...options import Goal +from ...strings.goal_names import Goal as GoalName +from ...test.bases import SVTestCase, solo_multiworld + + +@classvar_matrix(goal_and_location=[ + ("community_center", GoalName.community_center), + ("grandpa_evaluation", GoalName.grandpa_evaluation), + ("bottom_of_the_mines", GoalName.bottom_of_the_mines), + ("cryptic_note", GoalName.cryptic_note), + ("master_angler", GoalName.master_angler), + ("complete_collection", GoalName.complete_museum), + ("full_house", GoalName.full_house), + ("perfection", GoalName.perfection), +]) +class TestGoal(SVTestCase): + goal_and_location: ClassVar[tuple[str, str]] + + def test_given_goal_when_generate_then_victory_is_in_correct_location(self): + goal, location = self.goal_and_location + world_options = {Goal.internal_name: goal} + with solo_multiworld(world_options) as (multi_world, _): + victory = multi_world.find_item("Victory", 1) + self.assertEqual(victory.name, location) \ No newline at end of file diff --git a/worlds/stardew_valley/test/TestOptionFlags.py b/worlds/stardew_valley/test/test_options/TestOptionFlags.py similarity index 95% rename from worlds/stardew_valley/test/TestOptionFlags.py rename to worlds/stardew_valley/test/test_options/TestOptionFlags.py index d93015756486..9a5f4f5456a1 100644 --- a/worlds/stardew_valley/test/TestOptionFlags.py +++ b/worlds/stardew_valley/test/test_options/TestOptionFlags.py @@ -1,6 +1,5 @@ -from .bases import SVTestBase -from .. import BuildingProgression -from ..options import ToolProgression +from ...options import ToolProgression, BuildingProgression +from ...test.bases import SVTestBase class TestBitFlagsVanilla(SVTestBase): diff --git a/worlds/stardew_valley/test/TestOptionsPairs.py b/worlds/stardew_valley/test/test_options/TestOptionsPairs.py similarity index 93% rename from worlds/stardew_valley/test/TestOptionsPairs.py rename to worlds/stardew_valley/test/test_options/TestOptionsPairs.py index addd748c424c..e8310b92c75c 100644 --- a/worlds/stardew_valley/test/TestOptionsPairs.py +++ b/worlds/stardew_valley/test/test_options/TestOptionsPairs.py @@ -1,6 +1,6 @@ -from .assertion import WorldAssertMixin -from .bases import SVTestBase -from .. import options +from worlds.stardew_valley import options +from ...test.assertion import WorldAssertMixin +from ...test.bases import SVTestBase class TestCrypticNoteNoQuests(WorldAssertMixin, SVTestBase): diff --git a/worlds/stardew_valley/test/options/TestPresets.py b/worlds/stardew_valley/test/test_options/TestPresets.py similarity index 83% rename from worlds/stardew_valley/test/options/TestPresets.py rename to worlds/stardew_valley/test/test_options/TestPresets.py index 5c1cee4a58b9..b49f7985e519 100644 --- a/worlds/stardew_valley/test/options/TestPresets.py +++ b/worlds/stardew_valley/test/test_options/TestPresets.py @@ -1,13 +1,13 @@ from Options import PerGameCommonOptions, OptionSet, OptionDict -from ..bases import SVTestCase -from ...options import StardewValleyOptions, TrapItems +from ...options import StardewValleyOptions, TrapItems, BundlePlando from ...options.presets import sv_options_presets +from ...test.bases import SVTestCase class TestPresets(SVTestCase): def test_all_presets_explicitly_set_all_options(self): all_option_names = {option_key for option_key in StardewValleyOptions.type_hints} - omitted_option_names = {option_key for option_key in PerGameCommonOptions.type_hints} | {TrapItems.internal_name} + omitted_option_names = {option_key for option_key in PerGameCommonOptions.type_hints} | {TrapItems.internal_name, BundlePlando.internal_name} mandatory_option_names = {option_key for option_key in all_option_names if option_key not in omitted_option_names and not issubclass(StardewValleyOptions.type_hints[option_key], OptionSet | OptionDict)} diff --git a/worlds/stardew_valley/test/test_options/TestSeasonOptions.py b/worlds/stardew_valley/test/test_options/TestSeasonOptions.py new file mode 100644 index 000000000000..ce26e216af58 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestSeasonOptions.py @@ -0,0 +1,28 @@ +from ...options import SeasonRandomization +from ...strings.season_names import Season +from ...test.bases import SVTestCase, solo_multiworld + + +SEASONS = {Season.spring, Season.summer, Season.fall, Season.winter} + + +class TestSeasonOptions(SVTestCase): + def test_given_disabled_when_generate_then_all_seasons_are_precollected(self): + world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_disabled} + with solo_multiworld(world_options) as (multi_world, _): + precollected_items = {item.name for item in multi_world.precollected_items[1]} + self.assertTrue(all([season in precollected_items for season in SEASONS])) + + def test_given_randomized_when_generate_then_all_seasons_are_in_the_pool_or_precollected(self): + world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_randomized} + with solo_multiworld(world_options) as (multi_world, _): + precollected_items = {item.name for item in multi_world.precollected_items[1]} + items = {item.name for item in multi_world.get_items()} | precollected_items + self.assertTrue(all([season in items for season in SEASONS])) + self.assertEqual(len(SEASONS.intersection(precollected_items)), 1) + + def test_given_progressive_when_generate_then_3_progressive_seasons_are_in_the_pool(self): + world_options = {SeasonRandomization.internal_name: SeasonRandomization.option_progressive} + with solo_multiworld(world_options) as (multi_world, _): + items = [item.name for item in multi_world.get_items()] + self.assertEqual(items.count(Season.progressive), 3) \ No newline at end of file diff --git a/worlds/stardew_valley/test/test_options/TestSpecialOrderOptions.py b/worlds/stardew_valley/test/test_options/TestSpecialOrderOptions.py new file mode 100644 index 000000000000..a7b2396d3ad9 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestSpecialOrderOptions.py @@ -0,0 +1,56 @@ +from ...locations import locations_by_tag, LocationTags, location_table +from ...options import ExcludeGingerIsland, SpecialOrderLocations, ArcadeMachineLocations, Mods, all_mods_except_invalid_combinations +from ...strings.special_order_names import SpecialOrder +from ...test.bases import SVTestCase, solo_multiworld + + +class TestSpecialOrders(SVTestCase): + def test_given_disabled_then_no_order_in_pool(self): + world_options = {SpecialOrderLocations.internal_name: SpecialOrderLocations.option_vanilla} + with solo_multiworld(world_options) as (multi_world, _): + locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table} + for location_name in locations_in_pool: + location = location_table[location_name] + self.assertNotIn(LocationTags.SPECIAL_ORDER_BOARD, location.tags) + self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags) + + def test_given_board_only_then_no_qi_order_in_pool(self): + world_options = { + SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board, + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), + } + with solo_multiworld(world_options) as (multi_world, _): + + locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table} + for location_name in locations_in_pool: + location = location_table[location_name] + self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags) + + for board_location in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: + self.assertIn(board_location.name, locations_in_pool) + + def test_given_board_and_qi_then_all_orders_in_pool(self): + world_options = { + SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi, + ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_victories, + ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false, + Mods.internal_name: frozenset(all_mods_except_invalid_combinations), + } + with solo_multiworld(world_options) as (multi_world, _): + + locations_in_pool = {location.name for location in multi_world.get_locations()} + for qi_location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI]: + self.assertIn(qi_location.name, locations_in_pool) + + for board_location in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: + self.assertIn(board_location.name, locations_in_pool) + + def test_given_board_and_qi_without_arcade_machines_then_lets_play_a_game_not_in_pool(self): + world_options = { + SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi, + ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled, + ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false, + } + with solo_multiworld(world_options) as (multi_world, _): + locations_in_pool = {location.name for location in multi_world.get_locations()} + self.assertNotIn(SpecialOrder.lets_play_a_game, locations_in_pool) diff --git a/worlds/stardew_valley/test/test_options/TestToolOptions.py b/worlds/stardew_valley/test/test_options/TestToolOptions.py new file mode 100644 index 000000000000..3e5272288b29 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestToolOptions.py @@ -0,0 +1,45 @@ +import itertools +from collections import Counter + +from BaseClasses import ItemClassification +from ...options import ToolProgression, StartWithout +from ...strings.tool_names import ToolMaterial, Tool, APTool +from ...test.bases import SVTestBase + +TOOLS = {"Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can", "Fishing Rod"} + + +class TestToolProgression(SVTestBase): + options = { + ToolProgression.internal_name: ToolProgression.option_progressive, + StartWithout.internal_name: StartWithout.preset_none, + } + + def test_given_progressive_when_generate_then_tool_upgrades_are_locations(self): + locations = set(self.get_real_location_names()) + for material, tool in itertools.product(ToolMaterial.tiers.values(), + [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can]): + if material == ToolMaterial.basic: + continue + self.assertIn(f"{material} {tool} Upgrade", locations) + self.assertIn("Purchase Training Rod", locations) + self.assertIn("Bamboo Pole Cutscene", locations) + self.assertIn("Purchase Fiberglass Rod", locations) + self.assertIn("Purchase Iridium Rod", locations) + + def test_given_progressive_when_generate_then_last_trash_can_is_classified_differently(self): + trash_cans = self.get_items_by_name(APTool.trash_can) + progressive_count = sum([1 for item in trash_cans if item.classification == ItemClassification.progression]) + special_count = sum([1 for item in trash_cans if item.classification == ItemClassification.useful]) + + self.assertEqual(3, progressive_count) + self.assertEqual(1, special_count) + + def test_given_progressive_when_generate_then_last_trash_can_changes_classification_post_fill(self): + trash_can, post_fill_classification = next((classification_to_update + for classification_to_update in self.world.classifications_to_override_post_fill + if classification_to_update[0].name == APTool.trash_can), + (None, None)) + + self.assertEqual(post_fill_classification, ItemClassification.progression_skip_balancing) + self.assertEqual(Counter(), trash_can.events_to_collect) diff --git a/worlds/stardew_valley/test/test_options/TestTrapOptions.py b/worlds/stardew_valley/test/test_options/TestTrapOptions.py new file mode 100644 index 000000000000..1548b4943711 --- /dev/null +++ b/worlds/stardew_valley/test/test_options/TestTrapOptions.py @@ -0,0 +1,28 @@ +from ...items import items_by_group, Group +from ...options import TrapDifficulty +from ...test.bases import SVTestCase, solo_multiworld +from ...test.options.presets import allsanity_mods_7_x_x, allsanity_no_mods_7_x_x + + +class TestTraps(SVTestCase): + def test_given_no_traps_when_generate_then_no_trap_in_pool(self): + world_options = allsanity_no_mods_7_x_x().copy() + world_options[TrapDifficulty.internal_name] = TrapDifficulty.option_no_traps + with solo_multiworld(world_options) as (multi_world, _): + trap_items = [item_data.name for item_data in items_by_group[Group.TRAP]] + multiworld_items = [item.name for item in multi_world.get_items()] + + for item in trap_items: + with self.subTest(f"{item}"): + self.assertNotIn(item, multiworld_items) + + def test_given_traps_when_generate_then_all_traps_in_pool(self): + trap_option = TrapDifficulty + world_options = allsanity_mods_7_x_x() + world_options.update({TrapDifficulty.internal_name: trap_option.option_easy}) + with solo_multiworld(world_options) as (multi_world, _): + trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if Group.DEPRECATED not in item_data.groups] + multiworld_items = [item.name for item in multi_world.get_items()] + for item in trap_items: + with self.subTest(f"Item: {item}"): + self.assertIn(item, multiworld_items) \ No newline at end of file diff --git a/worlds/stardew_valley/test/test_options/__init__.py b/worlds/stardew_valley/test/test_options/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/stardew_valley/utility/__init__.py b/worlds/stardew_valley/utility/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/worlds/stardew_valley/utility/logging_random.py b/worlds/stardew_valley/utility/logging_random.py new file mode 100644 index 000000000000..2cbe352a7cb0 --- /dev/null +++ b/worlds/stardew_valley/utility/logging_random.py @@ -0,0 +1,35 @@ +from random import Random + + +class LoggingRandom: + internal_random: Random + total_calls: int + + def __init__(self, seed): + # logger.warning(f"Initializing LoggingRandom with seed: {seed}") + self.internal_random = Random(seed) + self.total_calls = 0 + + def sample(self, population, k, *, counts=None): + self.total_calls += 1 + return self.internal_random.sample(population=population, k=k, counts=counts) + + def choice(self, seq): + self.total_calls += 1 + return self.internal_random.choice(seq) + + def choices(self, population, weights=None, *, cum_weights=None, k=1): + self.total_calls += 1 + return self.internal_random.choices(population=population, weights=weights, cum_weights=cum_weights) + + def randint(self, a, b): + self.total_calls += 1 + return self.internal_random.randint(a, b) + + def shuffle(self, x): + self.total_calls += 1 + return self.internal_random.shuffle(x) + + def randrange(self, start, stop=None, step=1): + self.total_calls += 1 + return self.internal_random.randrange(start=start, stop=stop, step=step) \ No newline at end of file diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 1d59eeaecadf..78c9dcdb67b9 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -348,6 +348,15 @@ def create_item(self, name: str, classification: ItemClassification = None) -> T if name == "Shield" and self.options.ladder_storage and not self.options.ladder_storage_without_items else None) or item_data.classification) + # if there's 6 or less in the pool, then I could see them landing on priority locations being desireable + if name == "Gold Questagon" and self.options.hexagon_goal > 6: + itemclass = itemclass | ItemClassification.deprioritized + # remove deprioritized from Fairies and Coins if Laurels are there + if name == "Fairy" and self.options.laurels_location == LaurelsLocation.option_10_fairies: + itemclass = ItemClassification.progression + if (name == "Golden Coin" and self.options.laurels_location in + (LaurelsLocation.option_10_coins, LaurelsLocation.option_6_coins)): + itemclass = ItemClassification.progression return TunicItem(name, itemclass, self.item_name_to_id[name], self.player) def create_items(self) -> None: diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py index e8f201b92670..1e24b3c0d77c 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -30,14 +30,14 @@ class TunicItemData(NamedTuple): "Lure x2": TunicItemData(IC.filler, 1, 11, "Consumables"), "Pepper x2": TunicItemData(IC.filler, 4, 12, "Consumables"), "Ivy x3": TunicItemData(IC.filler, 2, 13, "Consumables"), - "Effigy": TunicItemData(IC.useful, 12, 14, "Money", combat_ic=IC.progression), + "Effigy": TunicItemData(IC.useful, 12, 14, "Money", combat_ic=IC.progression | IC.deprioritized), "HP Berry": TunicItemData(IC.filler, 2, 15, "Consumables"), "HP Berry x2": TunicItemData(IC.filler, 4, 16, "Consumables"), "HP Berry x3": TunicItemData(IC.filler, 2, 17, "Consumables"), "MP Berry": TunicItemData(IC.filler, 4, 18, "Consumables"), "MP Berry x2": TunicItemData(IC.filler, 2, 19, "Consumables"), "MP Berry x3": TunicItemData(IC.filler, 7, 20, "Consumables"), - "Fairy": TunicItemData(IC.progression, 20, 21), + "Fairy": TunicItemData(IC.progression | IC.deprioritized, 20, 21), "Stick": TunicItemData(IC.progression | IC.useful, 1, 22, "Weapons"), "Sword": TunicItemData(IC.progression | IC.useful, 3, 23, "Weapons"), "Sword Upgrade": TunicItemData(IC.progression | IC.useful, 4, 24, "Weapons"), @@ -52,22 +52,22 @@ class TunicItemData(NamedTuple): "Torch": TunicItemData(IC.useful, 0, 156), "Hourglass": TunicItemData(IC.useful, 1, 33), "Old House Key": TunicItemData(IC.progression, 1, 34, "Keys"), - "Key": TunicItemData(IC.progression, 2, 35, "Keys"), + "Key": TunicItemData(IC.progression | IC.deprioritized, 2, 35, "Keys"), "Fortress Vault Key": TunicItemData(IC.progression, 1, 36, "Keys"), - "Flask Shard": TunicItemData(IC.useful, 12, 37, combat_ic=IC.progression), + "Flask Shard": TunicItemData(IC.useful, 12, 37, combat_ic=IC.progression | IC.deprioritized), "Potion Flask": TunicItemData(IC.useful, 5, 38, "Flask", combat_ic=IC.progression), - "Golden Coin": TunicItemData(IC.progression, 17, 39), + "Golden Coin": TunicItemData(IC.progression | IC.deprioritized, 17, 39), "Card Slot": TunicItemData(IC.useful, 4, 40), "Red Questagon": TunicItemData(IC.progression_skip_balancing, 1, 41, "Hexagons"), "Green Questagon": TunicItemData(IC.progression_skip_balancing, 1, 42, "Hexagons"), "Blue Questagon": TunicItemData(IC.progression_skip_balancing, 1, 43, "Hexagons"), "Gold Questagon": TunicItemData(IC.progression_skip_balancing, 0, 44, "Hexagons"), "ATT Offering": TunicItemData(IC.useful, 4, 45, "Offerings", combat_ic=IC.progression), - "DEF Offering": TunicItemData(IC.useful, 4, 46, "Offerings", combat_ic=IC.progression), - "Potion Offering": TunicItemData(IC.useful, 3, 47, "Offerings", combat_ic=IC.progression), - "HP Offering": TunicItemData(IC.useful, 6, 48, "Offerings", combat_ic=IC.progression), - "MP Offering": TunicItemData(IC.useful, 3, 49, "Offerings", combat_ic=IC.progression), - "SP Offering": TunicItemData(IC.useful, 2, 50, "Offerings", combat_ic=IC.progression), + "DEF Offering": TunicItemData(IC.useful, 4, 46, "Offerings", combat_ic=IC.progression | IC.deprioritized), + "Potion Offering": TunicItemData(IC.useful, 3, 47, "Offerings", combat_ic=IC.progression | IC.deprioritized), + "HP Offering": TunicItemData(IC.useful, 6, 48, "Offerings", combat_ic=IC.progression | IC.deprioritized), + "MP Offering": TunicItemData(IC.useful, 3, 49, "Offerings", combat_ic=IC.progression | IC.deprioritized), + "SP Offering": TunicItemData(IC.useful, 2, 50, "Offerings", combat_ic=IC.progression | IC.deprioritized), "Hero Relic - ATT": TunicItemData(IC.progression_skip_balancing, 1, 51, "Hero Relics", combat_ic=IC.progression), "Hero Relic - DEF": TunicItemData(IC.progression_skip_balancing, 1, 52, "Hero Relics", combat_ic=IC.progression), "Hero Relic - HP": TunicItemData(IC.progression_skip_balancing, 1, 53, "Hero Relics", combat_ic=IC.progression), @@ -91,17 +91,17 @@ class TunicItemData(NamedTuple): "Aura's Gem": TunicItemData(IC.useful, 1, 71, "Cards"), "Bone Card": TunicItemData(IC.useful, 1, 72, "Cards"), "Mr Mayor": TunicItemData(IC.useful, 1, 73, "Golden Treasures", combat_ic=IC.progression), - "Secret Legend": TunicItemData(IC.useful, 1, 74, "Golden Treasures", combat_ic=IC.progression), - "Sacred Geometry": TunicItemData(IC.useful, 1, 75, "Golden Treasures", combat_ic=IC.progression), - "Vintage": TunicItemData(IC.useful, 1, 76, "Golden Treasures", combat_ic=IC.progression), - "Just Some Pals": TunicItemData(IC.useful, 1, 77, "Golden Treasures", combat_ic=IC.progression), - "Regal Weasel": TunicItemData(IC.useful, 1, 78, "Golden Treasures", combat_ic=IC.progression), - "Spring Falls": TunicItemData(IC.useful, 1, 79, "Golden Treasures", combat_ic=IC.progression), - "Power Up": TunicItemData(IC.useful, 1, 80, "Golden Treasures", combat_ic=IC.progression), - "Back To Work": TunicItemData(IC.useful, 1, 81, "Golden Treasures", combat_ic=IC.progression), - "Phonomath": TunicItemData(IC.useful, 1, 82, "Golden Treasures", combat_ic=IC.progression), - "Dusty": TunicItemData(IC.useful, 1, 83, "Golden Treasures", combat_ic=IC.progression), - "Forever Friend": TunicItemData(IC.useful, 1, 84, "Golden Treasures", combat_ic=IC.progression), + "Secret Legend": TunicItemData(IC.useful, 1, 74, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Sacred Geometry": TunicItemData(IC.useful, 1, 75, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Vintage": TunicItemData(IC.useful, 1, 76, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Just Some Pals": TunicItemData(IC.useful, 1, 77, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Regal Weasel": TunicItemData(IC.useful, 1, 78, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Spring Falls": TunicItemData(IC.useful, 1, 79, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Power Up": TunicItemData(IC.useful, 1, 80, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Back To Work": TunicItemData(IC.useful, 1, 81, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Phonomath": TunicItemData(IC.useful, 1, 82, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Dusty": TunicItemData(IC.useful, 1, 83, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), + "Forever Friend": TunicItemData(IC.useful, 1, 84, "Golden Treasures", combat_ic=IC.progression | IC.deprioritized), "Fool Trap": TunicItemData(IC.trap, 0, 85), "Money x1": TunicItemData(IC.filler, 3, 86, "Money"), "Money x2": TunicItemData(IC.filler, 0, 152, "Money"), @@ -120,9 +120,9 @@ class TunicItemData(NamedTuple): "Money x50": TunicItemData(IC.filler, 7, 96, "Money"), "Money x64": TunicItemData(IC.filler, 1, 97, "Money"), "Money x100": TunicItemData(IC.filler, 5, 98, "Money"), - "Money x128": TunicItemData(IC.useful, 3, 99, "Money", combat_ic=IC.progression), - "Money x200": TunicItemData(IC.useful, 1, 100, "Money", combat_ic=IC.progression), - "Money x255": TunicItemData(IC.useful, 1, 101, "Money", combat_ic=IC.progression), + "Money x128": TunicItemData(IC.useful, 3, 99, "Money", combat_ic=IC.progression | IC.deprioritized), + "Money x200": TunicItemData(IC.useful, 1, 100, "Money", combat_ic=IC.progression | IC.deprioritized), + "Money x255": TunicItemData(IC.useful, 1, 101, "Money", combat_ic=IC.progression | IC.deprioritized), "Pages 0-1": TunicItemData(IC.useful, 1, 102, "Pages"), "Pages 2-3": TunicItemData(IC.useful, 1, 103, "Pages"), "Pages 4-5": TunicItemData(IC.useful, 1, 104, "Pages"),