Stardew Valley 6.x.x: The Content Update (#3478)

Focus of the Update: Compatibility with Stardew Valley 1.6 Released on March 19th 2024
This includes randomization for pretty much all of the new content, including but not limited to
- Raccoon Bundles
- Booksanity
- Skill Masteries
- New Recipes, Craftables, Fish, Maps, Farm Type, Festivals and Quests

This also includes a significant reorganisation of the code into "Content Packs", to allow for easier modularity of various game mechanics between the settings and the supported mods. This improves maintainability quite a bit.

In addition to that, a few **very** requested new features have been introduced, although they weren't the focus of this update
- Walnutsanity
- Player Buffs
- More customizability in settings, such as shorter special orders, ER without farmhouse
- New Remixed Bundles
This commit is contained in:
agilbert1412 2024-07-07 16:04:25 +03:00 committed by GitHub
parent f99ee77325
commit 9b22458f44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
210 changed files with 10298 additions and 4540 deletions

View File

@ -1,12 +1,13 @@
import logging
from typing import Dict, Any, Iterable, Optional, Union, List, TextIO
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState
from Options import PerGameCommonOptions
from worlds.AutoWorld import World, WebWorld
from . import rules
from .bundles.bundle_room import BundleRoom
from .bundles.bundles import get_all_bundles
from .content import content_packs, StardewContent, unpack_content, create_content
from .early_items import setup_early_items
from .items import item_table, create_items, ItemData, Group, items_by_group, get_all_filler_items, remove_limited_amount_packs
from .locations import location_table, create_locations, LocationData, locations_by_tag
@ -14,16 +15,17 @@ from .logic.bundle_logic import BundleLogic
from .logic.logic import StardewLogic
from .logic.time_logic import MAX_MONTHS
from .option_groups import sv_option_groups
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, NumberOfLuckBuffs, NumberOfMovementBuffs, \
BackpackProgression, BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, EnabledFillerBuffs, NumberOfMovementBuffs, \
BackpackProgression, BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType, Walnutsanity
from .presets import sv_options_presets
from .regions import create_regions
from .rules import set_rules
from .stardew_rule import True_, StardewRule, HasProgressionPercent
from .stardew_rule import True_, StardewRule, HasProgressionPercent, true_
from .strings.ap_names.event_names import Event
from .strings.entrance_names import Entrance as EntranceName
from .strings.goal_names import Goal as GoalName
from .strings.region_names import Region as RegionName
from .strings.metal_names import Ore
from .strings.region_names import Region as RegionName, LogicRegion
client_version = 0
@ -77,6 +79,7 @@ class StardewValleyWorld(World):
options_dataclass = StardewValleyOptions
options: StardewValleyOptions
content: StardewContent
logic: StardewLogic
web = StardewWebWorld()
@ -94,6 +97,7 @@ class StardewValleyWorld(World):
def generate_early(self):
self.force_change_options_if_incompatible()
self.content = create_content(self.options)
def force_change_options_if_incompatible(self):
goal_is_walnut_hunter = self.options.goal == Goal.option_greatest_walnut_hunter
@ -106,6 +110,11 @@ class StardewValleyWorld(World):
player_name = self.multiworld.player_name[self.player]
logging.warning(
f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
if exclude_ginger_island and self.options.walnutsanity != Walnutsanity.preset_none:
self.options.walnutsanity.value = Walnutsanity.preset_none
player_name = self.multiworld.player_name[self.player]
logging.warning(
f"Walnutsanity requires Ginger Island. Ginger Island was excluded from {self.player} ({player_name})'s world, so walnutsanity was force disabled")
def create_regions(self):
def create_region(name: str, exits: Iterable[str]) -> Region:
@ -115,9 +124,10 @@ class StardewValleyWorld(World):
world_regions, world_entrances, self.randomized_entrances = create_regions(create_region, self.random, self.options)
self.logic = StardewLogic(self.player, self.options, world_regions.keys())
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)
def add_location(name: str, code: Optional[int], region: str):
@ -125,11 +135,12 @@ class StardewValleyWorld(World):
location = StardewLocation(self.player, name, code, region)
region.locations.append(location)
create_locations(add_location, self.modified_bundles, self.options, self.random)
create_locations(add_location, self.modified_bundles, self.options, self.content, self.random)
self.multiworld.regions.extend(world_regions.values())
def create_items(self):
self.precollect_starting_season()
self.precollect_farm_type_items()
items_to_exclude = [excluded_items
for excluded_items in self.multiworld.precollected_items[self.player]
if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK,
@ -143,7 +154,7 @@ class StardewValleyWorld(World):
for location in self.multiworld.get_locations(self.player)
if location.address is not None])
created_items = create_items(self.create_item, self.delete_item, locations_count, items_to_exclude, self.options,
created_items = create_items(self.create_item, self.delete_item, locations_count, items_to_exclude, self.options, self.content,
self.random)
self.multiworld.itempool += created_items
@ -173,10 +184,15 @@ class StardewValleyWorld(World):
starting_season = self.create_starting_item(self.random.choice(season_pool))
self.multiworld.push_precollected(starting_season)
def precollect_farm_type_items(self):
if self.options.farm_type == FarmType.option_meadowlands and self.options.building_progression & BuildingProgression.option_progressive:
self.multiworld.push_precollected(self.create_starting_item("Progressive Coop"))
def setup_player_events(self):
self.setup_construction_events()
self.setup_quest_events()
self.setup_action_events()
self.setup_logic_events()
def setup_construction_events(self):
can_construct_buildings = LocationData(None, RegionName.carpenter, Event.can_construct_buildings)
@ -187,10 +203,26 @@ class StardewValleyWorld(World):
self.create_event_location(start_dark_talisman_quest, self.logic.wallet.has_rusty_key(), Event.start_dark_talisman_quest)
def setup_action_events(self):
can_ship_event = LocationData(None, RegionName.shipping, Event.can_ship_items)
self.create_event_location(can_ship_event, True_(), Event.can_ship_items)
can_ship_event = LocationData(None, LogicRegion.shipping, Event.can_ship_items)
self.create_event_location(can_ship_event, true_, Event.can_ship_items)
can_shop_pierre_event = LocationData(None, RegionName.pierre_store, Event.can_shop_at_pierre)
self.create_event_location(can_shop_pierre_event, True_(), Event.can_shop_at_pierre)
self.create_event_location(can_shop_pierre_event, true_, Event.can_shop_at_pierre)
spring_farming = LocationData(None, LogicRegion.spring_farming, Event.spring_farming)
self.create_event_location(spring_farming, true_, Event.spring_farming)
summer_farming = LocationData(None, LogicRegion.summer_farming, Event.summer_farming)
self.create_event_location(summer_farming, true_, Event.summer_farming)
fall_farming = LocationData(None, LogicRegion.fall_farming, Event.fall_farming)
self.create_event_location(fall_farming, true_, Event.fall_farming)
winter_farming = LocationData(None, LogicRegion.winter_farming, Event.winter_farming)
self.create_event_location(winter_farming, true_, Event.winter_farming)
def setup_logic_events(self):
def register_event(name: str, region: str, rule: StardewRule):
event_location = LocationData(None, region, name)
self.create_event_location(event_location, rule, name)
self.logic.setup_events(register_event)
def setup_victory(self):
if self.options.goal == Goal.option_community_center:
@ -211,7 +243,7 @@ class StardewValleyWorld(World):
Event.victory)
elif self.options.goal == Goal.option_master_angler:
self.create_event_location(location_table[GoalName.master_angler],
self.logic.fishing.can_catch_every_fish_in_slot(self.get_all_location_names()),
self.logic.fishing.can_catch_every_fish_for_fishsanity(),
Event.victory)
elif self.options.goal == Goal.option_complete_collection:
self.create_event_location(location_table[GoalName.complete_museum],
@ -270,18 +302,13 @@ class StardewValleyWorld(World):
if override_classification is None:
override_classification = item.classification
if override_classification == ItemClassification.progression and item.name != Event.victory:
if override_classification == ItemClassification.progression:
self.total_progression_items += 1
# if item.name not in self.all_progression_items:
# self.all_progression_items[item.name] = 0
# self.all_progression_items[item.name] += 1
return StardewItem(item.name, override_classification, item.code, self.player)
def delete_item(self, item: Item):
if item.classification & ItemClassification.progression:
self.total_progression_items -= 1
# if item.name in self.all_progression_items:
# self.all_progression_items[item.name] -= 1
def create_starting_item(self, item: Union[str, ItemData]) -> StardewItem:
if isinstance(item, str):
@ -299,7 +326,11 @@ class StardewValleyWorld(World):
location = StardewLocation(self.player, location_data.name, None, region)
location.access_rule = rule
region.locations.append(location)
location.place_locked_item(self.create_item(item))
location.place_locked_item(StardewItem(item, ItemClassification.progression, None, self.player))
# This is not ideal, but the rule count them so...
if item != Event.victory:
self.total_progression_items += 1
def set_rules(self):
set_rules(self)
@ -358,7 +389,7 @@ class StardewValleyWorld(World):
quality = ""
else:
quality = f" ({item.quality.split(' ')[0]})"
spoiler_handle.write(f"\t\t{item.amount}x {item.item_name}{quality}\n")
spoiler_handle.write(f"\t\t{item.amount}x {item.get_item()}{quality}\n")
def add_entrances_to_spoiler_log(self):
if self.options.entrance_randomization == EntranceRandomization.option_disabled:
@ -373,9 +404,9 @@ class StardewValleyWorld(World):
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.item_name}|{item.amount}|{item.quality}"
bundles[room.name][bundle.name][i] = f"{item.get_item()}|{item.amount}|{item.quality}"
excluded_options = [BundleRandomization, NumberOfMovementBuffs, NumberOfLuckBuffs]
excluded_options = [BundleRandomization, NumberOfMovementBuffs, EnabledFillerBuffs]
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)
@ -385,7 +416,29 @@ class StardewValleyWorld(World):
"seed": self.random.randrange(1000000000), # Seed should be max 9 digits
"randomized_entrances": self.randomized_entrances,
"modified_bundles": bundles,
"client_version": "5.0.0",
"client_version": "6.0.0",
})
return slot_data
def collect(self, state: CollectionState, item: StardewItem) -> bool:
change = super().collect(state, item)
if change:
state.prog_items[self.player][Event.received_walnuts] += self.get_walnut_amount(item.name)
return change
def remove(self, state: CollectionState, item: StardewItem) -> bool:
change = super().remove(state, item)
if change:
state.prog_items[self.player][Event.received_walnuts] -= self.get_walnut_amount(item.name)
return change
@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

View File

@ -1,8 +1,10 @@
import math
from dataclasses import dataclass
from random import Random
from typing import List
from typing import List, Tuple
from .bundle_item import BundleItem
from ..content import StardewContent
from ..options import BundlePrice, StardewValleyOptions, ExcludeGingerIsland, FestivalLocations
from ..strings.currency_names import Currency
@ -26,7 +28,8 @@ class BundleTemplate:
number_possible_items: int
number_required_items: int
def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int, number_required_items: int):
def __init__(self, room: str, name: str, items: List[BundleItem], number_possible_items: int,
number_required_items: int):
self.room = room
self.name = name
self.items = items
@ -35,17 +38,12 @@ class BundleTemplate:
@staticmethod
def extend_from(template, items: List[BundleItem]):
return BundleTemplate(template.room, template.name, items, template.number_possible_items, template.number_required_items)
return BundleTemplate(template.room, template.name, items, template.number_possible_items,
template.number_required_items)
def create_bundle(self, bundle_price_option: BundlePrice, random: Random, options: StardewValleyOptions) -> Bundle:
if bundle_price_option == BundlePrice.option_minimum:
number_required = 1
elif bundle_price_option == BundlePrice.option_maximum:
number_required = 8
else:
number_required = self.number_required_items + bundle_price_option.value
number_required = max(1, number_required)
filtered_items = [item for item in self.items if item.can_appear(options)]
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)
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:
@ -55,6 +53,7 @@ class BundleTemplate:
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 can_appear(self, options: StardewValleyOptions) -> bool:
@ -68,19 +67,13 @@ class CurrencyBundleTemplate(BundleTemplate):
super().__init__(room, name, [item], 1, 1)
self.item = item
def create_bundle(self, bundle_price_option: BundlePrice, random: Random, options: StardewValleyOptions) -> Bundle:
currency_amount = self.get_currency_amount(bundle_price_option)
def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
currency_amount = self.get_currency_amount(options.bundle_price)
return Bundle(self.room, self.name, [BundleItem(self.item.item_name, currency_amount)], 1)
def get_currency_amount(self, bundle_price_option: BundlePrice):
if bundle_price_option == BundlePrice.option_minimum:
price_multiplier = 0.1
elif bundle_price_option == BundlePrice.option_maximum:
price_multiplier = 4
else:
price_multiplier = round(1 + (bundle_price_option.value * 0.4), 2)
currency_amount = int(self.item.amount * price_multiplier)
_, price_multiplier = get_bundle_final_prices(bundle_price_option, self.number_required_items, True)
currency_amount = max(1, int(self.item.amount * price_multiplier))
return currency_amount
def can_appear(self, options: StardewValleyOptions) -> bool:
@ -95,11 +88,11 @@ class CurrencyBundleTemplate(BundleTemplate):
class MoneyBundleTemplate(CurrencyBundleTemplate):
def __init__(self, room: str, item: BundleItem):
super().__init__(room, "", item)
def __init__(self, room: str, default_name: str, item: BundleItem):
super().__init__(room, default_name, item)
def create_bundle(self, bundle_price_option: BundlePrice, random: Random, options: StardewValleyOptions) -> Bundle:
currency_amount = self.get_currency_amount(bundle_price_option)
def create_bundle(self, random: Random, content: StardewContent, options: StardewValleyOptions) -> Bundle:
currency_amount = self.get_currency_amount(options.bundle_price)
currency_name = "g"
if currency_amount >= 1000:
unit_amount = currency_amount % 1000
@ -111,13 +104,8 @@ class MoneyBundleTemplate(CurrencyBundleTemplate):
return Bundle(self.room, name, [BundleItem(self.item.item_name, currency_amount)], 1)
def get_currency_amount(self, bundle_price_option: BundlePrice):
if bundle_price_option == BundlePrice.option_minimum:
price_multiplier = 0.1
elif bundle_price_option == BundlePrice.option_maximum:
price_multiplier = 4
else:
price_multiplier = round(1 + (bundle_price_option.value * 0.4), 2)
currency_amount = int(self.item.amount * price_multiplier)
_, price_multiplier = get_bundle_final_prices(bundle_price_option, self.number_required_items, True)
currency_amount = max(1, int(self.item.amount * price_multiplier))
return currency_amount
@ -134,30 +122,54 @@ class FestivalBundleTemplate(BundleTemplate):
class DeepBundleTemplate(BundleTemplate):
categories: List[List[BundleItem]]
def __init__(self, room: str, name: str, categories: List[List[BundleItem]], number_possible_items: int, number_required_items: int):
def __init__(self, room: str, name: str, categories: List[List[BundleItem]], number_possible_items: int,
number_required_items: int):
super().__init__(room, name, [], number_possible_items, number_required_items)
self.categories = categories
def create_bundle(self, bundle_price_option: BundlePrice, random: Random, options: StardewValleyOptions) -> Bundle:
if bundle_price_option == BundlePrice.option_minimum:
number_required = 1
elif bundle_price_option == BundlePrice.option_maximum:
number_required = 8
else:
number_required = self.number_required_items + bundle_price_option.value
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)
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)
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(options)]
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 * 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)
return number_required_items, price_multiplier
def get_number_required_items(bundle_price_option: BundlePrice, default_required_items: int) -> int:
if bundle_price_option == BundlePrice.option_minimum:
return 1
if bundle_price_option == BundlePrice.option_maximum:
return 8
number_required = default_required_items + bundle_price_option.value
return min(8, max(1, number_required))
def get_price_multiplier(bundle_price_option: BundlePrice, is_currency: bool) -> float:
if bundle_price_option == BundlePrice.option_minimum:
return 0.1 if is_currency else 0.2
if bundle_price_option == BundlePrice.option_maximum:
return 4 if is_currency else 1.4
price_factor = 0.4 if is_currency else (0.2 if bundle_price_option.value <= 0 else 0.1)
price_multiplier_difference = bundle_price_option.value * price_factor
price_multiplier = 1 + price_multiplier_difference
return round(price_multiplier, 2)

View File

@ -3,7 +3,8 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from ..options import StardewValleyOptions, ExcludeGingerIsland, FestivalLocations
from ..content import StardewContent
from ..options import StardewValleyOptions, ExcludeGingerIsland, FestivalLocations, SkillProgression
from ..strings.crop_names import Fruit
from ..strings.currency_names import Currency
from ..strings.quality_names import CropQuality, FishQuality, ForageQuality
@ -30,27 +31,50 @@ class FestivalItemSource(BundleItemSource):
return options.festival_locations != FestivalLocations.option_disabled
class MasteryItemSource(BundleItemSource):
def can_appear(self, options: StardewValleyOptions) -> bool:
return options.skill_progression == SkillProgression.option_progressive_with_masteries
class ContentItemSource(BundleItemSource):
"""This is meant to be used for items that are managed by the content packs."""
def can_appear(self, options: StardewValleyOptions) -> bool:
raise ValueError("This should not be called, check if the item is in the content instead.")
@dataclass(frozen=True, order=True)
class BundleItem:
class Sources:
vanilla = VanillaItemSource()
island = IslandItemSource()
festival = FestivalItemSource()
masteries = MasteryItemSource()
content = ContentItemSource()
item_name: str
amount: int = 1
quality: str = CropQuality.basic
source: BundleItemSource = Sources.vanilla
flavor: str = None
can_have_quality: bool = True
@staticmethod
def money_bundle(amount: int) -> BundleItem:
return BundleItem(Currency.money, amount)
def get_item(self) -> str:
if self.flavor is None:
return self.item_name
return f"{self.item_name} [{self.flavor}]"
def as_amount(self, amount: int) -> BundleItem:
return BundleItem(self.item_name, amount, self.quality, self.source)
return BundleItem(self.item_name, amount, self.quality, self.source, self.flavor)
def as_quality(self, quality: str) -> BundleItem:
return BundleItem(self.item_name, self.amount, quality, self.source)
if self.can_have_quality:
return BundleItem(self.item_name, self.amount, quality, self.source, self.flavor)
return BundleItem(self.item_name, self.amount, self.quality, self.source, self.flavor)
def as_quality_crop(self) -> BundleItem:
amount = 5
@ -67,7 +91,11 @@ class BundleItem:
def __repr__(self):
quality = "" if self.quality == CropQuality.basic else self.quality
return f"{self.amount} {quality} {self.item_name}"
return f"{self.amount} {quality} {self.get_item()}"
def can_appear(self, content: StardewContent, options: StardewValleyOptions) -> bool:
if isinstance(self.source, ContentItemSource):
return self.get_item() in content.game_items
def can_appear(self, options: StardewValleyOptions) -> bool:
return self.source.can_appear(options)

View File

@ -3,6 +3,7 @@ from random import Random
from typing import List
from .bundle import Bundle, BundleTemplate
from ..content import StardewContent
from ..options import BundlePrice, StardewValleyOptions
@ -18,7 +19,25 @@ class BundleRoomTemplate:
bundles: List[BundleTemplate]
number_bundles: int
def create_bundle_room(self, bundle_price_option: BundlePrice, random: Random, options: StardewValleyOptions):
def create_bundle_room(self, random: Random, content: StardewContent, options: StardewValleyOptions):
filtered_bundles = [bundle for bundle in self.bundles if bundle.can_appear(options)]
chosen_bundles = random.sample(filtered_bundles, self.number_bundles)
return BundleRoom(self.name, [bundle.create_bundle(bundle_price_option, random, options) for bundle in chosen_bundles])
priority_bundles = []
unpriority_bundles = []
for bundle in filtered_bundles:
if bundle.name in options.bundle_plando:
priority_bundles.append(bundle)
else:
unpriority_bundles.append(bundle)
if self.number_bundles <= len(priority_bundles):
chosen_bundles = random.sample(priority_bundles, self.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.extend(random.sample(unpriority_bundles, num_remaining_bundles))
return BundleRoom(self.name, [bundle.create_bundle(random, content, options) for bundle in chosen_bundles])

View File

@ -1,65 +1,102 @@
from random import Random
from typing import List
from typing import List, Tuple
from .bundle_room import BundleRoom
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
abandoned_joja_mart_thematic, abandoned_joja_mart_vanilla, abandoned_joja_mart_remixed, raccoon_vanilla, raccoon_thematic, raccoon_remixed, \
community_center_remixed_anywhere
from ..logic.logic import StardewLogic
from ..options import BundleRandomization, StardewValleyOptions, ExcludeGingerIsland
from ..options import BundleRandomization, StardewValleyOptions
def get_all_bundles(random: Random, logic: StardewLogic, options: StardewValleyOptions) -> List[BundleRoom]:
def get_all_bundles(random: Random, logic: StardewLogic, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]:
if options.bundle_randomization == BundleRandomization.option_vanilla:
return get_vanilla_bundles(random, options)
return get_vanilla_bundles(random, content, options)
elif options.bundle_randomization == BundleRandomization.option_thematic:
return get_thematic_bundles(random, options)
return get_thematic_bundles(random, content, options)
elif options.bundle_randomization == BundleRandomization.option_remixed:
return get_remixed_bundles(random, options)
return get_remixed_bundles(random, content, options)
elif options.bundle_randomization == BundleRandomization.option_remixed_anywhere:
return get_remixed_bundles_anywhere(random, content, options)
elif options.bundle_randomization == BundleRandomization.option_shuffled:
return get_shuffled_bundles(random, logic, options)
return get_shuffled_bundles(random, logic, content, options)
raise NotImplementedError
def get_vanilla_bundles(random: Random, options: StardewValleyOptions) -> List[BundleRoom]:
pantry = pantry_vanilla.create_bundle_room(options.bundle_price, random, options)
crafts_room = crafts_room_vanilla.create_bundle_room(options.bundle_price, random, options)
fish_tank = fish_tank_vanilla.create_bundle_room(options.bundle_price, random, options)
boiler_room = boiler_room_vanilla.create_bundle_room(options.bundle_price, random, options)
bulletin_board = bulletin_board_vanilla.create_bundle_room(options.bundle_price, random, options)
vault = vault_vanilla.create_bundle_room(options.bundle_price, random, options)
abandoned_joja_mart = abandoned_joja_mart_vanilla.create_bundle_room(options.bundle_price, random, options)
return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart]
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]
def get_thematic_bundles(random: Random, options: StardewValleyOptions) -> List[BundleRoom]:
pantry = pantry_thematic.create_bundle_room(options.bundle_price, random, options)
crafts_room = crafts_room_thematic.create_bundle_room(options.bundle_price, random, options)
fish_tank = fish_tank_thematic.create_bundle_room(options.bundle_price, random, options)
boiler_room = boiler_room_thematic.create_bundle_room(options.bundle_price, random, options)
bulletin_board = bulletin_board_thematic.create_bundle_room(options.bundle_price, random, options)
vault = vault_thematic.create_bundle_room(options.bundle_price, random, options)
abandoned_joja_mart = abandoned_joja_mart_thematic.create_bundle_room(options.bundle_price, random, options)
return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart]
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]
def get_remixed_bundles(random: Random, options: StardewValleyOptions) -> List[BundleRoom]:
pantry = pantry_remixed.create_bundle_room(options.bundle_price, random, options)
crafts_room = crafts_room_remixed.create_bundle_room(options.bundle_price, random, options)
fish_tank = fish_tank_remixed.create_bundle_room(options.bundle_price, random, options)
boiler_room = boiler_room_remixed.create_bundle_room(options.bundle_price, random, options)
bulletin_board = bulletin_board_remixed.create_bundle_room(options.bundle_price, random, options)
vault = vault_remixed.create_bundle_room(options.bundle_price, random, options)
abandoned_joja_mart = abandoned_joja_mart_remixed.create_bundle_room(options.bundle_price, random, options)
return [pantry, crafts_room, fish_tank, boiler_room, bulletin_board, vault, abandoned_joja_mart]
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)
abandoned_joja_mart = abandoned_joja_mart_remixed.create_bundle_room(random, content, options)
raccoon = raccoon_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_shuffled_bundles(random: Random, logic: StardewLogic, options: StardewValleyOptions) -> List[BundleRoom]:
valid_bundle_items = [bundle_item for bundle_item in all_bundle_items_except_money if bundle_item.can_appear(options)]
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)
all_chosen_bundles = big_room.bundles
random.shuffle(all_chosen_bundles)
rooms = [room for room in get_remixed_bundles(random, options) if room.name != "Vault"]
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)
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)
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]:
start_index = end_index
end_index += template.number_bundles
return BundleRoom(template.name, all_bundles[start_index:end_index]), end_index
def get_shuffled_bundles(random: Random, logic: StardewLogic, content: StardewContent, options: StardewValleyOptions) -> List[BundleRoom]:
valid_bundle_items = [bundle_item for bundle_item in all_bundle_items_except_money if bundle_item.can_appear(content, options)]
rooms = [room for room in get_remixed_bundles(random, content, options) if room.name != "Vault"]
required_items = 0
for room in rooms:
for bundle in room.bundles:
@ -67,14 +104,21 @@ def get_shuffled_bundles(random: Random, logic: StardewLogic, options: StardewVa
random.shuffle(room.bundles)
random.shuffle(rooms)
# Remove duplicates of the same item
valid_bundle_items = [item1 for i, item1 in enumerate(valid_bundle_items)
if not any(item1.item_name == item2.item_name and item1.quality == item2.quality for item2 in valid_bundle_items[:i])]
chosen_bundle_items = random.sample(valid_bundle_items, required_items)
sorted_bundle_items = sorted(chosen_bundle_items, key=lambda x: logic.has(x.item_name).get_difficulty())
for room in rooms:
for bundle in room.bundles:
num_items = len(bundle.items)
bundle.items = sorted_bundle_items[:num_items]
sorted_bundle_items = sorted_bundle_items[num_items:]
bundle.items = chosen_bundle_items[:num_items]
chosen_bundle_items = chosen_bundle_items[num_items:]
vault = vault_remixed.create_bundle_room(options.bundle_price, random, options)
vault = vault_remixed.create_bundle_room(random, content, options)
return [*rooms, vault]
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}"

View File

@ -0,0 +1,107 @@
from . import content_packs
from .feature import cropsanity, friendsanity, fishsanity, booksanity
from .game_content import ContentPack, StardewContent, StardewFeatures
from .unpacking import unpack_content
from .. import options
def create_content(player_options: options.StardewValleyOptions) -> StardewContent:
active_packs = choose_content_packs(player_options)
features = choose_features(player_options)
return unpack_content(features, active_packs)
def choose_content_packs(player_options: options.StardewValleyOptions):
active_packs = [content_packs.pelican_town, content_packs.the_desert, content_packs.the_farm, content_packs.the_mines]
if player_options.exclude_ginger_island == options.ExcludeGingerIsland.option_false:
active_packs.append(content_packs.ginger_island_content_pack)
if player_options.special_order_locations & options.SpecialOrderLocations.value_qi:
active_packs.append(content_packs.qi_board_content_pack)
for mod in player_options.mods.value:
active_packs.append(content_packs.by_mod[mod])
return active_packs
def choose_features(player_options: options.StardewValleyOptions) -> StardewFeatures:
return StardewFeatures(
choose_booksanity(player_options.booksanity),
choose_cropsanity(player_options.cropsanity),
choose_fishsanity(player_options.fishsanity),
choose_friendsanity(player_options.friendsanity, player_options.friendsanity_heart_size)
)
booksanity_by_option = {
options.Booksanity.option_none: booksanity.BooksanityDisabled(),
options.Booksanity.option_power: booksanity.BooksanityPower(),
options.Booksanity.option_power_skill: booksanity.BooksanityPowerSkill(),
options.Booksanity.option_all: booksanity.BooksanityAll(),
}
def choose_booksanity(booksanity_option: options.Booksanity) -> booksanity.BooksanityFeature:
booksanity_feature = booksanity_by_option.get(booksanity_option)
if booksanity_feature is None:
raise ValueError(f"No booksanity feature mapped to {str(booksanity_option.value)}")
return booksanity_feature
cropsanity_by_option = {
options.Cropsanity.option_disabled: cropsanity.CropsanityDisabled(),
options.Cropsanity.option_enabled: cropsanity.CropsanityEnabled(),
}
def choose_cropsanity(cropsanity_option: options.Cropsanity) -> cropsanity.CropsanityFeature:
cropsanity_feature = cropsanity_by_option.get(cropsanity_option)
if cropsanity_feature is None:
raise ValueError(f"No cropsanity feature mapped to {str(cropsanity_option.value)}")
return cropsanity_feature
fishsanity_by_option = {
options.Fishsanity.option_none: fishsanity.FishsanityNone(),
options.Fishsanity.option_legendaries: fishsanity.FishsanityLegendaries(),
options.Fishsanity.option_special: fishsanity.FishsanitySpecial(),
options.Fishsanity.option_randomized: fishsanity.FishsanityAll(randomization_ratio=0.4),
options.Fishsanity.option_all: fishsanity.FishsanityAll(),
options.Fishsanity.option_exclude_legendaries: fishsanity.FishsanityExcludeLegendaries(),
options.Fishsanity.option_exclude_hard_fish: fishsanity.FishsanityExcludeHardFish(),
options.Fishsanity.option_only_easy_fish: fishsanity.FishsanityOnlyEasyFish(),
}
def choose_fishsanity(fishsanity_option: options.Fishsanity) -> fishsanity.FishsanityFeature:
fishsanity_feature = fishsanity_by_option.get(fishsanity_option)
if fishsanity_feature is None:
raise ValueError(f"No fishsanity feature mapped to {str(fishsanity_option.value)}")
return fishsanity_feature
def choose_friendsanity(friendsanity_option: options.Friendsanity, heart_size: options.FriendsanityHeartSize) -> friendsanity.FriendsanityFeature:
if friendsanity_option == options.Friendsanity.option_none:
return friendsanity.FriendsanityNone()
if friendsanity_option == options.Friendsanity.option_bachelors:
return friendsanity.FriendsanityBachelors(heart_size.value)
if friendsanity_option == options.Friendsanity.option_starting_npcs:
return friendsanity.FriendsanityStartingNpc(heart_size.value)
if friendsanity_option == options.Friendsanity.option_all:
return friendsanity.FriendsanityAll(heart_size.value)
if friendsanity_option == options.Friendsanity.option_all_with_marriage:
return friendsanity.FriendsanityAllWithMarriage(heart_size.value)
raise ValueError(f"No friendsanity feature mapped to {str(friendsanity_option.value)}")

View File

@ -0,0 +1,31 @@
import importlib
import pkgutil
from . import mods
from .mod_registry import by_mod
from .vanilla.base import base_game
from .vanilla.ginger_island import ginger_island_content_pack
from .vanilla.pelican_town import pelican_town
from .vanilla.qi_board import qi_board_content_pack
from .vanilla.the_desert import the_desert
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__)
loaded_modules = {}
for mod_module in mod_modules:
module_name = mod_module.name
module = importlib.import_module("." + module_name, mods.__name__)
loaded_modules[module_name] = module
assert by_mod

View File

@ -0,0 +1,4 @@
from . import booksanity
from . import cropsanity
from . import fishsanity
from . import friendsanity

View File

@ -0,0 +1,72 @@
from abc import ABC, abstractmethod
from typing import ClassVar, Optional, Iterable
from ...data.game_item import GameItem, ItemTag
from ...strings.book_names import ordered_lost_books
item_prefix = "Power: "
location_prefix = "Read "
def to_item_name(book: str) -> str:
return item_prefix + book
def to_location_name(book: str) -> str:
return location_prefix + book
def extract_book_from_location_name(location_name: str) -> Optional[str]:
if not location_name.startswith(location_prefix):
return None
return location_name[len(location_prefix):]
class BooksanityFeature(ABC):
is_enabled: ClassVar[bool]
to_item_name = staticmethod(to_item_name)
progressive_lost_book = "Progressive Lost Book"
to_location_name = staticmethod(to_location_name)
extract_book_from_location_name = staticmethod(extract_book_from_location_name)
@abstractmethod
def is_included(self, book: GameItem) -> bool:
...
@staticmethod
def get_randomized_lost_books() -> Iterable[str]:
return []
class BooksanityDisabled(BooksanityFeature):
is_enabled = False
def is_included(self, book: GameItem) -> bool:
return False
class BooksanityPower(BooksanityFeature):
is_enabled = True
def is_included(self, book: GameItem) -> bool:
return ItemTag.BOOK_POWER in book.tags
class BooksanityPowerSkill(BooksanityFeature):
is_enabled = True
def is_included(self, book: GameItem) -> bool:
return ItemTag.BOOK_POWER in book.tags or ItemTag.BOOK_SKILL in book.tags
class BooksanityAll(BooksanityFeature):
is_enabled = True
def is_included(self, book: GameItem) -> bool:
return ItemTag.BOOK_POWER in book.tags or ItemTag.BOOK_SKILL in book.tags
@staticmethod
def get_randomized_lost_books() -> Iterable[str]:
return ordered_lost_books

View File

@ -0,0 +1,42 @@
from abc import ABC, abstractmethod
from typing import ClassVar, Optional
from ...data.game_item import GameItem, ItemTag
location_prefix = "Harvest "
def to_location_name(crop: str) -> str:
return location_prefix + crop
def extract_crop_from_location_name(location_name: str) -> Optional[str]:
if not location_name.startswith(location_prefix):
return None
return location_name[len(location_prefix):]
class CropsanityFeature(ABC):
is_enabled: ClassVar[bool]
to_location_name = staticmethod(to_location_name)
extract_crop_from_location_name = staticmethod(extract_crop_from_location_name)
@abstractmethod
def is_included(self, crop: GameItem) -> bool:
...
class CropsanityDisabled(CropsanityFeature):
is_enabled = False
def is_included(self, crop: GameItem) -> bool:
return False
class CropsanityEnabled(CropsanityFeature):
is_enabled = True
def is_included(self, crop: GameItem) -> bool:
return ItemTag.CROPSANITY_SEED in crop.tags

View File

@ -0,0 +1,101 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import ClassVar, Optional
from ...data.fish_data import FishItem
from ...strings.fish_names import Fish
location_prefix = "Fishsanity: "
def to_location_name(fish: str) -> str:
return location_prefix + fish
def extract_fish_from_location_name(location_name: str) -> Optional[str]:
if not location_name.startswith(location_prefix):
return None
return location_name[len(location_prefix):]
@dataclass(frozen=True)
class FishsanityFeature(ABC):
is_enabled: ClassVar[bool]
randomization_ratio: float = 1
to_location_name = staticmethod(to_location_name)
extract_fish_from_location_name = staticmethod(extract_fish_from_location_name)
@property
def is_randomized(self) -> bool:
return self.randomization_ratio != 1
@abstractmethod
def is_included(self, fish: FishItem) -> bool:
...
class FishsanityNone(FishsanityFeature):
is_enabled = False
def is_included(self, fish: FishItem) -> bool:
return False
class FishsanityLegendaries(FishsanityFeature):
is_enabled = True
def is_included(self, fish: FishItem) -> bool:
return fish.legendary
class FishsanitySpecial(FishsanityFeature):
is_enabled = True
included_fishes = {
Fish.angler,
Fish.crimsonfish,
Fish.glacierfish,
Fish.legend,
Fish.mutant_carp,
Fish.blobfish,
Fish.lava_eel,
Fish.octopus,
Fish.scorpion_carp,
Fish.ice_pip,
Fish.super_cucumber,
Fish.dorado
}
def is_included(self, fish: FishItem) -> bool:
return fish.name in self.included_fishes
class FishsanityAll(FishsanityFeature):
is_enabled = True
def is_included(self, fish: FishItem) -> bool:
return True
class FishsanityExcludeLegendaries(FishsanityFeature):
is_enabled = True
def is_included(self, fish: FishItem) -> bool:
return not fish.legendary
class FishsanityExcludeHardFish(FishsanityFeature):
is_enabled = True
def is_included(self, fish: FishItem) -> bool:
return fish.difficulty < 80
class FishsanityOnlyEasyFish(FishsanityFeature):
is_enabled = True
def is_included(self, fish: FishItem) -> bool:
return fish.difficulty < 50

View File

@ -0,0 +1,139 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from functools import lru_cache
from typing import Optional, Tuple, ClassVar
from ...data.villagers_data import Villager
from ...strings.villager_names import NPC
suffix = " <3"
location_prefix = "Friendsanity: "
def to_item_name(npc_name: str) -> str:
return npc_name + suffix
def to_location_name(npc_name: str, heart: int) -> str:
return location_prefix + npc_name + " " + str(heart) + suffix
pet_heart_item_name = to_item_name(NPC.pet)
def extract_npc_from_item_name(item_name: str) -> Optional[str]:
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]:
if not location_name.endswith(suffix):
return None, 0
trimmed = location_name[len(location_prefix):-len(suffix)]
last_space = trimmed.rindex(" ")
return trimmed[:last_space], int(trimmed[last_space + 1:])
@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, ...]:
return tuple(range(heart_size, max_heart + 1, heart_size)) + ((max_heart,) if max_heart % heart_size else ())
@dataclass(frozen=True)
class FriendsanityFeature(ABC):
is_enabled: ClassVar[bool]
heart_size: int
to_item_name = staticmethod(to_item_name)
to_location_name = staticmethod(to_location_name)
pet_heart_item_name = pet_heart_item_name
extract_npc_from_item_name = staticmethod(extract_npc_from_item_name)
extract_npc_from_location_name = staticmethod(extract_npc_from_location_name)
@abstractmethod
def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
...
@property
def is_pet_randomized(self):
return bool(self.get_pet_randomized_hearts())
@abstractmethod
def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
...
class FriendsanityNone(FriendsanityFeature):
is_enabled = False
def __init__(self):
super().__init__(1)
def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
return ()
def get_pet_randomized_hearts(self) -> Tuple[int, ...]:
return ()
@dataclass(frozen=True)
class FriendsanityBachelors(FriendsanityFeature):
is_enabled = True
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, ...]:
return ()
@dataclass(frozen=True)
class FriendsanityStartingNpc(FriendsanityFeature):
is_enabled = True
def get_randomized_hearts(self, villager: Villager) -> Tuple[int, ...]:
if not villager.available:
return ()
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, ...]:
return get_heart_steps(5, self.heart_size)
@dataclass(frozen=True)
class FriendsanityAll(FriendsanityFeature):
is_enabled = True
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, ...]:
return get_heart_steps(5, self.heart_size)
@dataclass(frozen=True)
class FriendsanityAllWithMarriage(FriendsanityFeature):
is_enabled = True
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, ...]:
return get_heart_steps(5, self.heart_size)

View File

@ -0,0 +1,117 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, Iterable, Set, Any, Mapping, Type, Tuple, Union
from .feature import booksanity, cropsanity, fishsanity, friendsanity
from ..data.fish_data import FishItem
from ..data.game_item import GameItem, ItemSource, ItemTag
from ..data.skill import Skill
from ..data.villagers_data import Villager
@dataclass(frozen=True)
class StardewContent:
features: StardewFeatures
registered_packs: Set[str] = field(default_factory=set)
# regions -> To be used with can reach rule
game_items: Dict[str, GameItem] = field(default_factory=dict)
fishes: Dict[str, FishItem] = field(default_factory=dict)
villagers: Dict[str, Villager] = field(default_factory=dict)
skills: Dict[str, Skill] = field(default_factory=dict)
quests: Dict[str, Any] = field(default_factory=dict)
def find_sources_of_type(self, types: Union[Type[ItemSource], Tuple[Type[ItemSource]]]) -> Iterable[ItemSource]:
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: ItemSource):
item = self.game_items.setdefault(item_name, GameItem(item_name))
item.add_sources(sources)
def tag_item(self, item_name: str, *tags: ItemTag):
item = self.game_items.setdefault(item_name, GameItem(item_name))
item.add_tags(tags)
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]:
# 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
@dataclass(frozen=True)
class StardewFeatures:
booksanity: booksanity.BooksanityFeature
cropsanity: cropsanity.CropsanityFeature
fishsanity: fishsanity.FishsanityFeature
friendsanity: friendsanity.FriendsanityFeature
@dataclass(frozen=True)
class ContentPack:
name: str
dependencies: Iterable[str] = ()
""" Hard requirement, generation will fail if it's missing. """
weak_dependencies: Iterable[str] = ()
""" Not a strict dependency, only used only for ordering the packs to make sure hooks are applied correctly. """
# items
# def item_hook
# ...
harvest_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict)
"""Harvest sources contains both crops and forageables, but also fruits from trees, the cave farm and stuff harvested from tapping like maple syrup."""
def harvest_source_hook(self, content: StardewContent):
...
shop_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict)
def shop_source_hook(self, content: StardewContent):
...
fishes: Iterable[FishItem] = ()
def fish_hook(self, content: StardewContent):
...
crafting_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict)
def crafting_hook(self, content: StardewContent):
...
artisan_good_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict)
def artisan_good_hook(self, content: StardewContent):
...
villagers: Iterable[Villager] = ()
def villager_hook(self, content: StardewContent):
...
skills: Iterable[Skill] = ()
def skill_hook(self, content: StardewContent):
...
quests: Iterable[Any] = ()
def quest_hook(self, content: StardewContent):
...
def finalize_hook(self, content: StardewContent):
"""Last hook called on the pack, once all other content packs have been registered.
This is the place to do any final adjustments to the content, like adding rules based on tags applied by other packs.
"""
...

View File

@ -0,0 +1,7 @@
from .game_content import ContentPack
by_mod = {}
def register_mod_content_pack(content_pack: ContentPack):
by_mod[content_pack.name] = content_pack

View File

@ -0,0 +1,20 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data.game_item import ItemTag, Tag
from ...data.shop import ShopSource
from ...data.skill import Skill
from ...mods.mod_data import ModNames
from ...strings.book_names import ModBook
from ...strings.region_names import LogicRegion
from ...strings.skill_names import ModSkill
register_mod_content_pack(ContentPack(
ModNames.archaeology,
shop_sources={
ModBook.digging_like_worms: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=500, shop_region=LogicRegion.bookseller_1),),
},
skills=(Skill(name=ModSkill.archaeology, has_mastery=False),),
))

View File

@ -0,0 +1,7 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.big_backpack,
))

View File

@ -0,0 +1,13 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data import villagers_data
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.boarding_house,
villagers=(
villagers_data.gregory,
villagers_data.sheila,
villagers_data.joel,
)
))

View File

@ -0,0 +1,28 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data.harvest import ForagingSource
from ...mods.mod_data import ModNames
from ...strings.crop_names import Fruit
from ...strings.flower_names import Flower
from ...strings.region_names import DeepWoodsRegion
from ...strings.season_names import Season
register_mod_content_pack(ContentPack(
ModNames.deepwoods,
harvest_sources={
# Deep enough to have seen such a tree at least once
Fruit.apple: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Fruit.apricot: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Fruit.cherry: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Fruit.orange: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Fruit.peach: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Fruit.pomegranate: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Fruit.mango: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Flower.tulip: (ForagingSource(seasons=Season.not_winter, regions=(DeepWoodsRegion.floor_10,)),),
Flower.blue_jazz: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
Flower.summer_spangle: (ForagingSource(seasons=Season.not_winter, regions=(DeepWoodsRegion.floor_10,)),),
Flower.poppy: (ForagingSource(seasons=Season.not_winter, regions=(DeepWoodsRegion.floor_10,)),),
Flower.fairy_rose: (ForagingSource(regions=(DeepWoodsRegion.floor_10,)),),
}
))

View File

@ -0,0 +1,17 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data import villagers_data, fish_data
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.distant_lands,
fishes=(
fish_data.void_minnow,
fish_data.purple_algae,
fish_data.swamp_leech,
fish_data.giant_horsehoe_crab,
),
villagers=(
villagers_data.zic,
)
))

View File

@ -0,0 +1,14 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ..override import override
from ...data import villagers_data
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.jasper,
villagers=(
villagers_data.jasper,
override(villagers_data.gunther, mod_name=ModNames.jasper),
override(villagers_data.marlon, mod_name=ModNames.jasper),
)
))

View File

@ -0,0 +1,10 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data.skill import Skill
from ...mods.mod_data import ModNames
from ...strings.skill_names import ModSkill
register_mod_content_pack(ContentPack(
ModNames.magic,
skills=(Skill(name=ModSkill.magic, has_mastery=False),)
))

View File

@ -0,0 +1,88 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data import villagers_data
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.alec,
villagers=(
villagers_data.alec,
)
))
register_mod_content_pack(ContentPack(
ModNames.ayeisha,
villagers=(
villagers_data.ayeisha,
)
))
register_mod_content_pack(ContentPack(
ModNames.delores,
villagers=(
villagers_data.delores,
)
))
register_mod_content_pack(ContentPack(
ModNames.eugene,
villagers=(
villagers_data.eugene,
)
))
register_mod_content_pack(ContentPack(
ModNames.juna,
villagers=(
villagers_data.juna,
)
))
register_mod_content_pack(ContentPack(
ModNames.ginger,
villagers=(
villagers_data.kitty,
)
))
register_mod_content_pack(ContentPack(
ModNames.shiko,
villagers=(
villagers_data.shiko,
)
))
register_mod_content_pack(ContentPack(
ModNames.wellwick,
villagers=(
villagers_data.wellwick,
)
))
register_mod_content_pack(ContentPack(
ModNames.yoba,
villagers=(
villagers_data.yoba,
)
))
register_mod_content_pack(ContentPack(
ModNames.riley,
villagers=(
villagers_data.riley,
)
))
register_mod_content_pack(ContentPack(
ModNames.alecto,
villagers=(
villagers_data.alecto,
)
))
register_mod_content_pack(ContentPack(
ModNames.lacey,
villagers=(
villagers_data.lacey,
)
))

View File

@ -0,0 +1,25 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...data.skill import Skill
from ...mods.mod_data import ModNames
from ...strings.skill_names import ModSkill
register_mod_content_pack(ContentPack(
ModNames.luck_skill,
skills=(Skill(name=ModSkill.luck, has_mastery=False),)
))
register_mod_content_pack(ContentPack(
ModNames.socializing_skill,
skills=(Skill(name=ModSkill.socializing, has_mastery=False),)
))
register_mod_content_pack(ContentPack(
ModNames.cooking_skill,
skills=(Skill(name=ModSkill.cooking, has_mastery=False),)
))
register_mod_content_pack(ContentPack(
ModNames.binning_skill,
skills=(Skill(name=ModSkill.binning, has_mastery=False),)
))

View File

@ -0,0 +1,7 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.skull_cavern_elevator,
))

View File

@ -0,0 +1,126 @@
from ..game_content import ContentPack, StardewContent
from ..mod_registry import register_mod_content_pack
from ..override import override
from ..vanilla.ginger_island import ginger_island_content_pack as ginger_island_content_pack
from ...data import villagers_data, fish_data
from ...data.harvest import ForagingSource
from ...data.requirement import YearRequirement
from ...mods.mod_data import ModNames
from ...strings.crop_names import Fruit
from ...strings.fish_names import WaterItem
from ...strings.flower_names import Flower
from ...strings.forageable_names import Mushroom, Forageable
from ...strings.region_names import Region, SVERegion
from ...strings.season_names import Season
class SVEContentPack(ContentPack):
def fish_hook(self, content: StardewContent):
if ginger_island_content_pack.name not in content.registered_packs:
content.fishes.pop(fish_data.baby_lunaloo.name)
content.fishes.pop(fish_data.clownfish.name)
content.fishes.pop(fish_data.lunaloo.name)
content.fishes.pop(fish_data.seahorse.name)
content.fishes.pop(fish_data.shiny_lunaloo.name)
content.fishes.pop(fish_data.starfish.name)
content.fishes.pop(fish_data.sea_sponge.name)
# Remove Highlands fishes at it requires 2 Lance hearts for the quest to access it
content.fishes.pop(fish_data.daggerfish.name)
content.fishes.pop(fish_data.gemfish.name)
# Remove Fable Reef fishes at it requires 8 Lance hearts for the event to access it
content.fishes.pop(fish_data.torpedo_trout.name)
def villager_hook(self, content: StardewContent):
if ginger_island_content_pack.name not in content.registered_packs:
# Remove Lance if Ginger Island is not in content since he is first encountered in Volcano Forge
content.villagers.pop(villagers_data.lance.name)
register_mod_content_pack(SVEContentPack(
ModNames.sve,
weak_dependencies=(
ginger_island_content_pack.name,
ModNames.jasper, # To override Marlon and Gunther
),
harvest_sources={
Mushroom.red: (
ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.summer, Season.fall)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), )
),
Mushroom.purple: (
ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), )
),
Mushroom.morel: (
ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), )
),
Mushroom.chanterelle: (
ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), )
),
Flower.tulip: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring,)),),
Flower.blue_jazz: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring,)),),
Flower.summer_spangle: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.summer,)),),
Flower.sunflower: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.summer,)),),
Flower.fairy_rose: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.fall,)),),
Fruit.ancient_fruit: (
ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring, Season.summer, Season.fall), other_requirements=(YearRequirement(3),)),
ForagingSource(regions=(SVERegion.sprite_spring_cave,)),
),
Fruit.sweet_gem_berry: (
ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring, Season.summer, Season.fall), other_requirements=(YearRequirement(3),)),
),
# Fable Reef
WaterItem.coral: (ForagingSource(regions=(SVERegion.fable_reef,)),),
Forageable.rainbow_shell: (ForagingSource(regions=(SVERegion.fable_reef,)),),
WaterItem.sea_urchin: (ForagingSource(regions=(SVERegion.fable_reef,)),),
},
fishes=(
fish_data.baby_lunaloo, # Removed when no ginger island
fish_data.bonefish,
fish_data.bull_trout,
fish_data.butterfish,
fish_data.clownfish, # Removed when no ginger island
fish_data.daggerfish,
fish_data.frog,
fish_data.gemfish,
fish_data.goldenfish,
fish_data.grass_carp,
fish_data.king_salmon,
fish_data.kittyfish,
fish_data.lunaloo, # Removed when no ginger island
fish_data.meteor_carp,
fish_data.minnow,
fish_data.puppyfish,
fish_data.radioactive_bass,
fish_data.seahorse, # Removed when no ginger island
fish_data.shiny_lunaloo, # Removed when no ginger island
fish_data.snatcher_worm,
fish_data.starfish, # Removed when no ginger island
fish_data.torpedo_trout,
fish_data.undeadfish,
fish_data.void_eel,
fish_data.water_grub,
fish_data.sea_sponge, # Removed when no ginger island
),
villagers=(
villagers_data.claire,
villagers_data.lance, # Removed when no ginger island
villagers_data.mommy,
villagers_data.sophia,
villagers_data.victor,
villagers_data.andy,
villagers_data.apples,
villagers_data.gunther,
villagers_data.martin,
villagers_data.marlon,
villagers_data.morgan,
villagers_data.scarlett,
villagers_data.susan,
villagers_data.morris,
# The wizard leaves his tower on sunday, for like 1 hour... Good enough for entrance rando!
override(villagers_data.wizard, locations=(Region.wizard_tower, Region.forest), bachelor=True, mod_name=ModNames.sve),
)
))

View File

@ -0,0 +1,7 @@
from ..game_content import ContentPack
from ..mod_registry import register_mod_content_pack
from ...mods.mod_data import ModNames
register_mod_content_pack(ContentPack(
ModNames.tractor,
))

View File

@ -0,0 +1,7 @@
from typing import Any
def override(content: Any, **kwargs) -> Any:
attributes = dict(content.__dict__)
attributes.update(kwargs)
return type(content)(**attributes)

View File

@ -0,0 +1,97 @@
from __future__ import annotations
from typing import Iterable, Mapping, Callable
from .game_content import StardewContent, ContentPack, StardewFeatures
from .vanilla.base import base_game as base_game_content_pack
from ..data.game_item import GameItem, ItemSource
try:
from graphlib import TopologicalSorter
except ImportError:
from graphlib_backport import TopologicalSorter # noqa
def unpack_content(features: StardewFeatures, packs: Iterable[ContentPack]) -> StardewContent:
# Base game is always registered first.
content = StardewContent(features)
packs_to_finalize = [base_game_content_pack]
register_pack(content, base_game_content_pack)
# Content packs are added in order based on their dependencies
sorter = TopologicalSorter()
packs_by_name = {p.name: p for p in packs}
# Build the dependency graph
for name, pack in packs_by_name.items():
sorter.add(name,
*pack.dependencies,
*(wd for wd in pack.weak_dependencies if wd in packs_by_name))
# Graph is traversed in BFS
sorter.prepare()
while sorter.is_active():
# Packs get shuffled in TopologicalSorter, most likely due to hash seeding.
for pack_name in sorted(sorter.get_ready()):
pack = packs_by_name[pack_name]
register_pack(content, pack)
sorter.done(pack_name)
packs_to_finalize.append(pack)
prune_inaccessible_items(content)
for pack in packs_to_finalize:
pack.finalize_hook(content)
# Maybe items without source should be removed at some point
return content
def register_pack(content: StardewContent, pack: ContentPack):
# register regions
# register entrances
register_sources_and_call_hook(content, pack.harvest_sources, pack.harvest_source_hook)
register_sources_and_call_hook(content, pack.shop_sources, pack.shop_source_hook)
register_sources_and_call_hook(content, pack.crafting_sources, pack.crafting_hook)
register_sources_and_call_hook(content, pack.artisan_good_sources, pack.artisan_good_hook)
for fish in pack.fishes:
content.fishes[fish.name] = fish
pack.fish_hook(content)
for villager in pack.villagers:
content.villagers[villager.name] = villager
pack.villager_hook(content)
for skill in pack.skills:
content.skills[skill.name] = skill
pack.skill_hook(content)
# register_quests
# ...
content.registered_packs.add(pack.name)
def register_sources_and_call_hook(content: StardewContent,
sources_by_item_name: Mapping[str, Iterable[ItemSource]],
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)
hook(content)
def prune_inaccessible_items(content: StardewContent):
for item in list(content.game_items.values()):
if not item.sources:
content.game_items.pop(item.name)

View File

@ -0,0 +1,172 @@
from ..game_content import ContentPack, StardewContent
from ...data.artisan import MachineSource
from ...data.game_item import ItemTag, CustomRuleSource, GameItem
from ...data.harvest import HarvestFruitTreeSource, HarvestCropSource
from ...data.skill import Skill
from ...strings.artisan_good_names import ArtisanGood
from ...strings.craftable_names import WildSeeds
from ...strings.crop_names import Fruit, Vegetable
from ...strings.flower_names import Flower
from ...strings.food_names import Beverage
from ...strings.forageable_names import all_edible_mushrooms, Mushroom, Forageable
from ...strings.fruit_tree_names import Sapling
from ...strings.machine_names import Machine
from ...strings.monster_names import Monster
from ...strings.season_names import Season
from ...strings.seed_names import Seed
from ...strings.skill_names import Skill as SkillName
all_fruits = (
Fruit.ancient_fruit, Fruit.apple, Fruit.apricot, Fruit.banana, Forageable.blackberry, Fruit.blueberry, Forageable.cactus_fruit, Fruit.cherry,
Forageable.coconut, Fruit.cranberries, Forageable.crystal_fruit, Fruit.grape, Fruit.hot_pepper, Fruit.mango, Fruit.melon, Fruit.orange, Fruit.peach,
Fruit.pineapple, Fruit.pomegranate, Fruit.powdermelon, Fruit.qi_fruit, Fruit.rhubarb, Forageable.salmonberry, Forageable.spice_berry, Fruit.starfruit,
Fruit.strawberry
)
all_vegetables = (
Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.bok_choy, Vegetable.broccoli, Vegetable.carrot, Vegetable.cauliflower,
Vegetable.corn, Vegetable.eggplant, Forageable.fiddlehead_fern, Vegetable.garlic, Vegetable.green_bean, Vegetable.hops, Vegetable.kale,
Vegetable.parsnip, Vegetable.potato, Vegetable.pumpkin, Vegetable.radish, Vegetable.red_cabbage, Vegetable.summer_squash, Vegetable.taro_root,
Vegetable.tea_leaves, Vegetable.tomato, Vegetable.unmilled_rice, Vegetable.wheat, Vegetable.yam
)
non_juiceable_vegetables = (Vegetable.hops, Vegetable.tea_leaves, Vegetable.wheat, Vegetable.tea_leaves)
# This will hold items, skills and stuff that is available everywhere across the game, but not directly needing pelican town (crops, ore, foraging, etc.)
class BaseGameContentPack(ContentPack):
def harvest_source_hook(self, content: StardewContent):
coffee_starter = content.game_items[Seed.coffee_starter]
content.game_items[Seed.coffee_starter] = GameItem(Seed.coffee, sources=coffee_starter.sources, tags=coffee_starter.tags)
content.untag_item(WildSeeds.ancient, ItemTag.CROPSANITY_SEED)
for fruit in all_fruits:
content.tag_item(fruit, ItemTag.FRUIT)
for vegetable in all_vegetables:
content.tag_item(vegetable, ItemTag.VEGETABLE)
for edible_mushroom in all_edible_mushrooms:
if edible_mushroom == Mushroom.magma_cap:
continue
content.tag_item(edible_mushroom, ItemTag.EDIBLE_MUSHROOM)
def finalize_hook(self, content: StardewContent):
# FIXME I hate this design. A listener design pattern would be more appropriate so artisan good are register at the exact moment a FRUIT tag is added.
for fruit in tuple(content.find_tagged_items(ItemTag.FRUIT)):
wine = ArtisanGood.specific_wine(fruit.name)
content.source_item(wine, MachineSource(item=fruit.name, machine=Machine.keg))
content.source_item(ArtisanGood.wine, MachineSource(item=fruit.name, machine=Machine.keg))
if fruit.name == Fruit.grape:
content.source_item(ArtisanGood.raisins, MachineSource(item=fruit.name, machine=Machine.dehydrator))
else:
dried_fruit = ArtisanGood.specific_dried_fruit(fruit.name)
content.source_item(dried_fruit, MachineSource(item=fruit.name, machine=Machine.dehydrator))
content.source_item(ArtisanGood.dried_fruit, MachineSource(item=fruit.name, machine=Machine.dehydrator))
jelly = ArtisanGood.specific_jelly(fruit.name)
content.source_item(jelly, MachineSource(item=fruit.name, machine=Machine.preserves_jar))
content.source_item(ArtisanGood.jelly, MachineSource(item=fruit.name, machine=Machine.preserves_jar))
for vegetable in tuple(content.find_tagged_items(ItemTag.VEGETABLE)):
if vegetable.name not in non_juiceable_vegetables:
juice = ArtisanGood.specific_juice(vegetable.name)
content.source_item(juice, MachineSource(item=vegetable.name, machine=Machine.keg))
content.source_item(ArtisanGood.juice, MachineSource(item=vegetable.name, machine=Machine.keg))
pickles = ArtisanGood.specific_pickles(vegetable.name)
content.source_item(pickles, MachineSource(item=vegetable.name, machine=Machine.preserves_jar))
content.source_item(ArtisanGood.pickles, MachineSource(item=vegetable.name, machine=Machine.preserves_jar))
for mushroom in tuple(content.find_tagged_items(ItemTag.EDIBLE_MUSHROOM)):
dried_mushroom = ArtisanGood.specific_dried_mushroom(mushroom.name)
content.source_item(dried_mushroom, MachineSource(item=mushroom.name, machine=Machine.dehydrator))
content.source_item(ArtisanGood.dried_mushroom, MachineSource(item=mushroom.name, machine=Machine.dehydrator))
# for fish in tuple(content.find_tagged_items(ItemTag.FISH)):
# smoked_fish = ArtisanGood.specific_smoked_fish(fish.name)
# content.source_item(smoked_fish, MachineSource(item=fish.name, machine=Machine.fish_smoker))
# content.source_item(ArtisanGood.smoked_fish, MachineSource(item=fish.name, machine=Machine.fish_smoker))
base_game = BaseGameContentPack(
"Base game (Vanilla)",
harvest_sources={
# Fruit tree
Fruit.apple: (HarvestFruitTreeSource(sapling=Sapling.apple, seasons=(Season.fall,)),),
Fruit.apricot: (HarvestFruitTreeSource(sapling=Sapling.apricot, seasons=(Season.spring,)),),
Fruit.cherry: (HarvestFruitTreeSource(sapling=Sapling.cherry, seasons=(Season.spring,)),),
Fruit.orange: (HarvestFruitTreeSource(sapling=Sapling.orange, seasons=(Season.summer,)),),
Fruit.peach: (HarvestFruitTreeSource(sapling=Sapling.peach, seasons=(Season.summer,)),),
Fruit.pomegranate: (HarvestFruitTreeSource(sapling=Sapling.pomegranate, seasons=(Season.fall,)),),
# Crops
Vegetable.parsnip: (HarvestCropSource(seed=Seed.parsnip, seasons=(Season.spring,)),),
Vegetable.green_bean: (HarvestCropSource(seed=Seed.bean, seasons=(Season.spring,)),),
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,)),),
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,)),),
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.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,)),),
Vegetable.hops: (HarvestCropSource(seed=Seed.hops, seasons=(Season.summer,)),),
Vegetable.corn: (HarvestCropSource(seed=Seed.corn, seasons=(Season.summer, Season.fall)),),
Flower.sunflower: (HarvestCropSource(seed=Seed.sunflower, seasons=(Season.summer, Season.fall)),),
Vegetable.red_cabbage: (HarvestCropSource(seed=Seed.red_cabbage, seasons=(Season.summer,)),),
Vegetable.eggplant: (HarvestCropSource(seed=Seed.eggplant, seasons=(Season.fall,)),),
Vegetable.pumpkin: (HarvestCropSource(seed=Seed.pumpkin, seasons=(Season.fall,)),),
Vegetable.bok_choy: (HarvestCropSource(seed=Seed.bok_choy, seasons=(Season.fall,)),),
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,)),),
Fruit.grape: (HarvestCropSource(seed=Seed.grape, seasons=(Season.fall,)),),
Vegetable.artichoke: (HarvestCropSource(seed=Seed.artichoke, seasons=(Season.fall,)),),
Vegetable.broccoli: (HarvestCropSource(seed=Seed.broccoli, seasons=(Season.fall,)),),
Vegetable.carrot: (HarvestCropSource(seed=Seed.carrot, seasons=(Season.spring,)),),
Fruit.powdermelon: (HarvestCropSource(seed=Seed.powdermelon, seasons=(Season.summer,)),),
Vegetable.summer_squash: (HarvestCropSource(seed=Seed.summer_squash, seasons=(Season.summer,)),),
Fruit.strawberry: (HarvestCropSource(seed=Seed.strawberry, seasons=(Season.spring,)),),
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: (HarvestCropSource(seed=Seed.coffee_starter, seasons=(Season.spring, Season.summer,)),),
Vegetable.tea_leaves: (CustomRuleSource(lambda logic: logic.has(Sapling.tea) & 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"))),
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),),
},
skills=(
Skill(SkillName.farming, has_mastery=True),
Skill(SkillName.foraging, has_mastery=True),
Skill(SkillName.fishing, has_mastery=True),
Skill(SkillName.mining, has_mastery=True),
Skill(SkillName.combat, has_mastery=True),
)
)

View File

@ -0,0 +1,81 @@
from .pelican_town import pelican_town as pelican_town_content_pack
from ..game_content import ContentPack, StardewContent
from ...data import villagers_data, fish_data
from ...data.game_item import ItemTag, Tag
from ...data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource
from ...data.shop import ShopSource
from ...strings.book_names import Book
from ...strings.crop_names import Fruit, Vegetable
from ...strings.fish_names import Fish
from ...strings.forageable_names import Forageable, Mushroom
from ...strings.fruit_tree_names import Sapling
from ...strings.metal_names import Fossil, Mineral
from ...strings.region_names import Region
from ...strings.season_names import Season
from ...strings.seed_names import Seed
class GingerIslandContentPack(ContentPack):
def harvest_source_hook(self, content: StardewContent):
content.tag_item(Fruit.banana, ItemTag.FRUIT)
content.tag_item(Fruit.pineapple, ItemTag.FRUIT)
content.tag_item(Fruit.mango, ItemTag.FRUIT)
content.tag_item(Vegetable.taro_root, ItemTag.VEGETABLE)
content.tag_item(Mushroom.magma_cap, ItemTag.EDIBLE_MUSHROOM)
ginger_island_content_pack = GingerIslandContentPack(
"Ginger Island (Vanilla)",
weak_dependencies=(
pelican_town_content_pack.name,
),
harvest_sources={
# Foraging
Forageable.dragon_tooth: (
ForagingSource(regions=(Region.volcano_floor_10,)),
),
Forageable.ginger: (
ForagingSource(regions=(Region.island_west,)),
),
Mushroom.magma_cap: (
ForagingSource(regions=(Region.volcano_floor_5,)),
),
# Fruit tree
Fruit.banana: (HarvestFruitTreeSource(sapling=Sapling.banana, seasons=(Season.summer,)),),
Fruit.mango: (HarvestFruitTreeSource(sapling=Sapling.mango, seasons=(Season.summer,)),),
# Crop
Vegetable.taro_root: (HarvestCropSource(seed=Seed.taro, seasons=(Season.summer,)),),
Fruit.pineapple: (HarvestCropSource(seed=Seed.pineapple, seasons=(Season.summer,)),),
},
shop_sources={
Seed.taro: (ShopSource(items_price=((2, Fossil.bone_fragment),), shop_region=Region.island_trader),),
Seed.pineapple: (ShopSource(items_price=((1, Mushroom.magma_cap),), shop_region=Region.island_trader),),
Sapling.banana: (ShopSource(items_price=((5, Forageable.dragon_tooth),), shop_region=Region.island_trader),),
Sapling.mango: (ShopSource(items_price=((75, Fish.mussel_node),), shop_region=Region.island_trader),),
# This one is 10 diamonds, should maybe add time?
Book.the_diamond_hunter: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(items_price=((10, Mineral.diamond),), shop_region=Region.volcano_dwarf_shop),
),
},
fishes=(
# TODO override region so no need to add inaccessible regions in logic
fish_data.blue_discus,
fish_data.lionfish,
fish_data.midnight_carp,
fish_data.pufferfish,
fish_data.stingray,
fish_data.super_cucumber,
fish_data.tilapia,
fish_data.tuna
),
villagers=(
villagers_data.leo,
)
)

View File

@ -0,0 +1,393 @@
from ..game_content import ContentPack
from ...data import villagers_data, fish_data
from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource
from ...data.harvest import ForagingSource, SeasonalForagingSource, ArtifactSpotSource
from ...data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement
from ...data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource
from ...strings.book_names import Book
from ...strings.crop_names import Fruit
from ...strings.fish_names import WaterItem
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.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
pelican_town = ContentPack(
"Pelican Town (Vanilla)",
harvest_sources={
# Spring
Forageable.daffodil: (
ForagingSource(seasons=(Season.spring,), regions=(Region.bus_stop, Region.town, Region.railroad)),
),
Forageable.dandelion: (
ForagingSource(seasons=(Season.spring,), regions=(Region.bus_stop, Region.forest, Region.railroad)),
),
Forageable.leek: (
ForagingSource(seasons=(Season.spring,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.railroad)),
),
Forageable.wild_horseradish: (
ForagingSource(seasons=(Season.spring,), regions=(Region.backwoods, Region.mountain, Region.forest, Region.secret_woods)),
),
Forageable.salmonberry: (
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: (
ForagingSource(seasons=(Season.spring,), regions=(Region.forest,)),
),
# Summer
Fruit.grape: (
ForagingSource(seasons=(Season.summer,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.railroad)),
),
Forageable.spice_berry: (
ForagingSource(seasons=(Season.summer,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.forest, Region.railroad)),
),
Forageable.sweet_pea: (
ForagingSource(seasons=(Season.summer,), regions=(Region.bus_stop, Region.town, Region.forest, Region.railroad)),
),
Forageable.fiddlehead_fern: (
ForagingSource(seasons=(Season.summer,), regions=(Region.secret_woods,)),
),
# Fall
Forageable.blackberry: (
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: (
ForagingSource(seasons=(Season.fall,), regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.railroad)),
),
Forageable.wild_plum: (
ForagingSource(seasons=(Season.fall,), regions=(Region.mountain, Region.bus_stop, Region.railroad)),
),
# Winter
Forageable.crocus: (
ForagingSource(seasons=(Season.winter,),
regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.secret_woods)),
),
Forageable.crystal_fruit: (
ForagingSource(seasons=(Season.winter,),
regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.railroad)),
),
Forageable.holly: (
ForagingSource(seasons=(Season.winter,),
regions=(Region.backwoods, Region.mountain, Region.bus_stop, Region.town, Region.forest, Region.railroad)),
),
Forageable.snow_yam: (
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: (
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),)),
),
# Mushrooms
Mushroom.common: (
ForagingSource(seasons=(Season.spring,), regions=(Region.secret_woods,)),
ForagingSource(seasons=(Season.fall,), regions=(Region.backwoods, Region.mountain, Region.forest)),
),
Mushroom.chanterelle: (
ForagingSource(seasons=(Season.fall,), regions=(Region.secret_woods,)),
),
Mushroom.morel: (
ForagingSource(seasons=(Season.spring, Season.fall), regions=(Region.secret_woods,)),
),
Mushroom.red: (
ForagingSource(seasons=(Season.summer, Season.fall), regions=(Region.secret_woods,)),
),
# Beach
WaterItem.coral: (
ForagingSource(regions=(Region.tide_pools,)),
SeasonalForagingSource(season=Season.summer, days=(12, 13, 14), regions=(Region.beach,)),
),
WaterItem.nautilus_shell: (
ForagingSource(seasons=(Season.winter,), regions=(Region.beach,)),
),
Forageable.rainbow_shell: (
ForagingSource(seasons=(Season.summer,), regions=(Region.beach,)),
),
WaterItem.sea_urchin: (
ForagingSource(regions=(Region.tide_pools,)),
),
Seed.mixed: (
ForagingSource(seasons=(Season.spring, Season.summer, Season.fall,), regions=(Region.town, Region.farm, Region.forest)),
),
Seed.mixed_flower: (
ForagingSource(seasons=(Season.summer,), regions=(Region.town, Region.farm, Region.forest)),
),
# Books
Book.jack_be_nimble_jack_be_thick: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ArtifactSpotSource(amount=22),), # After 22 spots, there are 50.48% chances player received the book.
Book.woodys_secret: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
GenericSource(regions=(Region.forest, Region.mountain),
other_requirements=(ToolRequirement(Tool.axe, ToolMaterial.iron), SkillRequirement(Skill.foraging, 5))),),
},
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),),
# 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.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.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.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.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)),),
# 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),),
# Books
Book.animal_catalogue: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=5000, shop_region=Region.ranch),),
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.
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),),
Book.friendship_101: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
PrizeMachineSource(amount=9),
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),),
Book.horse_the_book: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=25000, shop_region=LogicRegion.bookseller_2),),
Book.jack_be_nimble_jack_be_thick: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),),
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),),
Book.mapping_cave_systems: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
GenericSource(regions=Region.adventurer_guild_bedroom),
ShopSource(money_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),),
Book.ol_slitherlegs: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=25000, shop_region=LogicRegion.bookseller_2),),
Book.price_catalogue: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=3000, shop_region=LogicRegion.bookseller_2),),
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),),
Book.the_art_o_crabbing: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
GenericSource(regions=Region.beach,
other_requirements=(ToolRequirement(Tool.fishing_rod, ToolMaterial.iridium),
SkillRequirement(Skill.fishing, 6),
SeasonRequirement(Season.winter))),
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),),
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),),
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),),
Book.way_of_the_wind_pt_1: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=15000, shop_region=LogicRegion.bookseller_2),),
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),)),),
Book.woodys_secret: (
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),),
# Experience Books
Book.book_of_stars: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.bait_and_bobber: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.combat_quarterly: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.mining_monthly: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.stardew_valley_almanac: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.woodcutters_weekly: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.queen_of_sauce_cookbook: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=50000, shop_region=LogicRegion.bookseller_2),), # Worst book ever
},
fishes=(
fish_data.albacore,
fish_data.anchovy,
fish_data.bream,
fish_data.bullhead,
fish_data.carp,
fish_data.catfish,
fish_data.chub,
fish_data.dorado,
fish_data.eel,
fish_data.flounder,
fish_data.goby,
fish_data.halibut,
fish_data.herring,
fish_data.largemouth_bass,
fish_data.lingcod,
fish_data.midnight_carp, # Ginger island override
fish_data.octopus,
fish_data.perch,
fish_data.pike,
fish_data.pufferfish, # Ginger island override
fish_data.rainbow_trout,
fish_data.red_mullet,
fish_data.red_snapper,
fish_data.salmon,
fish_data.sardine,
fish_data.sea_cucumber,
fish_data.shad,
fish_data.slimejack,
fish_data.smallmouth_bass,
fish_data.squid,
fish_data.sturgeon,
fish_data.sunfish,
fish_data.super_cucumber, # Ginger island override
fish_data.tiger_trout,
fish_data.tilapia, # Ginger island override
fish_data.tuna, # Ginger island override
fish_data.void_salmon,
fish_data.walleye,
fish_data.woodskip,
fish_data.blobfish,
fish_data.midnight_squid,
fish_data.spook_fish,
# Legendaries
fish_data.angler,
fish_data.crimsonfish,
fish_data.glacierfish,
fish_data.legend,
fish_data.mutant_carp,
# Crab pot
fish_data.clam,
fish_data.cockle,
fish_data.crab,
fish_data.crayfish,
fish_data.lobster,
fish_data.mussel,
fish_data.oyster,
fish_data.periwinkle,
fish_data.shrimp,
fish_data.snail,
),
villagers=(
villagers_data.josh,
villagers_data.elliott,
villagers_data.harvey,
villagers_data.sam,
villagers_data.sebastian,
villagers_data.shane,
villagers_data.abigail,
villagers_data.emily,
villagers_data.haley,
villagers_data.leah,
villagers_data.maru,
villagers_data.penny,
villagers_data.caroline,
villagers_data.clint,
villagers_data.demetrius,
villagers_data.evelyn,
villagers_data.george,
villagers_data.gus,
villagers_data.jas,
villagers_data.jodi,
villagers_data.kent,
villagers_data.krobus,
villagers_data.lewis,
villagers_data.linus,
villagers_data.marnie,
villagers_data.pam,
villagers_data.pierre,
villagers_data.robin,
villagers_data.vincent,
villagers_data.willy,
villagers_data.wizard,
)
)

View File

@ -0,0 +1,36 @@
from .ginger_island import ginger_island_content_pack as ginger_island_content_pack
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.harvest import HarvestCropSource
from ...strings.crop_names import Fruit
from ...strings.region_names import Region
from ...strings.season_names import Season
from ...strings.seed_names import Seed
class QiBoardContentPack(ContentPack):
def harvest_source_hook(self, content: StardewContent):
content.untag_item(Seed.qi_bean, ItemTag.CROPSANITY_SEED)
qi_board_content_pack = QiBoardContentPack(
"Qi Board (Vanilla)",
dependencies=(
pelican_town_content_pack.name,
ginger_island_content_pack.name,
),
harvest_sources={
# This one is a bit special, because it's only available during the special order, but it can be found from like, everywhere.
Seed.qi_bean: (GenericSource(regions=(Region.qi_walnut_room,)),),
Fruit.qi_fruit: (HarvestCropSource(seed=Seed.qi_bean),),
},
fishes=(
fish_data.ms_angler,
fish_data.son_of_crimsonfish,
fish_data.glacierfish_jr,
fish_data.legend_ii,
fish_data.radioactive_carp,
)
)

View File

@ -0,0 +1,46 @@
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.harvest import ForagingSource, HarvestCropSource
from ...data.shop import ShopSource
from ...strings.crop_names import Fruit, Vegetable
from ...strings.forageable_names import Forageable, Mushroom
from ...strings.region_names import Region
from ...strings.season_names import Season
from ...strings.seed_names import Seed
the_desert = ContentPack(
"The Desert (Vanilla)",
dependencies=(
pelican_town_content_pack.name,
),
harvest_sources={
Forageable.cactus_fruit: (
ForagingSource(regions=(Region.desert,)),
HarvestCropSource(seed=Seed.cactus, seasons=())
),
Forageable.coconut: (
ForagingSource(regions=(Region.desert,)),
),
Mushroom.purple: (
ForagingSource(regions=(Region.skull_cavern_25,)),
),
Fruit.rhubarb: (HarvestCropSource(seed=Seed.rhubarb, seasons=(Season.spring,)),),
Fruit.starfruit: (HarvestCropSource(seed=Seed.starfruit, seasons=(Season.summer,)),),
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,)),),
},
fishes=(
fish_data.sandfish,
fish_data.scorpion_carp,
),
villagers=(
villagers_data.sandy,
),
)

View File

@ -0,0 +1,43 @@
from .pelican_town import pelican_town as pelican_town_content_pack
from ..game_content import ContentPack
from ...data.harvest import FruitBatsSource, MushroomCaveSource
from ...strings.forageable_names import Forageable, Mushroom
the_farm = ContentPack(
"The Farm (Vanilla)",
dependencies=(
pelican_town_content_pack.name,
),
harvest_sources={
# Fruit cave
Forageable.blackberry: (
FruitBatsSource(),
),
Forageable.salmonberry: (
FruitBatsSource(),
),
Forageable.spice_berry: (
FruitBatsSource(),
),
Forageable.wild_plum: (
FruitBatsSource(),
),
# Mushrooms
Mushroom.common: (
MushroomCaveSource(),
),
Mushroom.chanterelle: (
MushroomCaveSource(),
),
Mushroom.morel: (
MushroomCaveSource(),
),
Mushroom.purple: (
MushroomCaveSource(),
),
Mushroom.red: (
MushroomCaveSource(),
),
}
)

View File

@ -0,0 +1,35 @@
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.harvest import ForagingSource
from ...data.requirement import ToolRequirement
from ...strings.forageable_names import Forageable, Mushroom
from ...strings.region_names import Region
from ...strings.tool_names import Tool
the_mines = ContentPack(
"The Mines (Vanilla)",
dependencies=(
pelican_town_content_pack.name,
),
harvest_sources={
Forageable.cave_carrot: (
ForagingSource(regions=(Region.mines_floor_10,), other_requirements=(ToolRequirement(Tool.hoe),)),
),
Mushroom.red: (
ForagingSource(regions=(Region.mines_floor_95,)),
),
Mushroom.purple: (
ForagingSource(regions=(Region.mines_floor_95,)),
)
},
fishes=(
fish_data.ghostfish,
fish_data.ice_pip,
fish_data.lava_eel,
fish_data.stonefish,
),
villagers=(
villagers_data.dwarf,
),
)

View File

@ -1,2 +0,0 @@
from .crops_data import CropItem, SeedItem, all_crops, all_purchasable_seeds
from .fish_data import FishItem, all_fish

View File

@ -0,0 +1,10 @@
from dataclasses import dataclass
from .game_item import kw_only, ItemSource
@dataclass(frozen=True, **kw_only)
class MachineSource(ItemSource):
item: str # this should be optional (worm bin)
machine: str
# seasons

View File

@ -1,17 +1,19 @@
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
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.fish_names import Fish, WaterItem, Trash, all_fish
from ..strings.flower_names import Flower
from ..strings.food_names import Beverage, Meal
from ..strings.forageable_names import Forageable
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
@ -19,27 +21,27 @@ 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
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(Forageable.morel)
common_mushroom = BundleItem(Forageable.common_mushroom)
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(Forageable.red_mushroom)
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(Forageable.chanterelle)
chanterelle = BundleItem(Mushroom.chanterelle)
winter_root = BundleItem(Forageable.winter_root)
crystal_fruit = BundleItem(Forageable.crystal_fruit)
@ -50,7 +52,7 @@ holly = BundleItem(Forageable.holly)
coconut = BundleItem(Forageable.coconut)
cactus_fruit = BundleItem(Forageable.cactus_fruit)
cave_carrot = BundleItem(Forageable.cave_carrot)
purple_mushroom = BundleItem(Forageable.purple_mushroom)
purple_mushroom = BundleItem(Mushroom.purple)
maple_syrup = BundleItem(ArtisanGood.maple_syrup)
oak_resin = BundleItem(ArtisanGood.oak_resin)
pine_tar = BundleItem(ArtisanGood.pine_tar)
@ -62,13 +64,25 @@ clam = BundleItem(Fish.clam)
cockle = BundleItem(Fish.cockle)
mussel = BundleItem(Fish.mussel)
oyster = BundleItem(Fish.oyster)
seaweed = BundleItem(WaterItem.seaweed)
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)
@ -106,8 +120,13 @@ 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.island)
taro_root = BundleItem(Vegetable.taro_root, source=BundleItem.Sources.island, )
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)
@ -151,8 +170,8 @@ orange = BundleItem(Fruit.orange)
peach = BundleItem(Fruit.peach)
pomegranate = BundleItem(Fruit.pomegranate)
cherry = BundleItem(Fruit.cherry)
banana = BundleItem(Fruit.banana, source=BundleItem.Sources.island)
mango = BundleItem(Fruit.mango, source=BundleItem.Sources.island)
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)
@ -300,6 +319,13 @@ 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)
@ -370,6 +396,7 @@ 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)
@ -377,18 +404,67 @@ 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, 10)
magic_bait = BundleItem(Fishing.magic_bait, 5, source=BundleItem.Sources.island)
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.island)
magma_cap = BundleItem(Forageable.magma_cap, source=BundleItem.Sources.island)
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]
@ -436,42 +512,50 @@ island_foraging_bundle = IslandBundleTemplate(CCRoom.crafts_room, BundleName.isl
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(1)
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]})
*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, wild_medicine_bundle, quality_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]
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_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]
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})
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]
@ -492,7 +576,8 @@ artisan_bundle_thematic = BundleTemplate.extend_from(artisan_bundle_vanilla, art
rare_crops_items = [ancient_fruit, sweet_gem_berry]
rare_crops_bundle = BundleTemplate(CCRoom.pantry, BundleName.rare_crops, rare_crops_items, 2, 2)
fish_farmer_items = [roe.as_amount(15), aged_roe.as_amount(15), squid_ink]
# 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]
@ -516,12 +601,20 @@ slime_farmer_items = [slime.as_amount(99), petrified_slime.as_amount(10), blue_s
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]
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)
@ -579,8 +672,11 @@ winter_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.winter_fish, wi
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) 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, 4)
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)
@ -591,21 +687,31 @@ legendary_fish_bundle = BundleTemplate(CCRoom.fish_tank, BundleName.legendary_fi
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, cork_bobber, lead_bobber, treasure_hunter, barbed_hook, curiosity_lure, quality_bobber]
tackle_bundle = IslandBundleTemplate(CCRoom.fish_tank, BundleName.tackle, tackle_items, 3, 2)
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]
bait_bundle = IslandBundleTemplate(CCRoom.fish_tank, BundleName.bait, bait_items, 2, 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]
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)
@ -670,12 +776,12 @@ chef_bundle_vanilla = BundleTemplate(CCRoom.bulletin_board, BundleName.chef, che
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]
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]
dye_green_items = [fiddlehead_fern, kale, artichoke, bok_choy, green_bean]
dye_blue_items = [blueberry, blue_jazz, blackberry, crystal_fruit]
dye_purple_items = [beet, crocus, eggplant, red_cabbage, sweet_pea]
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)
@ -710,12 +816,31 @@ home_cook_items = [egg.as_amount(10), milk.as_amount(10), wheat_flour.as_amount(
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, bartender_bundle]
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)
@ -738,16 +863,15 @@ abandoned_joja_mart_vanilla = BundleRoomTemplate(CCRoom.abandoned_joja_mart, aba
abandoned_joja_mart_thematic = BundleRoomTemplate(CCRoom.abandoned_joja_mart, abandoned_joja_mart_bundles_thematic, 1)
abandoned_joja_mart_remixed = abandoned_joja_mart_thematic
# Make thematic with other currencies
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, vault_2500_gold)
vault_5000_bundle = MoneyBundleTemplate(CCRoom.vault, vault_5000_gold)
vault_10000_bundle = MoneyBundleTemplate(CCRoom.vault, vault_10000_gold)
vault_25000_bundle = MoneyBundleTemplate(CCRoom.vault, vault_25000_gold)
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)
@ -768,9 +892,14 @@ 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]
*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)

View File

@ -1,25 +1,28 @@
from typing import Dict, List, Optional
from ..mods.mod_data import ModNames
from .recipe_source import RecipeSource, StarterSource, QueenOfSauceSource, ShopSource, SkillSource, FriendshipSource, ShopTradeSource, CutsceneSource, \
ArchipelagoSource, LogicSource, SpecialOrderSource, FestivalShopSource, QuestSource
ArchipelagoSource, LogicSource, SpecialOrderSource, FestivalShopSource, QuestSource, MasterySource
from ..mods.mod_data import ModNames
from ..strings.animal_product_names import AnimalProduct
from ..strings.artisan_good_names import ArtisanGood
from ..strings.craftable_names import Bomb, Fence, Sprinkler, WildSeeds, Floor, Fishing, Ring, Consumable, Edible, Lighting, Storage, Furniture, Sign, Craftable, \
ModEdible, ModCraftable, ModMachine, ModFloor, ModConsumable
from ..strings.craftable_names import Bomb, Fence, Sprinkler, WildSeeds, Floor, Fishing, Ring, Consumable, Edible, Lighting, Storage, Furniture, Sign, \
Craftable, \
ModEdible, ModCraftable, ModMachine, ModFloor, ModConsumable, Statue
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
from ..strings.fish_names import Fish, WaterItem, ModTrash
from ..strings.flower_names import Flower
from ..strings.food_names import Meal
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable, Mushroom
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.metal_names import Ore, MetalBar, Fossil, Artifact, Mineral, ModFossil
from ..strings.monster_drop_names import Loot
from ..strings.monster_drop_names import Loot, ModLoot
from ..strings.quest_names import Quest
from ..strings.region_names import Region, SVERegion
from ..strings.region_names import Region, SVERegion, LogicRegion
from ..strings.seed_names import Seed, TreeSeed
from ..strings.skill_names import Skill, ModSkill
from ..strings.special_order_names import SpecialOrder
@ -61,6 +64,11 @@ def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int],
return create_recipe(name, ingredients, source, mod_name)
def mastery_recipe(name: str, skill: str, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe:
source = MasterySource(skill)
return create_recipe(name, ingredients, source, mod_name)
def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe:
source = ShopSource(region, price)
return create_recipe(name, ingredients, source, mod_name)
@ -133,27 +141,37 @@ cask = cellar_recipe(Machine.cask, {Material.wood: 40, Material.hardwood: 1})
cheese_press = skill_recipe(Machine.cheese_press, Skill.farming, 6, {Material.wood: 45, Material.stone: 45, Material.hardwood: 10, MetalBar.copper: 1})
keg = skill_recipe(Machine.keg, Skill.farming, 8, {Material.wood: 30, MetalBar.copper: 1, MetalBar.iron: 1, ArtisanGood.oak_resin: 1})
loom = skill_recipe(Machine.loom, Skill.farming, 7, {Material.wood: 60, Material.fiber: 30, ArtisanGood.pine_tar: 1})
mayonnaise_machine = skill_recipe(Machine.mayonnaise_machine, Skill.farming, 2, {Material.wood: 15, Material.stone: 15, Mineral.earth_crystal: 10, MetalBar.copper: 1})
mayonnaise_machine = skill_recipe(Machine.mayonnaise_machine, Skill.farming, 2,
{Material.wood: 15, Material.stone: 15, Mineral.earth_crystal: 10, MetalBar.copper: 1})
oil_maker = skill_recipe(Machine.oil_maker, Skill.farming, 8, {Loot.slime: 50, Material.hardwood: 20, MetalBar.gold: 1})
preserves_jar = skill_recipe(Machine.preserves_jar, Skill.farming, 4, {Material.wood: 50, Material.stone: 40, Material.coal: 8})
fish_smoker = shop_recipe(Machine.fish_smoker, Region.fish_shop, 10000,
{Material.hardwood: 10, WaterItem.sea_jelly: 1, WaterItem.river_jelly: 1, WaterItem.cave_jelly: 1})
dehydrator = shop_recipe(Machine.dehydrator, Region.pierre_store, 10000, {Material.wood: 30, Material.clay: 2, Mineral.fire_quartz: 1})
basic_fertilizer = skill_recipe(Fertilizer.basic, Skill.farming, 1, {Material.sap: 2})
quality_fertilizer = skill_recipe(Fertilizer.quality, Skill.farming, 9, {Material.sap: 2, Fish.any: 1})
quality_fertilizer = skill_recipe(Fertilizer.quality, Skill.farming, 9, {Material.sap: 4, Fish.any: 1})
deluxe_fertilizer = ap_recipe(Fertilizer.deluxe, {MetalBar.iridium: 1, Material.sap: 40})
basic_speed_gro = skill_recipe(SpeedGro.basic, Skill.farming, 3, {ArtisanGood.pine_tar: 1, Fish.clam: 1})
deluxe_speed_gro = skill_recipe(SpeedGro.deluxe, Skill.farming, 8, {ArtisanGood.oak_resin: 1, WaterItem.coral: 1})
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})
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})
deluxe_retaining_soil = shop_trade_recipe(RetainingSoil.deluxe, Region.island_trader, Currency.cinder_shard, 50,
{Material.stone: 5, Material.fiber: 3, Material.clay: 1})
tree_fertilizer = skill_recipe(Fertilizer.tree, Skill.foraging, 7, {Material.fiber: 5, Material.stone: 5})
spring_seeds = skill_recipe(WildSeeds.spring, Skill.foraging, 1, {Forageable.wild_horseradish: 1, Forageable.daffodil: 1, Forageable.leek: 1, Forageable.dandelion: 1})
spring_seeds = skill_recipe(WildSeeds.spring, Skill.foraging, 1,
{Forageable.wild_horseradish: 1, Forageable.daffodil: 1, Forageable.leek: 1, Forageable.dandelion: 1})
summer_seeds = skill_recipe(WildSeeds.summer, Skill.foraging, 4, {Forageable.spice_berry: 1, Fruit.grape: 1, Forageable.sweet_pea: 1})
fall_seeds = skill_recipe(WildSeeds.fall, Skill.foraging, 6, {Forageable.common_mushroom: 1, Forageable.wild_plum: 1, Forageable.hazelnut: 1, Forageable.blackberry: 1})
winter_seeds = skill_recipe(WildSeeds.winter, Skill.foraging, 7, {Forageable.winter_root: 1, Forageable.crystal_fruit: 1, Forageable.snow_yam: 1, Forageable.crocus: 1})
fall_seeds = skill_recipe(WildSeeds.fall, Skill.foraging, 6, {Mushroom.common: 1, Forageable.wild_plum: 1, Forageable.hazelnut: 1, Forageable.blackberry: 1})
winter_seeds = skill_recipe(WildSeeds.winter, Skill.foraging, 7,
{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})
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})
@ -161,7 +179,7 @@ fiber_seeds = special_order_recipe(WildSeeds.fiber, SpecialOrder.community_clean
wood_floor = shop_recipe(Floor.wood, Region.carpenter, 100, {Material.wood: 1})
rustic_floor = shop_recipe(Floor.rustic, Region.carpenter, 200, {Material.wood: 1})
straw_floor = shop_recipe(Floor.straw, Region.carpenter, 200, {Material.wood: 1, Material.fiber: 1})
weathered_floor = shop_recipe(Floor.weathered, Region.mines_dwarf_shop, 500, {Material.wood: 1})
weathered_floor = shop_recipe(Floor.weathered, LogicRegion.mines_dwarf_shop, 500, {Material.wood: 1})
crystal_floor = shop_recipe(Floor.crystal, Region.sewer, 500, {MetalBar.quartz: 1})
stone_floor = shop_recipe(Floor.stone, Region.carpenter, 100, {Material.stone: 1})
stone_walkway_floor = shop_recipe(Floor.stone_walkway, Region.carpenter, 200, {Material.stone: 1})
@ -174,6 +192,7 @@ crystal_path = shop_recipe(Floor.crystal_path, Region.carpenter, 200, {MetalBar.
spinner = skill_recipe(Fishing.spinner, Skill.fishing, 6, {MetalBar.iron: 2})
trap_bobber = skill_recipe(Fishing.trap_bobber, Skill.fishing, 6, {MetalBar.copper: 1, Material.sap: 10})
sonar_bobber = skill_recipe(Fishing.sonar_bobber, Skill.fishing, 6, {MetalBar.iron: 1, MetalBar.quartz: 2})
cork_bobber = skill_recipe(Fishing.cork_bobber, Skill.fishing, 7, {Material.wood: 10, Material.hardwood: 5, Loot.slime: 10})
quality_bobber = special_order_recipe(Fishing.quality_bobber, SpecialOrder.juicy_bugs_wanted, {MetalBar.copper: 1, Material.sap: 20, Loot.solar_essence: 5})
treasure_hunter = skill_recipe(Fishing.treasure_hunter, Skill.fishing, 7, {MetalBar.gold: 2})
@ -181,6 +200,7 @@ dressed_spinner = skill_recipe(Fishing.dressed_spinner, Skill.fishing, 8, {Metal
barbed_hook = skill_recipe(Fishing.barbed_hook, Skill.fishing, 8, {MetalBar.copper: 1, MetalBar.iron: 1, MetalBar.gold: 1})
magnet = skill_recipe(Fishing.magnet, Skill.fishing, 9, {MetalBar.iron: 1})
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})
crab_pot = skill_recipe(Machine.crab_pot, Skill.fishing, 3, {Material.wood: 40, MetalBar.iron: 3})
@ -191,11 +211,11 @@ ring_of_yoba = skill_recipe(Ring.ring_of_yoba, Skill.combat, 7, {MetalBar.gold:
thorns_ring = skill_recipe(Ring.thorns_ring, Skill.combat, 7, {Fossil.bone_fragment: 50, Material.stone: 50, MetalBar.gold: 1})
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, Region.traveling_cart, 500, {MetalBar.iridium: 5, Mineral.prismatic_shard: 1})
wedding_ring = shop_recipe(Ring.wedding_ring, LogicRegion.traveling_cart, 500, {MetalBar.iridium: 5, Mineral.prismatic_shard: 1})
field_snack = skill_recipe(Edible.field_snack, Skill.foraging, 1, {TreeSeed.acorn: 1, TreeSeed.maple: 1, TreeSeed.pine: 1})
bug_steak = skill_recipe(Edible.bug_steak, Skill.combat, 1, {Loot.bug_meat: 10})
life_elixir = skill_recipe(Edible.life_elixir, Skill.combat, 2, {Forageable.red_mushroom: 1, Forageable.purple_mushroom: 1, Forageable.morel: 1, Forageable.chanterelle: 1})
life_elixir = skill_recipe(Edible.life_elixir, Skill.combat, 2, {Mushroom.red: 1, Mushroom.purple: 1, Mushroom.morel: 1, Mushroom.chanterelle: 1})
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})
@ -203,8 +223,10 @@ fairy_dust = quest_recipe(Consumable.fairy_dust, Quest.the_pirates_wife, {Minera
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})
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})
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})
@ -219,13 +241,17 @@ skull_brazier = shop_recipe(Lighting.skull_brazier, Region.carpenter, 3000, {Fos
marble_brazier = shop_recipe(Lighting.marble_brazier, Region.carpenter, 5000, {Mineral.marble: 1, Mineral.aquamarine: 1, Material.stone: 100})
wood_lamp_post = shop_recipe(Lighting.wood_lamp_post, Region.carpenter, 500, {Material.wood: 50, ArtisanGood.battery_pack: 1})
iron_lamp_post = shop_recipe(Lighting.iron_lamp_post, Region.carpenter, 1000, {MetalBar.iron: 1, ArtisanGood.battery_pack: 1})
jack_o_lantern = festival_shop_recipe(Lighting.jack_o_lantern, Region.spirit_eve, 2000, {Vegetable.pumpkin: 1, Lighting.torch: 1})
jack_o_lantern = festival_shop_recipe(Lighting.jack_o_lantern, LogicRegion.spirit_eve, 2000, {Vegetable.pumpkin: 1, Lighting.torch: 1})
bone_mill = special_order_recipe(Machine.bone_mill, SpecialOrder.fragments_of_the_past, {Fossil.bone_fragment: 10, Material.clay: 3, Material.stone: 20})
charcoal_kiln = skill_recipe(Machine.charcoal_kiln, Skill.foraging, 4, {Material.wood: 20, MetalBar.copper: 2})
bait_maker = skill_recipe(Machine.bait_maker, Skill.fishing, 6, {MetalBar.iron: 3, WaterItem.coral: 3, WaterItem.sea_urchin: 1})
charcoal_kiln = skill_recipe(Machine.charcoal_kiln, Skill.foraging, 2, {Material.wood: 20, MetalBar.copper: 2})
crystalarium = skill_recipe(Machine.crystalarium, Skill.mining, 9, {Material.stone: 99, MetalBar.gold: 5, MetalBar.iridium: 2, ArtisanGood.battery_pack: 1})
furnace = skill_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})
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})
@ -234,20 +260,27 @@ seed_maker = skill_recipe(Machine.seed_maker, Skill.farming, 9, {Material.wood:
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})
tapper = skill_recipe(Machine.tapper, Skill.foraging, 3, {Material.wood: 40, MetalBar.copper: 2})
worm_bin = skill_recipe(Machine.worm_bin, Skill.fishing, 8, {Material.hardwood: 25, MetalBar.gold: 1, MetalBar.iron: 1, Material.fiber: 50})
tub_o_flowers = festival_shop_recipe(Furniture.tub_o_flowers, Region.flower_dance, 2000, {Material.wood: 15, Seed.tulip: 1, Seed.jazz: 1, Seed.poppy: 1, Seed.spangle: 1})
tapper = skill_recipe(Machine.tapper, Skill.foraging, 4, {Material.wood: 40, MetalBar.copper: 2})
worm_bin = skill_recipe(Machine.worm_bin, Skill.fishing, 4, {Material.hardwood: 25, MetalBar.gold: 1, MetalBar.iron: 1, Material.fiber: 50})
deluxe_worm_bin = skill_recipe(Machine.deluxe_worm_bin, Skill.fishing, 8, {Machine.worm_bin: 1, Material.moss: 30})
tub_o_flowers = festival_shop_recipe(Furniture.tub_o_flowers, LogicRegion.flower_dance, 2000,
{Material.wood: 15, Seed.tulip: 1, Seed.jazz: 1, Seed.poppy: 1, Seed.spangle: 1})
wicked_statue = shop_recipe(Furniture.wicked_statue, Region.sewer, 1000, {Material.stone: 25, Material.coal: 5})
flute_block = cutscene_recipe(Furniture.flute_block, Region.carpenter, NPC.robin, 6, {Material.wood: 10, Ore.copper: 2, Material.fiber: 20})
drum_block = cutscene_recipe(Furniture.drum_block, Region.carpenter, NPC.robin, 6, {Material.stone: 10, Ore.copper: 2, Material.fiber: 20})
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})
wood_sign = starter_recipe(Sign.wood, {Material.wood: 25})
stone_sign = starter_recipe(Sign.stone, {Material.stone: 25})
dark_sign = friendship_recipe(Sign.dark, NPC.krobus, 3, {Loot.bat_wing: 5, Fossil.bone_fragment: 5})
text_sign = starter_recipe(Sign.text, {Material.wood: 25})
garden_pot = ap_recipe(Craftable.garden_pot, {Material.clay: 1, Material.stone: 10, MetalBar.quartz: 1}, "Greenhouse")
scarecrow = skill_recipe(Craftable.scarecrow, Skill.farming, 1, {Material.wood: 50, Material.coal: 1, Material.fiber: 20})
@ -258,56 +291,84 @@ transmute_fe = skill_recipe(Craftable.transmute_fe, Skill.mining, 4, {MetalBar.c
transmute_au = skill_recipe(Craftable.transmute_au, Skill.mining, 7, {MetalBar.iron: 2})
mini_jukebox = cutscene_recipe(Craftable.mini_jukebox, Region.saloon, NPC.gus, 5, {MetalBar.iron: 2, ArtisanGood.battery_pack: 1})
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})
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})
cookout_kit = skill_recipe(Craftable.cookout_kit, Skill.foraging, 9, {Material.wood: 15, Material.fiber: 10, Material.coal: 3})
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})
statue_of_blessings = mastery_recipe(Statue.blessings, Skill.farming, {Material.sap: 999, Material.fiber: 999, Material.stone: 999})
statue_of_dwarf_king = mastery_recipe(Statue.dwarf_king, Skill.mining, {MetalBar.iridium: 20})
heavy_furnace = mastery_recipe(Machine.heavy_furnace, Skill.mining, {Machine.furnace: 2, MetalBar.iron: 3, Material.stone: 50})
mystic_tree_seed = mastery_recipe(TreeSeed.mystic, Skill.foraging, {TreeSeed.acorn: 5, TreeSeed.maple: 5, TreeSeed.pine: 5, TreeSeed.mahogany: 5})
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})
travel_charm = shop_recipe(ModCraftable.travel_core, Region.adventurer_guild, 250, {Loot.solar_essence: 1, Loot.void_essence: 1}, ModNames.magic)
preservation_chamber = skill_recipe(ModMachine.preservation_chamber, ModSkill.archaeology, 2, {MetalBar.copper: 1, Material.wood: 15, ArtisanGood.oak_resin: 30},
preservation_chamber = skill_recipe(ModMachine.preservation_chamber, ModSkill.archaeology, 1,
{MetalBar.copper: 1, Material.wood: 15, ArtisanGood.oak_resin: 30},
ModNames.archaeology)
preservation_chamber_h = skill_recipe(ModMachine.hardwood_preservation_chamber, ModSkill.archaeology, 7, {MetalBar.copper: 1, Material.hardwood: 15,
restoration_table = skill_recipe(ModMachine.restoration_table, ModSkill.archaeology, 1, {Material.wood: 15, MetalBar.copper: 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, 8, {Artifact.rusty_cog: 10, MetalBar.iron: 5, ArtisanGood.battery_pack: 1}, ModNames.archaeology)
ancient_battery = skill_recipe(ModMachine.ancient_battery, ModSkill.archaeology, 6, {Material.stone: 40, MetalBar.copper: 10, MetalBar.iron: 5},
grinder = skill_recipe(ModMachine.grinder, ModSkill.archaeology, 2, {Artifact.rusty_cog: 10, MetalBar.iron: 5, ArtisanGood.battery_pack: 1},
ModNames.archaeology)
glass_bazier = skill_recipe(ModCraftable.glass_bazier, ModSkill.archaeology, 1, {Artifact.glass_shards: 10}, ModNames.archaeology)
glass_path = skill_recipe(ModFloor.glass_path, ModSkill.archaeology, 1, {Artifact.glass_shards: 1}, ModNames.archaeology)
glass_fence = skill_recipe(ModCraftable.glass_fence, ModSkill.archaeology, 1, {Artifact.glass_shards: 5}, ModNames.archaeology)
bone_path = skill_recipe(ModFloor.bone_path, ModSkill.archaeology, 3, {Fossil.bone_fragment: 1}, ModNames.archaeology)
ancient_battery = skill_recipe(ModMachine.ancient_battery, ModSkill.archaeology, 7, {Material.stone: 40, MetalBar.copper: 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)
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, 2, {Material.wood: 25}, 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)
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, SVEForage.void_soul: 5, Ingredient.sugar: 1,
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)
hero_elixir = shop_recipe(ModEdible.hero_elixir, SVERegion.isaac_shop, 65000, {SVEForage.void_pebble: 3, SVEForage.void_soul: 5, Ingredient.oil: 1,
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)
armor_elixir = shop_recipe(ModEdible.armor_elixir, SVERegion.alesia_shop, 50000, {Loot.solar_essence: 30, SVEForage.void_soul: 5, Ingredient.vinegar: 5,
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)
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)
Material.cinder_shard: 1, DistantLandsForageable.swamp_herb: 1},
ModNames.distant_lands)
neanderthal_skeleton = shop_recipe(ModCraftable.neanderthal_skeleton, Region.mines_dwarf_shop, 5000,
{ModFossil.neanderthal_skull: 1, ModFossil.neanderthal_ribs: 1, ModFossil.neanderthal_pelvis: 1, ModFossil.neanderthal_limb_bones: 1,
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)
pterodactyl_skeleton_l = shop_recipe(ModCraftable.pterodactyl_skeleton_l, Region.mines_dwarf_shop, 5000,
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)
pterodactyl_skeleton_m = shop_recipe(ModCraftable.pterodactyl_skeleton_m, Region.mines_dwarf_shop, 5000,
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)
pterodactyl_skeleton_r = shop_recipe(ModCraftable.pterodactyl_skeleton_r, Region.mines_dwarf_shop, 5000,
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)
trex_skeleton_l = shop_recipe(ModCraftable.trex_skeleton_l, Region.mines_dwarf_shop, 5000,
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)
trex_skeleton_m = shop_recipe(ModCraftable.trex_skeleton_m, Region.mines_dwarf_shop, 5000,
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)
trex_skeleton_r = shop_recipe(ModCraftable.trex_skeleton_r, Region.mines_dwarf_shop, 5000,
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)
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)
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)
all_crafting_recipes_by_name = {recipe.item: recipe for recipe in all_crafting_recipes}

View File

@ -1,41 +0,0 @@
crop,farm_growth_seasons,seed,seed_seasons,seed_regions,requires_island
Amaranth,Fall,Amaranth Seeds,Fall,"Pierre's General Store",False
Artichoke,Fall,Artichoke Seeds,Fall,"Pierre's General Store",False
Beet,Fall,Beet Seeds,Fall,Oasis,False
Blue Jazz,Spring,Jazz Seeds,Spring,"Pierre's General Store",False
Blueberry,Summer,Blueberry Seeds,Summer,"Pierre's General Store",False
Bok Choy,Fall,Bok Choy Seeds,Fall,"Pierre's General Store",False
Cactus Fruit,,Cactus Seeds,,Oasis,False
Cauliflower,Spring,Cauliflower Seeds,Spring,"Pierre's General Store",False
Coffee Bean,"Spring,Summer",Coffee Bean,"Summer,Fall","Traveling Cart",False
Corn,"Summer,Fall",Corn Seeds,"Summer,Fall","Pierre's General Store",False
Cranberries,Fall,Cranberry Seeds,Fall,"Pierre's General Store",False
Eggplant,Fall,Eggplant Seeds,Fall,"Pierre's General Store",False
Fairy Rose,Fall,Fairy Seeds,Fall,"Pierre's General Store",False
Garlic,Spring,Garlic Seeds,Spring,"Pierre's General Store",False
Grape,Fall,Grape Starter,Fall,"Pierre's General Store",False
Green Bean,Spring,Bean Starter,Spring,"Pierre's General Store",False
Hops,Summer,Hops Starter,Summer,"Pierre's General Store",False
Hot Pepper,Summer,Pepper Seeds,Summer,"Pierre's General Store",False
Kale,Spring,Kale Seeds,Spring,"Pierre's General Store",False
Melon,Summer,Melon Seeds,Summer,"Pierre's General Store",False
Parsnip,Spring,Parsnip Seeds,Spring,"Pierre's General Store",False
Pineapple,Summer,Pineapple Seeds,Summer,"Island Trader",True
Poppy,Summer,Poppy Seeds,Summer,"Pierre's General Store",False
Potato,Spring,Potato Seeds,Spring,"Pierre's General Store",False
Qi Fruit,"Spring,Summer,Fall,Winter",Qi Bean,"Spring,Summer,Fall,Winter","Qi's Walnut Room",True
Pumpkin,Fall,Pumpkin Seeds,Fall,"Pierre's General Store",False
Radish,Summer,Radish Seeds,Summer,"Pierre's General Store",False
Red Cabbage,Summer,Red Cabbage Seeds,Summer,"Pierre's General Store",False
Rhubarb,Spring,Rhubarb Seeds,Spring,Oasis,False
Starfruit,Summer,Starfruit Seeds,Summer,Oasis,False
Strawberry,Spring,Strawberry Seeds,Spring,"Pierre's General Store",False
Summer Spangle,Summer,Spangle Seeds,Summer,"Pierre's General Store",False
Sunflower,"Summer,Fall",Sunflower Seeds,"Summer,Fall","Pierre's General Store",False
Sweet Gem Berry,Fall,Rare Seed,"Spring,Summer",Traveling Cart,False
Taro Root,Summer,Taro Tuber,Summer,"Island Trader",True
Tomato,Summer,Tomato Seeds,Summer,"Pierre's General Store",False
Tulip,Spring,Tulip Bulb,Spring,"Pierre's General Store",False
Unmilled Rice,Spring,Rice Shoot,Spring,"Pierre's General Store",False
Wheat,"Summer,Fall",Wheat Seeds,"Summer,Fall","Pierre's General Store",False
Yam,Fall,Yam Seeds,Fall,"Pierre's General Store",False
1 crop farm_growth_seasons seed seed_seasons seed_regions requires_island
2 Amaranth Fall Amaranth Seeds Fall Pierre's General Store False
3 Artichoke Fall Artichoke Seeds Fall Pierre's General Store False
4 Beet Fall Beet Seeds Fall Oasis False
5 Blue Jazz Spring Jazz Seeds Spring Pierre's General Store False
6 Blueberry Summer Blueberry Seeds Summer Pierre's General Store False
7 Bok Choy Fall Bok Choy Seeds Fall Pierre's General Store False
8 Cactus Fruit Cactus Seeds Oasis False
9 Cauliflower Spring Cauliflower Seeds Spring Pierre's General Store False
10 Coffee Bean Spring,Summer Coffee Bean Summer,Fall Traveling Cart False
11 Corn Summer,Fall Corn Seeds Summer,Fall Pierre's General Store False
12 Cranberries Fall Cranberry Seeds Fall Pierre's General Store False
13 Eggplant Fall Eggplant Seeds Fall Pierre's General Store False
14 Fairy Rose Fall Fairy Seeds Fall Pierre's General Store False
15 Garlic Spring Garlic Seeds Spring Pierre's General Store False
16 Grape Fall Grape Starter Fall Pierre's General Store False
17 Green Bean Spring Bean Starter Spring Pierre's General Store False
18 Hops Summer Hops Starter Summer Pierre's General Store False
19 Hot Pepper Summer Pepper Seeds Summer Pierre's General Store False
20 Kale Spring Kale Seeds Spring Pierre's General Store False
21 Melon Summer Melon Seeds Summer Pierre's General Store False
22 Parsnip Spring Parsnip Seeds Spring Pierre's General Store False
23 Pineapple Summer Pineapple Seeds Summer Island Trader True
24 Poppy Summer Poppy Seeds Summer Pierre's General Store False
25 Potato Spring Potato Seeds Spring Pierre's General Store False
26 Qi Fruit Spring,Summer,Fall,Winter Qi Bean Spring,Summer,Fall,Winter Qi's Walnut Room True
27 Pumpkin Fall Pumpkin Seeds Fall Pierre's General Store False
28 Radish Summer Radish Seeds Summer Pierre's General Store False
29 Red Cabbage Summer Red Cabbage Seeds Summer Pierre's General Store False
30 Rhubarb Spring Rhubarb Seeds Spring Oasis False
31 Starfruit Summer Starfruit Seeds Summer Oasis False
32 Strawberry Spring Strawberry Seeds Spring Pierre's General Store False
33 Summer Spangle Summer Spangle Seeds Summer Pierre's General Store False
34 Sunflower Summer,Fall Sunflower Seeds Summer,Fall Pierre's General Store False
35 Sweet Gem Berry Fall Rare Seed Spring,Summer Traveling Cart False
36 Taro Root Summer Taro Tuber Summer Island Trader True
37 Tomato Summer Tomato Seeds Summer Pierre's General Store False
38 Tulip Spring Tulip Bulb Spring Pierre's General Store False
39 Unmilled Rice Spring Rice Shoot Spring Pierre's General Store False
40 Wheat Summer,Fall Wheat Seeds Summer,Fall Pierre's General Store False
41 Yam Fall Yam Seeds Fall Pierre's General Store False

View File

@ -1,50 +0,0 @@
from dataclasses import dataclass
from typing import Tuple
from .. import data
@dataclass(frozen=True)
class SeedItem:
name: str
seasons: Tuple[str]
regions: Tuple[str]
requires_island: bool
@dataclass(frozen=True)
class CropItem:
name: str
farm_growth_seasons: Tuple[str]
seed: SeedItem
def load_crop_csv():
import csv
try:
from importlib.resources import files
except ImportError:
from importlib_resources import files # noqa
with files(data).joinpath("crops.csv").open() as file:
reader = csv.DictReader(file)
crops = []
seeds = []
for item in reader:
seeds.append(SeedItem(item["seed"],
tuple(season for season in item["seed_seasons"].split(","))
if item["seed_seasons"] else tuple(),
tuple(region for region in item["seed_regions"].split(","))
if item["seed_regions"] else tuple(),
item["requires_island"] == "True"))
crops.append(CropItem(item["crop"],
tuple(season for season in item["farm_growth_seasons"].split(","))
if item["farm_growth_seasons"] else tuple(),
seeds[-1]))
return crops, seeds
# TODO Those two should probably be split to we can include rest of seeds
all_crops, all_purchasable_seeds = load_crop_csv()
crops_by_name = {crop.name: crop for crop in all_crops}

View File

@ -1,10 +1,10 @@
from dataclasses import dataclass
from typing import List, Tuple, Union, Optional, Set
from typing import Tuple, Union, Optional
from . import season_data as season
from ..strings.fish_names import Fish, SVEFish, DistantLandsFish
from ..strings.region_names import Region, SVERegion
from ..mods.mod_data import ModNames
from ..strings.fish_names import Fish, SVEFish, DistantLandsFish
from ..strings.region_names import Region, SVERegion, LogicRegion
@dataclass(frozen=True)
@ -30,6 +30,7 @@ town_river = (Region.town,)
mountain_lake = (Region.mountain,)
forest_pond = (Region.forest,)
forest_river = (Region.forest,)
forest_waterfall = (LogicRegion.forest_waterfall,)
secret_woods = (Region.secret_woods,)
mines_floor_20 = (Region.mines_floor_20,)
mines_floor_60 = (Region.mines_floor_60,)
@ -50,8 +51,6 @@ sprite_spring = (SVERegion.sprite_spring,)
fable_reef = (SVERegion.fable_reef,)
vineyard = (SVERegion.blue_moon_vineyard,)
all_fish: List[FishItem] = []
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:
@ -59,63 +58,63 @@ def create_fish(name: str, locations: Tuple[str, ...], seasons: Union[str, Tuple
seasons = (seasons,)
fish_item = FishItem(name, locations, seasons, difficulty, legendary, extended_family, mod_name)
all_fish.append(fish_item)
return fish_item
albacore = create_fish("Albacore", ocean, (season.fall, season.winter), 60)
anchovy = create_fish("Anchovy", ocean, (season.spring, season.fall), 30)
blue_discus = create_fish("Blue Discus", ginger_island_river, season.all_seasons, 60)
bream = create_fish("Bream", town_river + forest_river, season.all_seasons, 35)
bullhead = create_fish("Bullhead", mountain_lake, season.all_seasons, 46)
albacore = create_fish(Fish.albacore, ocean, (season.fall, season.winter), 60)
anchovy = create_fish(Fish.anchovy, ocean, (season.spring, season.fall), 30)
blue_discus = create_fish(Fish.blue_discus, ginger_island_river, season.all_seasons, 60)
bream = create_fish(Fish.bream, town_river + forest_river, season.all_seasons, 35)
bullhead = create_fish(Fish.bullhead, mountain_lake, season.all_seasons, 46)
carp = create_fish(Fish.carp, mountain_lake + secret_woods + sewers + mutant_bug_lair, season.not_winter, 15)
catfish = create_fish("Catfish", town_river + forest_river + secret_woods, (season.spring, season.fall), 75)
chub = create_fish("Chub", forest_river + mountain_lake, season.all_seasons, 35)
dorado = create_fish("Dorado", forest_river, season.summer, 78)
eel = create_fish("Eel", ocean, (season.spring, season.fall), 70)
flounder = create_fish("Flounder", ocean, (season.spring, season.summer), 50)
ghostfish = create_fish("Ghostfish", mines_floor_20 + mines_floor_60, season.all_seasons, 50)
halibut = create_fish("Halibut", ocean, season.not_fall, 50)
herring = create_fish("Herring", ocean, (season.spring, season.winter), 25)
ice_pip = create_fish("Ice Pip", mines_floor_60, season.all_seasons, 85)
largemouth_bass = create_fish("Largemouth Bass", mountain_lake, season.all_seasons, 50)
lava_eel = create_fish("Lava Eel", mines_floor_100, season.all_seasons, 90)
lingcod = create_fish("Lingcod", town_river + forest_river + mountain_lake, season.winter, 85)
lionfish = create_fish("Lionfish", ginger_island_ocean, season.all_seasons, 50)
midnight_carp = create_fish("Midnight Carp", mountain_lake + forest_pond + ginger_island_river,
catfish = create_fish(Fish.catfish, town_river + forest_river + secret_woods, (season.spring, season.fall), 75)
chub = create_fish(Fish.chub, forest_river + mountain_lake, season.all_seasons, 35)
dorado = create_fish(Fish.dorado, forest_river, season.summer, 78)
eel = create_fish(Fish.eel, ocean, (season.spring, season.fall), 70)
flounder = create_fish(Fish.flounder, ocean, (season.spring, season.summer), 50)
ghostfish = create_fish(Fish.ghostfish, mines_floor_20 + mines_floor_60, season.all_seasons, 50)
goby = create_fish(Fish.goby, forest_waterfall, season.all_seasons, 55)
halibut = create_fish(Fish.halibut, ocean, season.not_fall, 50)
herring = create_fish(Fish.herring, ocean, (season.spring, season.winter), 25)
ice_pip = create_fish(Fish.ice_pip, mines_floor_60, season.all_seasons, 85)
largemouth_bass = create_fish(Fish.largemouth_bass, mountain_lake, season.all_seasons, 50)
lava_eel = create_fish(Fish.lava_eel, mines_floor_100, season.all_seasons, 90)
lingcod = create_fish(Fish.lingcod, town_river + forest_river + mountain_lake, season.winter, 85)
lionfish = create_fish(Fish.lionfish, ginger_island_ocean, season.all_seasons, 50)
midnight_carp = create_fish(Fish.midnight_carp, mountain_lake + forest_pond + ginger_island_river,
(season.fall, season.winter), 55)
octopus = create_fish("Octopus", ocean, season.summer, 95)
perch = create_fish("Perch", town_river + forest_river + forest_pond + mountain_lake, season.winter, 35)
pike = create_fish("Pike", town_river + forest_river + forest_pond, (season.summer, season.winter), 60)
pufferfish = create_fish("Pufferfish", ocean + ginger_island_ocean, season.summer, 80)
rainbow_trout = create_fish("Rainbow Trout", town_river + forest_river + mountain_lake, season.summer, 45)
red_mullet = create_fish("Red Mullet", ocean, (season.summer, season.winter), 55)
red_snapper = create_fish("Red Snapper", ocean, (season.summer, season.fall), 40)
salmon = create_fish("Salmon", town_river + forest_river, season.fall, 50)
sandfish = create_fish("Sandfish", desert, season.all_seasons, 65)
sardine = create_fish("Sardine", ocean, (season.spring, season.fall, season.winter), 30)
scorpion_carp = create_fish("Scorpion Carp", desert, season.all_seasons, 90)
sea_cucumber = create_fish("Sea Cucumber", ocean, (season.fall, season.winter), 40)
shad = create_fish("Shad", town_river + forest_river, season.not_winter, 45)
slimejack = create_fish("Slimejack", mutant_bug_lair, season.all_seasons, 55)
smallmouth_bass = create_fish("Smallmouth Bass", town_river + forest_river, (season.spring, season.fall), 28)
squid = create_fish("Squid", ocean, season.winter, 75)
stingray = create_fish("Stingray", pirate_cove, season.all_seasons, 80)
stonefish = create_fish("Stonefish", mines_floor_20, season.all_seasons, 65)
sturgeon = create_fish("Sturgeon", mountain_lake, (season.summer, season.winter), 78)
sunfish = create_fish("Sunfish", town_river + forest_river, (season.spring, season.summer), 30)
super_cucumber = create_fish("Super Cucumber", ocean + ginger_island_ocean, (season.summer, season.fall), 80)
tiger_trout = create_fish("Tiger Trout", town_river + forest_river, (season.fall, season.winter), 60)
tilapia = create_fish("Tilapia", ocean + ginger_island_ocean, (season.summer, season.fall), 50)
octopus = create_fish(Fish.octopus, ocean, season.summer, 95)
perch = create_fish(Fish.perch, town_river + forest_river + forest_pond + mountain_lake, season.winter, 35)
pike = create_fish(Fish.pike, town_river + forest_river + forest_pond, (season.summer, season.winter), 60)
pufferfish = create_fish(Fish.pufferfish, ocean + ginger_island_ocean, season.summer, 80)
rainbow_trout = create_fish(Fish.rainbow_trout, town_river + forest_river + mountain_lake, season.summer, 45)
red_mullet = create_fish(Fish.red_mullet, ocean, (season.summer, season.winter), 55)
red_snapper = create_fish(Fish.red_snapper, ocean, (season.summer, season.fall), 40)
salmon = create_fish(Fish.salmon, town_river + forest_river, season.fall, 50)
sandfish = create_fish(Fish.sandfish, desert, season.all_seasons, 65)
sardine = create_fish(Fish.sardine, ocean, (season.spring, season.fall, season.winter), 30)
scorpion_carp = create_fish(Fish.scorpion_carp, desert, season.all_seasons, 90)
sea_cucumber = create_fish(Fish.sea_cucumber, ocean, (season.fall, season.winter), 40)
shad = create_fish(Fish.shad, town_river + forest_river, season.not_winter, 45)
slimejack = create_fish(Fish.slimejack, mutant_bug_lair, season.all_seasons, 55)
smallmouth_bass = create_fish(Fish.smallmouth_bass, town_river + forest_river, (season.spring, season.fall), 28)
squid = create_fish(Fish.squid, ocean, season.winter, 75)
stingray = create_fish(Fish.stingray, pirate_cove, season.all_seasons, 80)
stonefish = create_fish(Fish.stonefish, mines_floor_20, season.all_seasons, 65)
sturgeon = create_fish(Fish.sturgeon, mountain_lake, (season.summer, season.winter), 78)
sunfish = create_fish(Fish.sunfish, town_river + forest_river, (season.spring, season.summer), 30)
super_cucumber = create_fish(Fish.super_cucumber, ocean + ginger_island_ocean, (season.summer, season.fall), 80)
tiger_trout = create_fish(Fish.tiger_trout, town_river + forest_river, (season.fall, season.winter), 60)
tilapia = create_fish(Fish.tilapia, ocean + ginger_island_ocean, (season.summer, season.fall), 50)
# Tuna has different seasons on ginger island. Should be changed when the whole fish thing is refactored
tuna = create_fish("Tuna", ocean + ginger_island_ocean, (season.summer, season.winter), 70)
void_salmon = create_fish("Void Salmon", witch_swamp, season.all_seasons, 80)
walleye = create_fish("Walleye", town_river + forest_river + forest_pond + mountain_lake, season.fall, 45)
woodskip = create_fish("Woodskip", secret_woods, season.all_seasons, 50)
tuna = create_fish(Fish.tuna, ocean + ginger_island_ocean, (season.summer, season.winter), 70)
void_salmon = create_fish(Fish.void_salmon, witch_swamp, season.all_seasons, 80)
walleye = create_fish(Fish.walleye, town_river + forest_river + forest_pond + mountain_lake, season.fall, 45)
woodskip = create_fish(Fish.woodskip, secret_woods, season.all_seasons, 50)
blob_fish = create_fish("Blobfish", night_market, season.winter, 75)
midnight_squid = create_fish("Midnight Squid", night_market, season.winter, 55)
spook_fish = create_fish("Spook Fish", night_market, season.winter, 60)
blobfish = create_fish(Fish.blobfish, night_market, season.winter, 75)
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, ocean, season.summer, 95, True, False)
@ -155,37 +154,21 @@ undeadfish = create_fish(SVEFish.undeadfish, crimson_badlands, season.all_season
void_eel = create_fish(SVEFish.void_eel, witch_swamp, season.all_seasons, 100, mod_name=ModNames.sve)
water_grub = create_fish(SVEFish.water_grub, mutant_bug_lair, season.all_seasons, 60, mod_name=ModNames.sve)
sea_sponge = create_fish(SVEFish.sea_sponge, ginger_island_ocean, season.all_seasons, 40, mod_name=ModNames.sve)
dulse_seaweed = create_fish(SVEFish.dulse_seaweed, vineyard, season.all_seasons, 50, mod_name=ModNames.sve)
void_minnow = create_fish(DistantLandsFish.void_minnow, witch_swamp, season.all_seasons, 15, mod_name=ModNames.distant_lands)
purple_algae = create_fish(DistantLandsFish.purple_algae, witch_swamp, season.all_seasons, 15, mod_name=ModNames.distant_lands)
swamp_leech = create_fish(DistantLandsFish.swamp_leech, witch_swamp, season.all_seasons, 15, mod_name=ModNames.distant_lands)
giant_horsehoe_crab = create_fish(DistantLandsFish.giant_horsehoe_crab, witch_swamp, season.all_seasons, 90, mod_name=ModNames.distant_lands)
clam = create_fish(Fish.clam, ocean, season.all_seasons, -1)
cockle = create_fish(Fish.cockle, ocean, season.all_seasons, -1)
crab = create_fish(Fish.crab, ocean, season.all_seasons, -1)
crayfish = create_fish(Fish.crayfish, fresh_water, season.all_seasons, -1)
lobster = create_fish(Fish.lobster, ocean, season.all_seasons, -1)
mussel = create_fish(Fish.mussel, ocean, season.all_seasons, -1)
oyster = create_fish(Fish.oyster, ocean, season.all_seasons, -1)
periwinkle = create_fish(Fish.periwinkle, fresh_water, season.all_seasons, -1)
shrimp = create_fish(Fish.shrimp, ocean, season.all_seasons, -1)
snail = create_fish(Fish.snail, fresh_water, season.all_seasons, -1)
clam = create_fish("Clam", ocean, season.all_seasons, -1)
cockle = create_fish("Cockle", ocean, season.all_seasons, -1)
crab = create_fish("Crab", ocean, season.all_seasons, -1)
crayfish = create_fish("Crayfish", fresh_water, season.all_seasons, -1)
lobster = create_fish("Lobster", ocean, season.all_seasons, -1)
mussel = create_fish("Mussel", ocean, season.all_seasons, -1)
oyster = create_fish("Oyster", ocean, season.all_seasons, -1)
periwinkle = create_fish("Periwinkle", fresh_water, season.all_seasons, -1)
shrimp = create_fish("Shrimp", ocean, season.all_seasons, -1)
snail = create_fish("Snail", fresh_water, season.all_seasons, -1)
legendary_fish = [angler, crimsonfish, glacierfish, legend, mutant_carp]
extended_family = [ms_angler, son_of_crimsonfish, glacierfish_jr, legend_ii, radioactive_carp]
special_fish = [*legendary_fish, blob_fish, lava_eel, octopus, scorpion_carp, ice_pip, super_cucumber, dorado]
island_fish = [lionfish, blue_discus, stingray, *extended_family]
all_fish_by_name = {fish.name: fish for fish in all_fish}
def get_fish_for_mods(mods: Set[str]) -> List[FishItem]:
fish_for_mods = []
for fish in all_fish:
if fish.mod_name and fish.mod_name not in mods:
continue
fish_for_mods.append(fish)
return fish_for_mods
vanilla_legendary_fish = [angler, crimsonfish, glacierfish, legend, mutant_carp]

View File

@ -0,0 +1,86 @@
import enum
import sys
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 ..stardew_rule.protocol import StardewRule
if sys.version_info >= (3, 10):
kw_only = {"kw_only": True}
else:
kw_only = {}
DEFAULT_REQUIREMENT_TAGS = MappingProxyType({})
@dataclass(frozen=True)
class Requirement(ABC):
...
class ItemTag(enum.Enum):
CROPSANITY_SEED = enum.auto()
CROPSANITY = enum.auto()
FISH = enum.auto()
FRUIT = enum.auto()
VEGETABLE = enum.auto()
EDIBLE_MUSHROOM = enum.auto()
BOOK = enum.auto()
BOOK_POWER = enum.auto()
BOOK_SKILL = enum.auto()
@dataclass(frozen=True)
class ItemSource(ABC):
add_tags: ClassVar[Tuple[ItemTag]] = ()
@property
def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]:
return DEFAULT_REQUIREMENT_TAGS
# FIXME this should just be an optional field, but kw_only requires python 3.10...
@property
def other_requirements(self) -> Iterable[Requirement]:
return ()
@dataclass(frozen=True, **kw_only)
class GenericSource(ItemSource):
regions: Tuple[str, ...] = ()
"""No region means it's available everywhere."""
other_requirements: Tuple[Requirement, ...] = ()
@dataclass(frozen=True)
class CustomRuleSource(ItemSource):
"""Hopefully once everything is migrated to sources, we won't need these custom logic anymore."""
create_rule: Callable[[Any], StardewRule]
class Tag(ItemSource):
"""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, ...]
def __init__(self, *tag: ItemTag):
self.tag = tag # noqa
@property
def add_tags(self):
return self.tag
@dataclass(frozen=True)
class GameItem:
name: str
sources: List[ItemSource] = field(default_factory=list)
tags: Set[ItemTag] = field(default_factory=set)
def add_sources(self, sources: Iterable[ItemSource]):
self.sources.extend(source for source in sources if type(source) is not Tag)
for source in sources:
self.add_tags(source.add_tags)
def add_tags(self, tags: Iterable[ItemTag]):
self.tags.update(tags)

View File

@ -0,0 +1,66 @@
from dataclasses import dataclass
from typing import Tuple, Sequence, Mapping
from .game_item import ItemSource, kw_only, ItemTag, Requirement
from ..strings.season_names import Season
@dataclass(frozen=True, **kw_only)
class ForagingSource(ItemSource):
regions: Tuple[str, ...]
seasons: Tuple[str, ...] = Season.all
other_requirements: Tuple[Requirement, ...] = ()
@dataclass(frozen=True, **kw_only)
class SeasonalForagingSource(ItemSource):
season: str
days: Sequence[int]
regions: Tuple[str, ...]
def as_foraging_source(self) -> ForagingSource:
return ForagingSource(seasons=(self.season,), regions=self.regions)
@dataclass(frozen=True, **kw_only)
class FruitBatsSource(ItemSource):
...
@dataclass(frozen=True, **kw_only)
class MushroomCaveSource(ItemSource):
...
@dataclass(frozen=True, **kw_only)
class HarvestFruitTreeSource(ItemSource):
add_tags = (ItemTag.CROPSANITY,)
sapling: str
seasons: Tuple[str, ...] = Season.all
@property
def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]:
return {
self.sapling: (ItemTag.CROPSANITY_SEED,)
}
@dataclass(frozen=True, **kw_only)
class HarvestCropSource(ItemSource):
add_tags = (ItemTag.CROPSANITY,)
seed: str
seasons: Tuple[str, ...] = Season.all
"""Empty means it can't be grown on the farm."""
@property
def requirement_tags(self) -> Mapping[str, Tuple[ItemTag, ...]]:
return {
self.seed: (ItemTag.CROPSANITY_SEED,)
}
@dataclass(frozen=True, **kw_only)
class ArtifactSpotSource(ItemSource):
amount: int

View File

@ -54,7 +54,7 @@ id,name,classification,groups,mod_name
68,Progressive Watering Can,progression,PROGRESSIVE_TOOLS,
69,Progressive Trash Can,progression,PROGRESSIVE_TOOLS,
70,Progressive Fishing Rod,progression,PROGRESSIVE_TOOLS,
71,Golden Scythe,useful,,
71,Golden Scythe,useful,DEPRECATED,
72,Progressive Mine Elevator,progression,,
73,Farming Level,progression,SKILL_LEVEL_UP,
74,Fishing Level,progression,SKILL_LEVEL_UP,
@ -92,8 +92,8 @@ id,name,classification,groups,mod_name
106,Galaxy Sword,filler,"WEAPON,DEPRECATED",
107,Galaxy Dagger,filler,"WEAPON,DEPRECATED",
108,Galaxy Hammer,filler,"WEAPON,DEPRECATED",
109,Movement Speed Bonus,progression,,
110,Luck Bonus,progression,,
109,Movement Speed Bonus,useful,,
110,Luck Bonus,filler,PLAYER_BUFF,
111,Lava Katana,filler,"WEAPON,DEPRECATED",
112,Progressive House,progression,,
113,Traveling Merchant: Sunday,progression,TRAVELING_MERCHANT_DAY,
@ -104,7 +104,7 @@ id,name,classification,groups,mod_name
118,Traveling Merchant: Friday,progression,TRAVELING_MERCHANT_DAY,
119,Traveling Merchant: Saturday,progression,TRAVELING_MERCHANT_DAY,
120,Traveling Merchant Stock Size,useful,,
121,Traveling Merchant Discount,useful,,
121,Traveling Merchant Discount,useful,DEPRECATED,
122,Return Scepter,useful,,
123,Progressive Season,progression,,
124,Spring,progression,SEASON,
@ -398,6 +398,7 @@ id,name,classification,groups,mod_name
417,Tropical Curry Recipe,progression,"CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE",
418,Trout Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
419,Vegetable Medley Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
420,Moss Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
425,Gate Recipe,progression,CRAFTSANITY,
426,Wood Fence Recipe,progression,CRAFTSANITY,
427,Deluxe Retaining Soil Recipe,progression,"CRAFTSANITY,GINGER_ISLAND",
@ -430,7 +431,7 @@ id,name,classification,groups,mod_name
454,Marble Brazier Recipe,progression,CRAFTSANITY,
455,Wood Lamp-post Recipe,progression,CRAFTSANITY,
456,Iron Lamp-post Recipe,progression,CRAFTSANITY,
457,Furnace Recipe,progression,CRAFTSANITY,
457,Furnace Recipe,progression,"CRAFTSANITY",
458,Wicked Statue Recipe,progression,CRAFTSANITY,
459,Chest Recipe,progression,CRAFTSANITY,
460,Wood Sign Recipe,progression,CRAFTSANITY,
@ -439,6 +440,75 @@ id,name,classification,groups,mod_name
470,Fruit Bats,progression,,
471,Mushroom Boxes,progression,,
475,The Gateway Gazette,progression,TV_CHANNEL,
476,Carrot Seeds,progression,CROPSANITY,
477,Summer Squash Seeds,progression,CROPSANITY,
478,Broccoli Seeds,progression,CROPSANITY,
479,Powdermelon Seeds,progression,CROPSANITY,
480,Progressive Raccoon,progression,,
481,Farming Mastery,progression,SKILL_MASTERY,
482,Mining Mastery,progression,SKILL_MASTERY,
483,Foraging Mastery,progression,SKILL_MASTERY,
484,Fishing Mastery,progression,SKILL_MASTERY,
485,Combat Mastery,progression,SKILL_MASTERY,
486,Fish Smoker Recipe,progression,CRAFTSANITY,
487,Dehydrator Recipe,progression,CRAFTSANITY,
488,Big Chest Recipe,progression,CRAFTSANITY,
489,Big Stone Chest Recipe,progression,CRAFTSANITY,
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,,
494,Progressive Pan,progression,PROGRESSIVE_TOOLS,
495,Calico Statue,filler,FESTIVAL,
496,Mummy Mask,filler,FESTIVAL,
497,Free Cactis,filler,FESTIVAL,
498,Gil's Hat,filler,FESTIVAL,
499,Bucket Hat,filler,FESTIVAL,
500,Mounted Trout,filler,FESTIVAL,
501,'Squid Kid',filler,FESTIVAL,
502,Squid Hat,filler,FESTIVAL,
503,Resource Pack: 200 Calico Egg,useful,"FESTIVAL",
504,Resource Pack: 120 Calico Egg,useful,"FESTIVAL",
505,Resource Pack: 100 Calico Egg,useful,"FESTIVAL",
506,Resource Pack: 50 Calico Egg,useful,"FESTIVAL",
507,Resource Pack: 40 Calico Egg,useful,"FESTIVAL",
508,Resource Pack: 35 Calico Egg,useful,"FESTIVAL",
509,Resource Pack: 30 Calico Egg,useful,"FESTIVAL",
510,Book: The Art O' Crabbing,useful,"FESTIVAL",
511,Mr Qi's Plane Ride,progression,,
521,Power: Price Catalogue,useful,"BOOK_POWER",
522,Power: Mapping Cave Systems,useful,"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",
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",
541,Progressive Lost Book,progression,"LOST_BOOK",
551,Golden Walnut,progression,"RESOURCE_PACK,GINGER_ISLAND",
552,3 Golden Walnuts,progression,"GINGER_ISLAND",
553,5 Golden Walnuts,progression,"GINGER_ISLAND",
554,Damage Bonus,filler,PLAYER_BUFF,
555,Defense Bonus,filler,PLAYER_BUFF,
556,Immunity Bonus,filler,PLAYER_BUFF,
557,Health Bonus,filler,PLAYER_BUFF,
558,Energy Bonus,filler,PLAYER_BUFF,
559,Bite Rate Bonus,filler,PLAYER_BUFF,
560,Fish Trap Bonus,filler,PLAYER_BUFF,
561,Fishing Bar Size Bonus,filler,PLAYER_BUFF,
562,Quality Bonus,filler,PLAYER_BUFF,
563,Glow Bonus,filler,PLAYER_BUFF,
4001,Burnt Trap,trap,TRAP,
4002,Darkness Trap,trap,TRAP,
4003,Frozen Trap,trap,TRAP,
@ -464,6 +534,8 @@ id,name,classification,groups,mod_name
4023,Benjamin Budton Trap,trap,TRAP,
4024,Inflation Trap,trap,TRAP,
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",
@ -701,9 +773,9 @@ id,name,classification,groups,mod_name
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",
5245,Golden Walnut,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,GINGER_ISLAND",
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",
@ -726,6 +798,27 @@ id,name,classification,groups,mod_name
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",
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
@ -802,11 +895,16 @@ id,name,classification,groups,mod_name
10409,Void Delight Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10410,Void Salmon Sushi Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10411,Mushroom Kebab Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul
10412,Crayfish Soup Recipe,progression,,Distant Lands - Witch Swamp Overhaul
10412,Crayfish Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul
10413,Pemmican Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul
10414,Void Mint Tea Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul
10415,Ginger Tincture Recipe,progression,GINGER_ISLAND,Distant Lands - Witch Swamp Overhaul
10416,Special Pumpkin Soup Recipe,progression,,Boarding House and Bus Stop Extension
10415,Ginger Tincture Recipe,progression,"GINGER_ISLAND,CHEFSANITY,CHEFSANITY_FRIENDSHIP",Distant Lands - Witch Swamp Overhaul
10416,Special Pumpkin Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",Boarding House and Bus Stop Extension
10417,Rocky Root Coffee Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology
10418,Digger's Delight Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology
10419,Ancient Jello Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",Archaeology
10420,Grilled Cheese Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",Binning Skill
10421,Fish Casserole Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",Binning Skill
10450,Void Mint Seeds,progression,DEPRECATED,Distant Lands - Witch Swamp Overhaul
10451,Vile Ancient Fruit Seeds,progression,DEPRECATED,Distant Lands - Witch Swamp Overhaul
10501,Marlon's Boat Paddle,progression,GINGER_ISLAND,Stardew Valley Expanded
@ -850,10 +948,15 @@ id,name,classification,groups,mod_name
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,Starde Valley Expanded
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

1 id name classification groups mod_name
54 68 Progressive Watering Can progression PROGRESSIVE_TOOLS
55 69 Progressive Trash Can progression PROGRESSIVE_TOOLS
56 70 Progressive Fishing Rod progression PROGRESSIVE_TOOLS
57 71 Golden Scythe useful DEPRECATED
58 72 Progressive Mine Elevator progression
59 73 Farming Level progression SKILL_LEVEL_UP
60 74 Fishing Level progression SKILL_LEVEL_UP
92 106 Galaxy Sword filler WEAPON,DEPRECATED
93 107 Galaxy Dagger filler WEAPON,DEPRECATED
94 108 Galaxy Hammer filler WEAPON,DEPRECATED
95 109 Movement Speed Bonus progression useful
96 110 Luck Bonus progression filler PLAYER_BUFF
97 111 Lava Katana filler WEAPON,DEPRECATED
98 112 Progressive House progression
99 113 Traveling Merchant: Sunday progression TRAVELING_MERCHANT_DAY
104 118 Traveling Merchant: Friday progression TRAVELING_MERCHANT_DAY
105 119 Traveling Merchant: Saturday progression TRAVELING_MERCHANT_DAY
106 120 Traveling Merchant Stock Size useful
107 121 Traveling Merchant Discount useful DEPRECATED
108 122 Return Scepter useful
109 123 Progressive Season progression
110 124 Spring progression SEASON
398 417 Tropical Curry Recipe progression CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE
399 418 Trout Soup Recipe progression CHEFSANITY,CHEFSANITY_QOS
400 419 Vegetable Medley Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
401 420 Moss Soup Recipe progression CHEFSANITY,CHEFSANITY_SKILL
402 425 Gate Recipe progression CRAFTSANITY
403 426 Wood Fence Recipe progression CRAFTSANITY
404 427 Deluxe Retaining Soil Recipe progression CRAFTSANITY,GINGER_ISLAND
431 454 Marble Brazier Recipe progression CRAFTSANITY
432 455 Wood Lamp-post Recipe progression CRAFTSANITY
433 456 Iron Lamp-post Recipe progression CRAFTSANITY
434 457 Furnace Recipe progression CRAFTSANITY
435 458 Wicked Statue Recipe progression CRAFTSANITY
436 459 Chest Recipe progression CRAFTSANITY
437 460 Wood Sign Recipe progression CRAFTSANITY
440 470 Fruit Bats progression
441 471 Mushroom Boxes progression
442 475 The Gateway Gazette progression TV_CHANNEL
443 476 Carrot Seeds progression CROPSANITY
444 477 Summer Squash Seeds progression CROPSANITY
445 478 Broccoli Seeds progression CROPSANITY
446 479 Powdermelon Seeds progression CROPSANITY
447 480 Progressive Raccoon progression
448 481 Farming Mastery progression SKILL_MASTERY
449 482 Mining Mastery progression SKILL_MASTERY
450 483 Foraging Mastery progression SKILL_MASTERY
451 484 Fishing Mastery progression SKILL_MASTERY
452 485 Combat Mastery progression SKILL_MASTERY
453 486 Fish Smoker Recipe progression CRAFTSANITY
454 487 Dehydrator Recipe progression CRAFTSANITY
455 488 Big Chest Recipe progression CRAFTSANITY
456 489 Big Stone Chest Recipe progression CRAFTSANITY
457 490 Text Sign Recipe progression CRAFTSANITY
458 491 Blue Grass Starter Recipe progression QI_CRAFTING_RECIPE,GINGER_ISLAND
459 492 Mastery Of The Five Ways progression SKILL_MASTERY
460 493 Progressive Scythe useful
461 494 Progressive Pan progression PROGRESSIVE_TOOLS
462 495 Calico Statue filler FESTIVAL
463 496 Mummy Mask filler FESTIVAL
464 497 Free Cactis filler FESTIVAL
465 498 Gil's Hat filler FESTIVAL
466 499 Bucket Hat filler FESTIVAL
467 500 Mounted Trout filler FESTIVAL
468 501 'Squid Kid' filler FESTIVAL
469 502 Squid Hat filler FESTIVAL
470 503 Resource Pack: 200 Calico Egg useful FESTIVAL
471 504 Resource Pack: 120 Calico Egg useful FESTIVAL
472 505 Resource Pack: 100 Calico Egg useful FESTIVAL
473 506 Resource Pack: 50 Calico Egg useful FESTIVAL
474 507 Resource Pack: 40 Calico Egg useful FESTIVAL
475 508 Resource Pack: 35 Calico Egg useful FESTIVAL
476 509 Resource Pack: 30 Calico Egg useful FESTIVAL
477 510 Book: The Art O' Crabbing useful FESTIVAL
478 511 Mr Qi's Plane Ride progression
479 521 Power: Price Catalogue useful BOOK_POWER
480 522 Power: Mapping Cave Systems useful BOOK_POWER
481 523 Power: Way Of The Wind pt. 1 progression BOOK_POWER
482 524 Power: Way Of The Wind pt. 2 useful BOOK_POWER
483 525 Power: Monster Compendium useful BOOK_POWER
484 526 Power: Friendship 101 useful BOOK_POWER
485 527 Power: Jack Be Nimble, Jack Be Thick useful BOOK_POWER
486 528 Power: Woody's Secret useful BOOK_POWER
487 529 Power: Raccoon Journal useful BOOK_POWER
488 530 Power: Jewels Of The Sea useful BOOK_POWER
489 531 Power: Dwarvish Safety Manual useful BOOK_POWER
490 532 Power: The Art O' Crabbing useful BOOK_POWER
491 533 Power: The Alleyway Buffet useful BOOK_POWER
492 534 Power: The Diamond Hunter useful BOOK_POWER
493 535 Power: Book of Mysteries progression BOOK_POWER
494 536 Power: Horse: The Book useful BOOK_POWER
495 537 Power: Treasure Appraisal Guide useful BOOK_POWER
496 538 Power: Ol' Slitherlegs useful BOOK_POWER
497 539 Power: Animal Catalogue useful BOOK_POWER
498 541 Progressive Lost Book progression LOST_BOOK
499 551 Golden Walnut progression RESOURCE_PACK,GINGER_ISLAND
500 552 3 Golden Walnuts progression GINGER_ISLAND
501 553 5 Golden Walnuts progression GINGER_ISLAND
502 554 Damage Bonus filler PLAYER_BUFF
503 555 Defense Bonus filler PLAYER_BUFF
504 556 Immunity Bonus filler PLAYER_BUFF
505 557 Health Bonus filler PLAYER_BUFF
506 558 Energy Bonus filler PLAYER_BUFF
507 559 Bite Rate Bonus filler PLAYER_BUFF
508 560 Fish Trap Bonus filler PLAYER_BUFF
509 561 Fishing Bar Size Bonus filler PLAYER_BUFF
510 562 Quality Bonus filler PLAYER_BUFF
511 563 Glow Bonus filler PLAYER_BUFF
512 4001 Burnt Trap trap TRAP
513 4002 Darkness Trap trap TRAP
514 4003 Frozen Trap trap TRAP
534 4023 Benjamin Budton Trap trap TRAP
535 4024 Inflation Trap trap TRAP
536 4025 Bomb Trap trap TRAP
537 4026 Nudge Trap trap TRAP
538 4501 Deflation Bonus filler
539 5000 Resource Pack: 500 Money useful BASE_RESOURCE,RESOURCE_PACK
540 5001 Resource Pack: 1000 Money useful BASE_RESOURCE,RESOURCE_PACK
541 5002 Resource Pack: 1500 Money useful BASE_RESOURCE,RESOURCE_PACK
773 5234 Resource Pack: 10 Qi Seasoning filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
774 5235 Mr. Qi's Hat filler MAXIMUM_ONE,RESOURCE_PACK
775 5236 Aquatic Sanctuary filler RESOURCE_PACK
776 5237 Leprechaun Hat filler MAXIMUM_ONE,RESOURCE_PACK
777 5242 Exotic Double Bed filler RESOURCE_PACK
778 5243 Resource Pack: 2 Qi Gem filler GINGER_ISLAND,RESOURCE_PACK,DEPRECATED
5245 Golden Walnut filler RESOURCE_PACK,RESOURCE_PACK_USEFUL,GINGER_ISLAND
779 5247 Fairy Dust useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
780 5248 Seed Maker useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
781 5249 Keg useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
798 5266 Resource Pack: 5 Staircase filler RESOURCE_PACK
799 5267 Auto-Petter useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
800 5268 Auto-Grabber useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
801 5269 Resource Pack: 10 Calico Egg filler RESOURCE_PACK
802 5270 Resource Pack: 20 Calico Egg filler RESOURCE_PACK
803 5272 Tent Kit useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
804 5273 Resource Pack: 4 Mystery Box filler RESOURCE_PACK
805 5274 Prize Ticket filler RESOURCE_PACK
806 5275 Resource Pack: 20 Deluxe Bait filler RESOURCE_PACK
807 5276 Resource Pack: 2 Triple Shot Espresso filler RESOURCE_PACK
808 5277 Dish O' The Sea filler RESOURCE_PACK
809 5278 Seafoam Pudding useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
810 5279 Trap Bobber useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
811 5280 Treasure Chest filler RESOURCE_PACK
812 5281 Resource Pack: 15 Mixed Seeds filler RESOURCE_PACK
813 5282 Resource Pack: 15 Mixed Flower Seeds filler RESOURCE_PACK
814 5283 Resource Pack: 5 Cherry Bomb filler RESOURCE_PACK
815 5284 Resource Pack: 3 Bomb filler RESOURCE_PACK
816 5285 Resource Pack: 2 Mega Bomb filler RESOURCE_PACK
817 5286 Resource Pack: 2 Life Elixir filler RESOURCE_PACK
818 5287 Resource Pack: 5 Coffee filler RESOURCE_PACK
819 5289 Prismatic Shard filler RESOURCE_PACK
820 5290 Stardrop Tea filler RESOURCE_PACK
821 5291 Resource Pack: 2 Artifact Trove filler RESOURCE_PACK
822 10001 Luck Level progression SKILL_LEVEL_UP Luck Skill
823 10002 Magic Level progression SKILL_LEVEL_UP Magic
824 10003 Socializing Level progression SKILL_LEVEL_UP Socializing Skill
895 10409 Void Delight Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
896 10410 Void Salmon Sushi Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
897 10411 Mushroom Kebab Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
898 10412 Crayfish Soup Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
899 10413 Pemmican Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
900 10414 Void Mint Tea Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
901 10415 Ginger Tincture Recipe progression GINGER_ISLAND GINGER_ISLAND,CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
902 10416 Special Pumpkin Soup Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Boarding House and Bus Stop Extension
903 10417 Rocky Root Coffee Recipe progression CHEFSANITY,CHEFSANITY_SKILL Archaeology
904 10418 Digger's Delight Recipe progression CHEFSANITY,CHEFSANITY_SKILL Archaeology
905 10419 Ancient Jello Recipe progression CHEFSANITY,CHEFSANITY_SKILL Archaeology
906 10420 Grilled Cheese Recipe progression CHEFSANITY,CHEFSANITY_SKILL Binning Skill
907 10421 Fish Casserole Recipe progression CHEFSANITY,CHEFSANITY_SKILL Binning Skill
908 10450 Void Mint Seeds progression DEPRECATED Distant Lands - Witch Swamp Overhaul
909 10451 Vile Ancient Fruit Seeds progression DEPRECATED Distant Lands - Witch Swamp Overhaul
910 10501 Marlon's Boat Paddle progression GINGER_ISLAND Stardew Valley Expanded
948 10707 Resource Pack: 5 Wooden Display filler RESOURCE_PACK Archaeology
949 10708 Grinder filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
950 10709 Ancient Battery Production Station filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
951 10710 Hero Elixir filler RESOURCE_PACK Starde Valley Expanded Stardew Valley Expanded
952 10711 Aegis Elixir filler RESOURCE_PACK Stardew Valley Expanded
953 10712 Haste Elixir filler RESOURCE_PACK Stardew Valley Expanded
954 10713 Lightning Elixir filler RESOURCE_PACK Stardew Valley Expanded
955 10714 Armor Elixir filler RESOURCE_PACK Stardew Valley Expanded
956 10715 Gravity Elixir filler RESOURCE_PACK Stardew Valley Expanded
957 10716 Barbarian Elixir filler RESOURCE_PACK Stardew Valley Expanded
958 10717 Restoration Table filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
959 10718 Trash Bin filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Binning Skill
960 10719 Composter filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Binning Skill
961 10720 Recycling Bin filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Binning Skill
962 10721 Advanced Recycling Machine filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Binning Skill

View File

@ -36,6 +36,7 @@ id,region,name,tags,mod_name
35,Boiler Room,Complete Boiler Room,COMMUNITY_CENTER_ROOM,
36,Bulletin Board,Complete Bulletin Board,COMMUNITY_CENTER_ROOM,
37,Vault,Complete Vault,COMMUNITY_CENTER_ROOM,
38,Crafts Room,Forest Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE",
39,Fish Tank,Deep Fishing Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE",
40,Crafts Room,Beach Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE",
41,Crafts Room,Mines Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE",
@ -78,7 +79,6 @@ id,region,name,tags,mod_name
78,Vault,500g Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
79,Vault,"1,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
80,Vault,"2,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
81,Vault,"5,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
82,Vault,"1,500g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
83,Vault,"3,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
84,Vault,"3,500g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,VAULT_BUNDLE",
@ -124,6 +124,20 @@ id,region,name,tags,mod_name
124,Beach,Bamboo Pole Cutscene,"FISHING_ROD_UPGRADE,TOOL_UPGRADE",
125,Willy's Fish Shop,Purchase Fiberglass Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE",
126,Willy's Fish Shop,Purchase Iridium Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE",
127,Mountain,Copper Pan Cutscene,"TOOL_UPGRADE,PAN_UPGRADE",
128,Blacksmith Iron Upgrades,Iron Pan Upgrade,"TOOL_UPGRADE,PAN_UPGRADE",
129,Blacksmith Gold Upgrades,Gold Pan Upgrade,"TOOL_UPGRADE,PAN_UPGRADE",
130,Blacksmith Iridium Upgrades,Iridium Pan Upgrade,"TOOL_UPGRADE,PAN_UPGRADE",
151,Bulletin Board,Helper's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
152,Bulletin Board,Spirit's Eve Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
153,Bulletin Board,Winter Star Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
154,Bulletin Board,Calico Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
155,Pantry,Sommelier Bundle,"PANTRY_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
156,Pantry,Dry Bundle,"PANTRY_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
157,Fish Tank,Fish Smoker Bundle,"FISH_TANK_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE",
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",
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",
@ -161,18 +175,18 @@ id,region,name,tags,mod_name
235,The Mines - Floor 110,Floor 110 Elevator,ELEVATOR,
236,The Mines - Floor 115,Floor 115 Elevator,ELEVATOR,
237,The Mines - Floor 120,Floor 120 Elevator,ELEVATOR,
250,Shipping,Demetrius's Breakthrough,MANDATORY
250,Shipping,Demetrius's Breakthrough,MANDATORY,
251,Volcano - Floor 10,Volcano Caldera Treasure,"GINGER_ISLAND,MANDATORY",
301,Farming,Level 1 Farming,"FARMING_LEVEL,SKILL_LEVEL",
302,Farming,Level 2 Farming,"FARMING_LEVEL,SKILL_LEVEL",
303,Farming,Level 3 Farming,"FARMING_LEVEL,SKILL_LEVEL",
304,Farming,Level 4 Farming,"FARMING_LEVEL,SKILL_LEVEL",
305,Farming,Level 5 Farming,"FARMING_LEVEL,SKILL_LEVEL",
306,Farming,Level 6 Farming,"FARMING_LEVEL,SKILL_LEVEL",
307,Farming,Level 7 Farming,"FARMING_LEVEL,SKILL_LEVEL",
308,Farming,Level 8 Farming,"FARMING_LEVEL,SKILL_LEVEL",
309,Farming,Level 9 Farming,"FARMING_LEVEL,SKILL_LEVEL",
310,Farming,Level 10 Farming,"FARMING_LEVEL,SKILL_LEVEL",
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",
304,Farm,Level 4 Farming,"FARMING_LEVEL,SKILL_LEVEL",
305,Farm,Level 5 Farming,"FARMING_LEVEL,SKILL_LEVEL",
306,Farm,Level 6 Farming,"FARMING_LEVEL,SKILL_LEVEL",
307,Farm,Level 7 Farming,"FARMING_LEVEL,SKILL_LEVEL",
308,Farm,Level 8 Farming,"FARMING_LEVEL,SKILL_LEVEL",
309,Farm,Level 9 Farming,"FARMING_LEVEL,SKILL_LEVEL",
310,Farm,Level 10 Farming,"FARMING_LEVEL,SKILL_LEVEL",
311,Fishing,Level 1 Fishing,"FISHING_LEVEL,SKILL_LEVEL",
312,Fishing,Level 2 Fishing,"FISHING_LEVEL,SKILL_LEVEL",
313,Fishing,Level 3 Fishing,"FISHING_LEVEL,SKILL_LEVEL",
@ -213,6 +227,11 @@ id,region,name,tags,mod_name
348,The Mines - Floor 90,Level 8 Combat,"COMBAT_LEVEL,SKILL_LEVEL",
349,The Mines - Floor 100,Level 9 Combat,"COMBAT_LEVEL,SKILL_LEVEL",
350,The Mines - Floor 110,Level 10 Combat,"COMBAT_LEVEL,SKILL_LEVEL",
351,Mastery Cave,Farming Mastery,"MASTERY_LEVEL,SKILL_LEVEL",
352,Mastery Cave,Fishing Mastery,"MASTERY_LEVEL,SKILL_LEVEL",
353,Mastery Cave,Foraging Mastery,"MASTERY_LEVEL,SKILL_LEVEL",
354,Mastery Cave,Mining Mastery,"MASTERY_LEVEL,SKILL_LEVEL",
355,Mastery Cave,Combat Mastery,"MASTERY_LEVEL,SKILL_LEVEL",
401,Carpenter Shop,Coop Blueprint,BUILDING_BLUEPRINT,
402,Carpenter Shop,Big Coop Blueprint,BUILDING_BLUEPRINT,
403,Carpenter Shop,Deluxe Coop Blueprint,BUILDING_BLUEPRINT,
@ -279,6 +298,8 @@ id,region,name,tags,mod_name
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",
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",
@ -307,6 +328,7 @@ id,region,name,tags,mod_name
705,Farmhouse,Have Another Baby,BABY,
706,Farmhouse,Spouse Stardrop,,
707,Sewer,Krobus Stardrop,MANDATORY,
708,Forest,Pot Of Gold,MANDATORY,
801,Forest,Help Wanted: Gathering 1,HELP_WANTED,
802,Forest,Help Wanted: Gathering 2,HELP_WANTED,
803,Forest,Help Wanted: Gathering 3,HELP_WANTED,
@ -454,6 +476,7 @@ id,region,name,tags,mod_name
1068,Beach,Fishsanity: Legend II,"FISHSANITY,GINGER_ISLAND,REQUIRES_QI_ORDERS",
1069,Beach,Fishsanity: Ms. Angler,"FISHSANITY,GINGER_ISLAND,REQUIRES_QI_ORDERS",
1070,Beach,Fishsanity: Radioactive Carp,"FISHSANITY,GINGER_ISLAND,REQUIRES_QI_ORDERS",
1071,Fishing,Fishsanity: Goby,FISHSANITY,
1100,Museum,Museumsanity: 5 Donations,MUSEUM_MILESTONES,
1101,Museum,Museumsanity: 10 Donations,MUSEUM_MILESTONES,
1102,Museum,Museumsanity: 15 Donations,MUSEUM_MILESTONES,
@ -1021,6 +1044,57 @@ id,region,name,tags,mod_name
2034,Dance of the Moonlight Jellies,Moonlight Jellies Banner,FESTIVAL,
2035,Dance of the Moonlight Jellies,Starport Decal,FESTIVAL,
2036,Casino,Rarecrow #3 (Alien),FESTIVAL,
2041,Desert Festival,Calico Race,FESTIVAL,
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,
2066,Desert Festival,Free Cactis,FESTIVAL,
2067,Desert Festival,Monster Hunt,FESTIVAL_HARD,
2068,Desert Festival,Deep Dive,FESTIVAL_HARD,
2069,Desert Festival,Treasure Hunt,FESTIVAL_HARD,
2070,Desert Festival,Touch A Calico Statue,FESTIVAL,
2071,Desert Festival,Real Calico Egg Hunter,FESTIVAL,
2072,Desert Festival,Willy's Challenge,FESTIVAL_HARD,
2073,Desert Festival,Desert Scholar,FESTIVAL,
2074,Trout Derby,Trout Derby Reward 1,FESTIVAL,
2075,Trout Derby,Trout Derby Reward 2,FESTIVAL,
2076,Trout Derby,Trout Derby Reward 3,FESTIVAL,
2077,Trout Derby,Trout Derby Reward 4,FESTIVAL_HARD,
2078,Trout Derby,Trout Derby Reward 5,FESTIVAL_HARD,
2079,Trout Derby,Trout Derby Reward 6,FESTIVAL_HARD,
2080,Trout Derby,Trout Derby Reward 7,FESTIVAL_HARD,
2081,Trout Derby,Trout Derby Reward 8,FESTIVAL_HARD,
2082,Trout Derby,Trout Derby Reward 9,FESTIVAL_HARD,
2083,Trout Derby,Trout Derby Reward 10,FESTIVAL_HARD,
2084,SquidFest,SquidFest Day 1 Copper,FESTIVAL,
2085,SquidFest,SquidFest Day 1 Iron,FESTIVAL,
2086,SquidFest,SquidFest Day 1 Gold,FESTIVAL_HARD,
2087,SquidFest,SquidFest Day 1 Iridium,FESTIVAL_HARD,
2088,SquidFest,SquidFest Day 2 Copper,FESTIVAL,
2089,SquidFest,SquidFest Day 2 Iron,FESTIVAL,
2090,SquidFest,SquidFest Day 2 Gold,FESTIVAL_HARD,
2091,SquidFest,SquidFest Day 2 Iridium,FESTIVAL_HARD,
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,
@ -1065,53 +1139,59 @@ id,region,name,tags,mod_name
2214,Island West,Parrot Express,"GINGER_ISLAND,WALNUT_PURCHASE",
2215,Dig Site,Open Professor Snail Cave,GINGER_ISLAND,
2216,Field Office,Complete Island Field Office,GINGER_ISLAND,
2301,Farming,Harvest Amaranth,CROPSANITY,
2302,Farming,Harvest Artichoke,CROPSANITY,
2303,Farming,Harvest Beet,CROPSANITY,
2304,Farming,Harvest Blue Jazz,CROPSANITY,
2305,Farming,Harvest Blueberry,CROPSANITY,
2306,Farming,Harvest Bok Choy,CROPSANITY,
2307,Farming,Harvest Cauliflower,CROPSANITY,
2308,Farming,Harvest Corn,CROPSANITY,
2309,Farming,Harvest Cranberries,CROPSANITY,
2310,Farming,Harvest Eggplant,CROPSANITY,
2311,Farming,Harvest Fairy Rose,CROPSANITY,
2312,Farming,Harvest Garlic,CROPSANITY,
2313,Farming,Harvest Grape,CROPSANITY,
2314,Farming,Harvest Green Bean,CROPSANITY,
2315,Farming,Harvest Hops,CROPSANITY,
2316,Farming,Harvest Hot Pepper,CROPSANITY,
2317,Farming,Harvest Kale,CROPSANITY,
2318,Farming,Harvest Melon,CROPSANITY,
2319,Farming,Harvest Parsnip,CROPSANITY,
2320,Farming,Harvest Poppy,CROPSANITY,
2321,Farming,Harvest Potato,CROPSANITY,
2322,Farming,Harvest Pumpkin,CROPSANITY,
2323,Farming,Harvest Radish,CROPSANITY,
2324,Farming,Harvest Red Cabbage,CROPSANITY,
2325,Farming,Harvest Rhubarb,CROPSANITY,
2326,Farming,Harvest Starfruit,CROPSANITY,
2327,Farming,Harvest Strawberry,CROPSANITY,
2328,Farming,Harvest Summer Spangle,CROPSANITY,
2329,Farming,Harvest Sunflower,CROPSANITY,
2330,Farming,Harvest Tomato,CROPSANITY,
2331,Farming,Harvest Tulip,CROPSANITY,
2332,Farming,Harvest Unmilled Rice,CROPSANITY,
2333,Farming,Harvest Wheat,CROPSANITY,
2334,Farming,Harvest Yam,CROPSANITY,
2335,Farming,Harvest Cactus Fruit,CROPSANITY,
2336,Farming,Harvest Pineapple,"CROPSANITY,GINGER_ISLAND",
2337,Farming,Harvest Taro Root,"CROPSANITY,GINGER_ISLAND",
2338,Farming,Harvest Sweet Gem Berry,CROPSANITY,
2339,Farming,Harvest Apple,CROPSANITY,
2340,Farming,Harvest Apricot,CROPSANITY,
2341,Farming,Harvest Cherry,CROPSANITY,
2342,Farming,Harvest Orange,CROPSANITY,
2343,Farming,Harvest Pomegranate,CROPSANITY,
2344,Farming,Harvest Peach,CROPSANITY,
2345,Farming,Harvest Banana,"CROPSANITY,GINGER_ISLAND",
2346,Farming,Harvest Mango,"CROPSANITY,GINGER_ISLAND",
2347,Farming,Harvest Coffee Bean,CROPSANITY,
2301,Fall Farming,Harvest Amaranth,CROPSANITY,
2302,Fall Farming,Harvest Artichoke,CROPSANITY,
2303,Fall Farming,Harvest Beet,CROPSANITY,
2304,Spring Farming,Harvest Blue Jazz,CROPSANITY,
2305,Summer Farming,Harvest Blueberry,CROPSANITY,
2306,Fall Farming,Harvest Bok Choy,CROPSANITY,
2307,Spring Farming,Harvest Cauliflower,CROPSANITY,
2308,Summer or Fall Farming,Harvest Corn,CROPSANITY,
2309,Fall Farming,Harvest Cranberries,CROPSANITY,
2310,Fall Farming,Harvest Eggplant,CROPSANITY,
2311,Fall Farming,Harvest Fairy Rose,CROPSANITY,
2312,Spring Farming,Harvest Garlic,CROPSANITY,
2313,Fall Farming,Harvest Grape,CROPSANITY,
2314,Spring Farming,Harvest Green Bean,CROPSANITY,
2315,Summer Farming,Harvest Hops,CROPSANITY,
2316,Summer Farming,Harvest Hot Pepper,CROPSANITY,
2317,Spring Farming,Harvest Kale,CROPSANITY,
2318,Summer Farming,Harvest Melon,CROPSANITY,
2319,Spring Farming,Harvest Parsnip,CROPSANITY,
2320,Summer Farming,Harvest Poppy,CROPSANITY,
2321,Spring Farming,Harvest Potato,CROPSANITY,
2322,Fall Farming,Harvest Pumpkin,CROPSANITY,
2323,Summer Farming,Harvest Radish,CROPSANITY,
2324,Summer Farming,Harvest Red Cabbage,CROPSANITY,
2325,Spring Farming,Harvest Rhubarb,CROPSANITY,
2326,Summer Farming,Harvest Starfruit,CROPSANITY,
2327,Spring Farming,Harvest Strawberry,CROPSANITY,
2328,Summer Farming,Harvest Summer Spangle,CROPSANITY,
2329,Summer or Fall Farming,Harvest Sunflower,CROPSANITY,
2330,Summer Farming,Harvest Tomato,CROPSANITY,
2331,Spring Farming,Harvest Tulip,CROPSANITY,
2332,Spring Farming,Harvest Unmilled Rice,CROPSANITY,
2333,Summer or Fall Farming,Harvest Wheat,CROPSANITY,
2334,Fall Farming,Harvest Yam,CROPSANITY,
2335,Indoor Farming,Harvest Cactus Fruit,CROPSANITY,
2336,Summer Farming,Harvest Pineapple,"CROPSANITY,GINGER_ISLAND",
2337,Summer Farming,Harvest Taro Root,"CROPSANITY,GINGER_ISLAND",
2338,Fall Farming,Harvest Sweet Gem Berry,CROPSANITY,
2339,Fall Farming,Harvest Apple,CROPSANITY,
2340,Spring Farming,Harvest Apricot,CROPSANITY,
2341,Spring Farming,Harvest Cherry,CROPSANITY,
2342,Summer Farming,Harvest Orange,CROPSANITY,
2343,Fall Farming,Harvest Pomegranate,CROPSANITY,
2344,Summer Farming,Harvest Peach,CROPSANITY,
2345,Summer Farming,Harvest Banana,"CROPSANITY,GINGER_ISLAND",
2346,Summer Farming,Harvest Mango,"CROPSANITY,GINGER_ISLAND",
2347,Indoor Farming,Harvest Coffee Bean,CROPSANITY,
2348,Fall Farming,Harvest Broccoli,CROPSANITY,
2349,Spring Farming,Harvest Carrot,CROPSANITY,
2350,Summer Farming,Harvest Powdermelon,CROPSANITY,
2351,Summer Farming,Harvest Summer Squash,CROPSANITY,
2352,Indoor Farming,Harvest Ancient Fruit,CROPSANITY,
2353,Indoor Farming,Harvest Qi Fruit,"CROPSANITY,GINGER_ISLAND",
2401,Shipping,Shipsanity: Duck Egg,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
2402,Shipping,Shipsanity: Duck Feather,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
2403,Shipping,Shipsanity: Egg,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
@ -1431,7 +1511,7 @@ id,region,name,tags,mod_name
2717,Shipping,Shipsanity: Cactus Fruit,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
2718,Shipping,Shipsanity: Cave Carrot,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
2719,Shipping,Shipsanity: Chanterelle,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
2720,Shipping,Shipsanity: Clam,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
2720,Shipping,Shipsanity: Clam,"SHIPSANITY,SHIPSANITY_FISH",
2721,Shipping,Shipsanity: Coconut,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
2722,Shipping,Shipsanity: Common Mushroom,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
2723,Shipping,Shipsanity: Coral,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
@ -1683,7 +1763,7 @@ id,region,name,tags,mod_name
2969,Shipping,Shipsanity: Mango Sapling,"GINGER_ISLAND,SHIPSANITY",
2970,Shipping,Shipsanity: Mushroom Tree Seed,"GINGER_ISLAND,SHIPSANITY,REQUIRES_QI_ORDERS",
2971,Shipping,Shipsanity: Pineapple Seeds,"GINGER_ISLAND,SHIPSANITY",
2972,Shipping,Shipsanity: Qi Bean,"GINGER_ISLAND,SHIPSANITY",
2972,Shipping,Shipsanity: Qi Bean,"GINGER_ISLAND,SHIPSANITY,REQUIRES_QI_ORDERS",
2973,Shipping,Shipsanity: Taro Tuber,"GINGER_ISLAND,SHIPSANITY",
3001,Adventurer's Guild,Monster Eradication: Slimes,"MONSTERSANITY,MONSTERSANITY_GOALS",
3002,Adventurer's Guild,Monster Eradication: Void Spirits,"MONSTERSANITY,MONSTERSANITY_GOALS",
@ -1852,6 +1932,7 @@ id,region,name,tags,mod_name
3278,Kitchen,Cook Tropical Curry,"COOKSANITY,GINGER_ISLAND",
3279,Kitchen,Cook Trout Soup,"COOKSANITY,COOKSANITY_QOS",
3280,Kitchen,Cook Vegetable Medley,COOKSANITY,
3281,Kitchen,Cook Moss Soup,COOKSANITY,
3301,Farm,Algae Soup Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
3302,The Queen of Sauce,Artichoke Dip Recipe,"CHEFSANITY,CHEFSANITY_QOS",
3303,Farm,Autumn's Bounty Recipe,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
@ -1932,6 +2013,7 @@ id,region,name,tags,mod_name
3378,Island Resort,Tropical Curry Recipe,"CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE",
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,
@ -2062,6 +2144,26 @@ id,region,name,tags,mod_name
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,
@ -2088,6 +2190,226 @@ id,region,name,tags,mod_name
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",
3801,Shipping,Shipsanity: Goby,"SHIPSANITY,SHIPSANITY_FISH",
3802,Shipping,Shipsanity: Fireworks (Red),"SHIPSANITY",
3803,Shipping,Shipsanity: Fireworks (Purple),"SHIPSANITY",
3804,Shipping,Shipsanity: Fireworks (Green),"SHIPSANITY",
3805,Shipping,Shipsanity: Far Away Stone,"SHIPSANITY",
3806,Shipping,Shipsanity: Calico Egg,"SHIPSANITY",
3807,Shipping,Shipsanity: Mixed Flower Seeds,"SHIPSANITY",
3808,Shipping,Shipsanity: Mystery Box,"SHIPSANITY",
3809,Shipping,Shipsanity: Golden Tag,"SHIPSANITY",
3810,Shipping,Shipsanity: Deluxe Bait,"SHIPSANITY",
3811,Shipping,Shipsanity: Moss,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
3812,Shipping,Shipsanity: Mossy Seed,"SHIPSANITY",
3813,Shipping,Shipsanity: Sonar Bobber,"SHIPSANITY",
3814,Shipping,Shipsanity: Tent Kit,"SHIPSANITY",
3815,Shipping,Shipsanity: Mystic Tree Seed,"SHIPSANITY,REQUIRES_MASTERIES",
3816,Shipping,Shipsanity: Mystic Syrup,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3817,Shipping,Shipsanity: Raisins,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3818,Shipping,Shipsanity: Dried Fruit,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3819,Shipping,Shipsanity: Dried Mushrooms,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3820,Shipping,Shipsanity: Stardrop Tea,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3821,Shipping,Shipsanity: Prize Ticket,"SHIPSANITY",
3822,Shipping,Shipsanity: Treasure Totem,"SHIPSANITY,REQUIRES_MASTERIES",
3823,Shipping,Shipsanity: Challenge Bait,"SHIPSANITY,REQUIRES_MASTERIES",
3824,Shipping,Shipsanity: Carrot Seeds,"SHIPSANITY",
3825,Shipping,Shipsanity: Carrot,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
3826,Shipping,Shipsanity: Summer Squash Seeds,"SHIPSANITY",
3827,Shipping,Shipsanity: Summer Squash,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
3828,Shipping,Shipsanity: Broccoli Seeds,"SHIPSANITY",
3829,Shipping,Shipsanity: Broccoli,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
3830,Shipping,Shipsanity: Powdermelon Seeds,"SHIPSANITY",
3831,Shipping,Shipsanity: Powdermelon,"SHIPSANITY,SHIPSANITY_CROP,SHIPSANITY_FULL_SHIPMENT",
3832,Shipping,Shipsanity: Smoked Fish,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3833,Shipping,Shipsanity: Book Of Stars,"SHIPSANITY",
3834,Shipping,Shipsanity: Stardew Valley Almanac,"SHIPSANITY",
3835,Shipping,Shipsanity: Woodcutter's Weekly,"SHIPSANITY",
3836,Shipping,Shipsanity: Bait And Bobber,"SHIPSANITY",
3837,Shipping,Shipsanity: Mining Monthly,"SHIPSANITY",
3838,Shipping,Shipsanity: Combat Quarterly,"SHIPSANITY",
3839,Shipping,Shipsanity: The Alleyway Buffet,"SHIPSANITY",
3840,Shipping,Shipsanity: The Art O' Crabbing,"SHIPSANITY",
3841,Shipping,Shipsanity: Dwarvish Safety Manual,"SHIPSANITY",
3842,Shipping,Shipsanity: Jewels Of The Sea,"SHIPSANITY",
3843,Shipping,Shipsanity: Raccoon Journal,"SHIPSANITY",
3844,Shipping,Shipsanity: Woody's Secret,"SHIPSANITY",
3845,Shipping,"Shipsanity: Jack Be Nimble, Jack Be Thick","SHIPSANITY",
3846,Shipping,Shipsanity: Friendship 101,"SHIPSANITY",
3847,Shipping,Shipsanity: Monster Compendium,"SHIPSANITY",
3848,Shipping,Shipsanity: Way Of The Wind pt. 1,"SHIPSANITY",
3849,Shipping,Shipsanity: Mapping Cave Systems,"SHIPSANITY",
3850,Shipping,Shipsanity: Price Catalogue,"SHIPSANITY",
3851,Shipping,Shipsanity: Queen Of Sauce Cookbook,"SHIPSANITY",
3852,Shipping,Shipsanity: The Diamond Hunter,"SHIPSANITY,GINGER_ISLAND",
3853,Shipping,Shipsanity: Book of Mysteries,"SHIPSANITY",
3854,Shipping,Shipsanity: Animal Catalogue,"SHIPSANITY",
3855,Shipping,Shipsanity: Way Of The Wind pt. 2,"SHIPSANITY",
3856,Shipping,Shipsanity: Golden Animal Cracker,"SHIPSANITY,REQUIRES_MASTERIES",
3857,Shipping,Shipsanity: Golden Mystery Box,"SHIPSANITY,REQUIRES_MASTERIES",
3858,Shipping,Shipsanity: Sea Jelly,"SHIPSANITY,SHIPSANITY_FISH",
3859,Shipping,Shipsanity: Cave Jelly,"SHIPSANITY,SHIPSANITY_FISH",
3860,Shipping,Shipsanity: River Jelly,"SHIPSANITY,SHIPSANITY_FISH",
3861,Shipping,Shipsanity: Treasure Appraisal Guide,"SHIPSANITY",
3862,Shipping,Shipsanity: Horse: The Book,"SHIPSANITY",
3863,Shipping,Shipsanity: Butterfly Powder,"SHIPSANITY",
3864,Shipping,Shipsanity: Blue Grass Starter,"SHIPSANITY,GINGER_ISLAND,REQUIRES_QI_ORDERS",
3865,Shipping,Shipsanity: Moss Soup,"SHIPSANITY",
3866,Shipping,Shipsanity: Ol' Slitherlegs,"SHIPSANITY",
3867,Shipping,Shipsanity: Targeted Bait,"SHIPSANITY",
4001,Farm,Read Price Catalogue,"BOOKSANITY,BOOKSANITY_POWER",
4002,Farm,Read Mapping Cave Systems,"BOOKSANITY,BOOKSANITY_POWER",
4003,Farm,Read Way Of The Wind pt. 1,"BOOKSANITY,BOOKSANITY_POWER",
4004,Farm,Read Way Of The Wind pt. 2,"BOOKSANITY,BOOKSANITY_POWER",
4005,Farm,Read Monster Compendium,"BOOKSANITY,BOOKSANITY_POWER",
4006,Farm,Read Friendship 101,"BOOKSANITY,BOOKSANITY_POWER",
4007,Farm,"Read Jack Be Nimble, Jack Be Thick","BOOKSANITY,BOOKSANITY_POWER",
4008,Farm,Read Woody's Secret,"BOOKSANITY,BOOKSANITY_POWER",
4009,Farm,Read Raccoon Journal,"BOOKSANITY,BOOKSANITY_POWER",
4010,Farm,Read Jewels Of The Sea,"BOOKSANITY,BOOKSANITY_POWER",
4011,Farm,Read Dwarvish Safety Manual,"BOOKSANITY,BOOKSANITY_POWER",
4012,Farm,Read The Art O' Crabbing,"BOOKSANITY,BOOKSANITY_POWER",
4013,Farm,Read The Alleyway Buffet,"BOOKSANITY,BOOKSANITY_POWER",
4014,Farm,Read The Diamond Hunter,"BOOKSANITY,BOOKSANITY_POWER,GINGER_ISLAND",
4015,Farm,Read Book of Mysteries,"BOOKSANITY,BOOKSANITY_POWER",
4016,Farm,Read Horse: The Book,"BOOKSANITY,BOOKSANITY_POWER",
4017,Farm,Read Treasure Appraisal Guide,"BOOKSANITY,BOOKSANITY_POWER",
4018,Farm,Read Ol' Slitherlegs,"BOOKSANITY,BOOKSANITY_POWER",
4019,Farm,Read Animal Catalogue,"BOOKSANITY,BOOKSANITY_POWER",
4031,Farm,Read Bait And Bobber,"BOOKSANITY,BOOKSANITY_SKILL",
4032,Farm,Read Book Of Stars,"BOOKSANITY,BOOKSANITY_SKILL",
4033,Farm,Read Combat Quarterly,"BOOKSANITY,BOOKSANITY_SKILL",
4034,Farm,Read Mining Monthly,"BOOKSANITY,BOOKSANITY_SKILL",
4035,Farm,Read Queen Of Sauce Cookbook,"BOOKSANITY,BOOKSANITY_SKILL",
4036,Farm,Read Stardew Valley Almanac,"BOOKSANITY,BOOKSANITY_SKILL",
4037,Farm,Read Woodcutter's Weekly,"BOOKSANITY,BOOKSANITY_SKILL",
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",
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",
4058,Museum,Read The Secret of the Stardrop,"BOOKSANITY,BOOKSANITY_LOST",
4059,Museum,Read Journey of the Prairie King -- The Smash Hit Video Game!,"BOOKSANITY,BOOKSANITY_LOST",
4060,Museum,Read A Study on Diamond Yields,"BOOKSANITY,BOOKSANITY_LOST",
4061,Museum,Read Brewmaster's Guide,"BOOKSANITY,BOOKSANITY_LOST",
4062,Museum,Read Mysteries of the Dwarves,"BOOKSANITY,BOOKSANITY_LOST",
4063,Museum,Read Highlights From The Book of Yoba,"BOOKSANITY,BOOKSANITY_LOST",
4064,Museum,Read Marriage Guide for Farmers,"BOOKSANITY,BOOKSANITY_LOST",
4065,Museum,"Read The Fisherman, Act II","BOOKSANITY,BOOKSANITY_LOST",
4066,Museum,Read Technology Report!,"BOOKSANITY,BOOKSANITY_LOST",
4067,Museum,Read Secrets of the Legendary Fish,"BOOKSANITY,BOOKSANITY_LOST",
4068,Museum,Read Gunther Tunnel Notice,"BOOKSANITY,BOOKSANITY_LOST",
4069,Museum,Read Note From Gunther,"BOOKSANITY,BOOKSANITY_LOST",
4070,Museum,Read Goblins by M. Jasper,"BOOKSANITY,BOOKSANITY_LOST",
4071,Museum,Read Secret Statues Acrostics,"BOOKSANITY,BOOKSANITY_LOST",
4101,Clint's Blacksmith,Open Golden Coconut,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4102,Island West,Fishing Walnut 1,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4103,Island West,Fishing Walnut 2,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4104,Island North,Fishing Walnut 3,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4105,Island North,Fishing Walnut 4,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4106,Island Southeast,Fishing Walnut 5,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4107,Island East,Jungle Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4108,Island East,Banana Altar,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4109,Leo's Hut,Leo's Tree,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4110,Island Shrine,Gem Birds Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4111,Island Shrine,Gem Birds Shrine,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4112,Island West,Harvesting Walnut 1,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4113,Island West,Harvesting Walnut 2,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4114,Island West,Harvesting Walnut 3,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4115,Island West,Harvesting Walnut 4,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4116,Island West,Harvesting Walnut 5,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4117,Gourmand Frog Cave,Gourmand Frog Melon,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4118,Gourmand Frog Cave,Gourmand Frog Wheat,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4119,Gourmand Frog Cave,Gourmand Frog Garlic,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4120,Island West,Journal Scrap #6,"WALNUTSANITY,WALNUTSANITY_DIG",
4121,Island West,Mussel Node Walnut 1,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4122,Island West,Mussel Node Walnut 2,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4123,Island West,Mussel Node Walnut 3,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4124,Island West,Mussel Node Walnut 4,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4125,Island West,Mussel Node Walnut 5,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4126,Shipwreck,Shipwreck Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4127,Island West,Whack A Mole,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4128,Island West,Starfish Triangle,"WALNUTSANITY,WALNUTSANITY_DIG",
4129,Island West,Starfish Diamond,"WALNUTSANITY,WALNUTSANITY_DIG",
4130,Island West,X in the sand,"WALNUTSANITY,WALNUTSANITY_DIG",
4131,Island West,Diamond Of Indents,"WALNUTSANITY,WALNUTSANITY_DIG",
4132,Island West,Bush Behind Coconut Tree,"WALNUTSANITY,WALNUTSANITY_BUSH",
4133,Island West,Journal Scrap #4,"WALNUTSANITY,WALNUTSANITY_DIG",
4134,Island West,Walnut Room Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4135,Island West,Coast Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4136,Island West,Tiger Slime Walnut,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4137,Island West,Bush Behind Mahogany Tree,"WALNUTSANITY,WALNUTSANITY_BUSH",
4138,Island West,Circle Of Grass,"WALNUTSANITY,WALNUTSANITY_DIG",
4139,Island West,Below Colored Crystals Cave Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4140,Colored Crystals Cave,Colored Crystals,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4141,Island West,Cliff Edge Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4142,Island West,Diamond Of Pebbles,"WALNUTSANITY,WALNUTSANITY_DIG",
4143,Island West,Farm Parrot Express Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4144,Island West,Farmhouse Cliff Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4145,Island North,Big Circle Of Stones,"WALNUTSANITY,WALNUTSANITY_DIG",
4146,Island North,Grove Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4147,Island North,Diamond Of Grass,"WALNUTSANITY,WALNUTSANITY_DIG",
4148,Island North,Small Circle Of Stones,"WALNUTSANITY,WALNUTSANITY_DIG",
4149,Island North,Patch Of Sand,"WALNUTSANITY,WALNUTSANITY_DIG",
4150,Dig Site,Crooked Circle Of Stones,"WALNUTSANITY,WALNUTSANITY_DIG",
4151,Dig Site,Above Dig Site Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4152,Dig Site,Above Field Office Bush 1,"WALNUTSANITY,WALNUTSANITY_BUSH",
4153,Dig Site,Above Field Office Bush 2,"WALNUTSANITY,WALNUTSANITY_BUSH",
4154,Field Office,Complete Large Animal Collection,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4155,Field Office,Complete Snake Collection,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4156,Field Office,Complete Mummified Frog Collection,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4157,Field Office,Complete Mummified Bat Collection,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4158,Field Office,Purple Flowers Island Survey,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4159,Field Office,Purple Starfish Island Survey,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4160,Island North,Bush Behind Volcano Tree,"WALNUTSANITY,WALNUTSANITY_BUSH",
4161,Island North,Arc Of Stones,"WALNUTSANITY,WALNUTSANITY_DIG",
4162,Island North,Protruding Tree Walnut,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4163,Island North,Journal Scrap #10,"WALNUTSANITY,WALNUTSANITY_DIG",
4164,Island North,Northmost Point Circle Of Stones,"WALNUTSANITY,WALNUTSANITY_DIG",
4165,Island North,Hidden Passage Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4166,Volcano Secret Beach,Secret Beach Bush 1,"WALNUTSANITY,WALNUTSANITY_BUSH",
4167,Volcano Secret Beach,Secret Beach Bush 2,"WALNUTSANITY,WALNUTSANITY_BUSH",
4168,Volcano - Floor 5,Volcano Rocks Walnut 1,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4169,Volcano - Floor 5,Volcano Rocks Walnut 2,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4170,Volcano - Floor 10,Volcano Rocks Walnut 3,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4171,Volcano - Floor 10,Volcano Rocks Walnut 4,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4172,Volcano - Floor 10,Volcano Rocks Walnut 5,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4173,Volcano - Floor 5,Volcano Monsters Walnut 1,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4174,Volcano - Floor 5,Volcano Monsters Walnut 2,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4175,Volcano - Floor 10,Volcano Monsters Walnut 3,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4176,Volcano - Floor 10,Volcano Monsters Walnut 4,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4177,Volcano - Floor 10,Volcano Monsters Walnut 5,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4178,Volcano - Floor 5,Volcano Crates Walnut 1,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4179,Volcano - Floor 5,Volcano Crates Walnut 2,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4180,Volcano - Floor 10,Volcano Crates Walnut 3,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4181,Volcano - Floor 10,Volcano Crates Walnut 4,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4182,Volcano - Floor 10,Volcano Crates Walnut 5,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4183,Volcano - Floor 5,Volcano Common Chest Walnut,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4184,Volcano - Floor 10,Volcano Rare Chest Walnut,"WALNUTSANITY,WALNUTSANITY_REPEATABLE",
4185,Volcano - Floor 10,Forge Entrance Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4186,Volcano - Floor 10,Forge Exit Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4187,Island North,Cliff Over Island South Bush,"WALNUTSANITY,WALNUTSANITY_BUSH",
4188,Island Southeast,Starfish Tide Pool,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4189,Island Southeast,Diamond Of Yellow Starfish,"WALNUTSANITY,WALNUTSANITY_DIG",
4190,Island Southeast,Mermaid Song,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4191,Pirate Cove,Pirate Darts 1,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4192,Pirate Cove,Pirate Darts 2,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4193,Pirate Cove,Pirate Darts 3,"WALNUTSANITY,WALNUTSANITY_PUZZLE",
4194,Pirate Cove,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
@ -2578,6 +2900,7 @@ id,region,name,tags,mod_name
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
7351,Farm,Read Digging Like Worms,"BOOKSANITY,BOOKSANITY_SKILL",Archaeology
7401,Farm,Cook Magic Elixir,COOKSANITY,Magic
7402,Farm,Craft Travel Core,CRAFTSANITY,Magic
7403,Farm,Craft Haste Elixir,CRAFTSANITY,Stardew Valley Expanded
@ -2585,7 +2908,7 @@ id,region,name,tags,mod_name
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 Bazier,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
@ -2603,13 +2926,23 @@ id,region,name,tags,mod_name
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
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,Lost Emerald Ring,"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
@ -2648,6 +2981,11 @@ id,region,name,tags,mod_name
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
@ -2668,6 +3006,11 @@ id,region,name,tags,mod_name
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
@ -2697,7 +3040,6 @@ id,region,name,tags,mod_name
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
7727,Blue Moon Vineyard,Fishsanity: Dulse Seaweed,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
@ -2714,15 +3056,15 @@ id,region,name,tags,mod_name
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 Ferns Seed,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: Bearberrys,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",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: Big Conch,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",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
@ -2730,8 +3072,7 @@ id,region,name,tags,mod_name
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: Dried Sand Dollar,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Stardew Valley Expanded
8022,Shipping,Shipsanity: Dulse Seaweed,"SHIPSANITY,SHIPSANITY_FISH",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
@ -2751,7 +3092,7 @@ id,region,name,tags,mod_name
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: Lucky Four Leaf Clover,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",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
@ -2774,7 +3115,7 @@ id,region,name,tags,mod_name
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: Smelly Rafflesia,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",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
@ -2937,3 +3278,12 @@ id,region,name,tags,mod_name
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
8240,Shipping,Shipsanity: Digging Like Worms,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

Can't render this file because it has a wrong number of fields in line 164.

View File

@ -76,6 +76,8 @@ def create_mineral(name: str,
difficulty += 1.0 / 26.0 * 100
if "Omni Geode" in geodes:
difficulty += 31.0 / 2750.0 * 100
if "Fishing Chest" in geodes:
difficulty += 4.3
mineral_item = MuseumItem.of(name, difficulty, locations, geodes, monsters)
all_museum_minerals.append(mineral_item)
@ -95,7 +97,7 @@ class Artifact:
geodes=Geode.artifact_trove)
arrowhead = create_artifact("Arrowhead", 8.5, (Region.mountain, Region.forest, Region.bus_stop),
geodes=Geode.artifact_trove)
ancient_doll = create_artifact("Ancient Doll", 13.1, (Region.mountain, Region.forest, Region.bus_stop),
ancient_doll = create_artifact(Artifact.ancient_doll, 13.1, (Region.mountain, Region.forest, Region.bus_stop),
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
elvish_jewelry = create_artifact("Elvish Jewelry", 5.3, Region.forest,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
@ -103,8 +105,7 @@ class Artifact:
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
ornamental_fan = create_artifact("Ornamental Fan", 7.4, (Region.beach, Region.forest, Region.town),
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
dinosaur_egg = create_artifact("Dinosaur Egg", 11.4, (Region.mountain, Region.skull_cavern),
geodes=WaterChest.fishing_chest,
dinosaur_egg = create_artifact("Dinosaur Egg", 11.4, (Region.skull_cavern),
monsters=Monster.pepper_rex)
rare_disc = create_artifact("Rare Disc", 5.6, Region.stardew_valley,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest),
@ -170,18 +171,18 @@ class Artifact:
class Mineral:
quartz = create_mineral(Mineral.quartz, Region.mines_floor_20)
quartz = create_mineral(Mineral.quartz, Region.mines_floor_20, difficulty=100.0 / 5.0)
fire_quartz = create_mineral("Fire Quartz", Region.mines_floor_100,
geodes=(Geode.magma, Geode.omni, WaterChest.fishing_chest),
difficulty=1.0 / 12.0)
difficulty=100.0 / 5.0)
frozen_tear = create_mineral("Frozen Tear", Region.mines_floor_60,
geodes=(Geode.frozen, Geode.omni, WaterChest.fishing_chest),
monsters=unlikely,
difficulty=1.0 / 12.0)
difficulty=100.0 / 5.0)
earth_crystal = create_mineral("Earth Crystal", Region.mines_floor_20,
geodes=(Geode.geode, Geode.omni, WaterChest.fishing_chest),
monsters=Monster.duggy,
difficulty=1.0 / 12.0)
difficulty=100.0 / 5.0)
emerald = create_mineral("Emerald", Region.mines_floor_100,
geodes=WaterChest.fishing_chest)
aquamarine = create_mineral("Aquamarine", Region.mines_floor_60,

View File

@ -7,15 +7,16 @@ from ..strings.craftable_names import ModEdible, Edible
from ..strings.crop_names import Fruit, Vegetable, SVEFruit, DistantLandsCrop
from ..strings.fish_names import Fish, SVEFish, WaterItem, DistantLandsFish
from ..strings.flower_names import Flower
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable, Mushroom
from ..strings.ingredient_names import Ingredient
from ..strings.food_names import Meal, SVEMeal, Beverage, DistantLandsMeal, BoardingHouseMeal
from ..strings.food_names import Meal, SVEMeal, Beverage, DistantLandsMeal, BoardingHouseMeal, ArchaeologyMeal, TrashyMeal
from ..strings.material_names import Material
from ..strings.metal_names import Fossil
from ..strings.metal_names import Fossil, Artifact
from ..strings.monster_drop_names import Loot
from ..strings.region_names import Region, SVERegion
from ..strings.season_names import Season
from ..strings.skill_names import Skill
from ..strings.seed_names import Seed
from ..strings.skill_names import Skill, ModSkill
from ..strings.villager_names import NPC, ModNPC
@ -49,9 +50,9 @@ def friendship_and_shop_recipe(name: str, friend: str, hearts: int, region: str,
return create_recipe(name, ingredients, source, mod_name)
def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int]) -> CookingRecipe:
def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe:
source = SkillSource(skill, level)
return create_recipe(name, ingredients, source)
return create_recipe(name, ingredients, source, mod_name)
def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe:
@ -116,7 +117,7 @@ fish_taco = friendship_recipe(Meal.fish_taco, NPC.linus, 7, {Fish.tuna: 1, Meal.
fried_calamari = friendship_recipe(Meal.fried_calamari, NPC.jodi, 3, {Fish.squid: 1, Ingredient.wheat_flour: 1, Ingredient.oil: 1})
fried_eel = friendship_recipe(Meal.fried_eel, NPC.george, 3, {Fish.eel: 1, Ingredient.oil: 1})
fried_egg = starter_recipe(Meal.fried_egg, {AnimalProduct.chicken_egg: 1})
fried_mushroom = friendship_recipe(Meal.fried_mushroom, NPC.demetrius, 3, {Forageable.common_mushroom: 1, Forageable.morel: 1, Ingredient.oil: 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})
glazed_yams = queen_of_sauce_recipe(Meal.glazed_yams, 1, Season.fall, 21, {Vegetable.yam: 1, Ingredient.sugar: 1})
@ -130,6 +131,7 @@ maki_roll = queen_of_sauce_recipe(Meal.maki_roll, 1, Season.summer, 21, {Fish.an
mango_sticky_rice = friendship_recipe(Meal.mango_sticky_rice, NPC.leo, 7, {Fruit.mango: 1, Forageable.coconut: 1, Ingredient.rice: 1})
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})
omelet = queen_of_sauce_recipe(Meal.omelet, 1, Season.spring, 28, {AnimalProduct.chicken_egg: 1, AnimalProduct.cow_milk: 1})
pale_broth = friendship_recipe(Meal.pale_broth, NPC.marnie, 3, {WaterItem.white_algae: 2})
pancakes = queen_of_sauce_recipe(Meal.pancakes, 1, Season.summer, 14, {Ingredient.wheat_flour: 1, AnimalProduct.chicken_egg: 1})
@ -160,13 +162,14 @@ shrimp_cocktail = queen_of_sauce_recipe(Meal.shrimp_cocktail, 2, Season.winter,
spaghetti = friendship_recipe(Meal.spaghetti, NPC.lewis, 3, {Vegetable.tomato: 1, Ingredient.wheat_flour: 1})
spicy_eel = friendship_recipe(Meal.spicy_eel, NPC.george, 7, {Fish.eel: 1, Fruit.hot_pepper: 1})
squid_ink_ravioli = skill_recipe(Meal.squid_ink_ravioli, Skill.combat, 9, {AnimalProduct.squid_ink: 1, Ingredient.wheat_flour: 1, Vegetable.tomato: 1})
stir_fry_ingredients = {Forageable.cave_carrot: 1, Forageable.common_mushroom: 1, Vegetable.kale: 1, Ingredient.sugar: 1}
stir_fry_ingredients = {Forageable.cave_carrot: 1, Mushroom.common: 1, Vegetable.kale: 1, Ingredient.sugar: 1}
stir_fry_qos = queen_of_sauce_recipe(Meal.stir_fry, 1, Season.spring, 7, stir_fry_ingredients)
strange_bun = friendship_recipe(Meal.strange_bun, NPC.shane, 7, {Ingredient.wheat_flour: 1, Fish.periwinkle: 1, ArtisanGood.void_mayonnaise: 1})
stuffing = friendship_recipe(Meal.stuffing, NPC.pam, 7, {Meal.bread: 1, Fruit.cranberries: 1, Forageable.hazelnut: 1})
super_meal = friendship_recipe(Meal.super_meal, NPC.kent, 7, {Vegetable.bok_choy: 1, Fruit.cranberries: 1, Vegetable.artichoke: 1})
survival_burger = skill_recipe(Meal.survival_burger, Skill.foraging, 2, {Meal.bread: 1, Forageable.cave_carrot: 1, Vegetable.eggplant: 1})
tom_kha_soup = friendship_recipe(Meal.tom_kha_soup, NPC.sandy, 7, {Forageable.coconut: 1, Fish.shrimp: 1, Forageable.common_mushroom: 1})
survival_burger = skill_recipe(Meal.survival_burger, Skill.foraging, 8, {Meal.bread: 1, Forageable.cave_carrot: 1, Vegetable.eggplant: 1})
tom_kha_soup = friendship_recipe(Meal.tom_kha_soup, NPC.sandy, 7, {Forageable.coconut: 1, Fish.shrimp: 1, Mushroom.common: 1})
tortilla_ingredients = {Vegetable.corn: 1}
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)
@ -175,7 +178,7 @@ tropical_curry = shop_recipe(Meal.tropical_curry, Region.island_resort, 2000, {F
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, Forageable.purple_mushroom: 1}, ModNames.magic)
magic_elixir = shop_recipe(ModEdible.magic_elixir, Region.adventurer_guild, 3000, {Edible.life_elixir: 1, Mushroom.purple: 1}, 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)
@ -188,7 +191,7 @@ frog_legs = shop_recipe(SVEMeal.frog_legs, Region.adventurer_guild, 2000, {SVEFi
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)
mixed_berry_pie = shop_recipe(SVEMeal.mixed_berry_pie, Region.saloon, 3500, {Fruit.strawberry: 6, SVEFruit.salal_berry: 6, Forageable.blackberry: 6,
SVEForage.bearberrys: 6, Ingredient.sugar: 1, Ingredient.wheat_flour: 1},
SVEForage.bearberry: 6, Ingredient.sugar: 1, Ingredient.wheat_flour: 1},
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)
@ -198,8 +201,8 @@ void_delight = friendship_and_shop_recipe(SVEMeal.void_delight, NPC.krobus, 10,
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)
mushroom_kebab = friendship_recipe(DistantLandsMeal.mushroom_kebab, ModNPC.goblin, 2, {Forageable.chanterelle: 1, Forageable.common_mushroom: 1,
Forageable.red_mushroom: 1, Material.wood: 1}, ModNames.distant_lands)
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)
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)
@ -208,6 +211,11 @@ pemmican = friendship_recipe(DistantLandsMeal.pemmican, ModNPC.goblin, 8, {Loot.
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)
diggers_delight = skill_recipe(ArchaeologyMeal.diggers_delight, ModSkill.archaeology, 3, {Forageable.cave_carrot: 2, Ingredient.sugar: 1, AnimalProduct.milk: 1}, ModNames.archaeology)
rocky_root = skill_recipe(ArchaeologyMeal.rocky_root, ModSkill.archaeology, 7, {Forageable.cave_carrot: 3, Seed.coffee: 1, Material.stone: 1}, 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)
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)
all_cooking_recipes_by_name = {recipe.meal: recipe for recipe in all_cooking_recipes}

View File

@ -94,6 +94,16 @@ class SkillSource(RecipeSource):
return f"SkillSource at level {self.level} {self.skill}"
class MasterySource(RecipeSource):
skill: str
def __init__(self, skill: str):
self.skill = skill
def __repr__(self):
return f"MasterySource at level {self.level} {self.skill}"
class ShopSource(RecipeSource):
region: str
price: int

View File

@ -0,0 +1,31 @@
from dataclasses import dataclass
from .game_item import Requirement
from ..strings.tool_names import ToolMaterial
@dataclass(frozen=True)
class BookRequirement(Requirement):
book: str
@dataclass(frozen=True)
class ToolRequirement(Requirement):
tool: str
tier: str = ToolMaterial.basic
@dataclass(frozen=True)
class SkillRequirement(Requirement):
skill: str
level: int
@dataclass(frozen=True)
class SeasonRequirement(Requirement):
season: str
@dataclass(frozen=True)
class YearRequirement(Requirement):
year: int

View File

@ -0,0 +1,40 @@
from dataclasses import dataclass
from typing import Tuple, Optional
from .game_item import ItemSource, kw_only, Requirement
from ..strings.season_names import Season
ItemPrice = Tuple[int, str]
@dataclass(frozen=True, **kw_only)
class ShopSource(ItemSource):
shop_region: str
money_price: Optional[int] = None
items_price: Optional[Tuple[ItemPrice, ...]] = None
seasons: Tuple[str, ...] = Season.all
other_requirements: Tuple[Requirement, ...] = ()
def __post_init__(self):
assert self.money_price or self.items_price, "At least money price or items price need to be defined."
assert self.items_price is None or all(type(p) == tuple for p in self.items_price), "Items price should be a tuple."
@dataclass(frozen=True, **kw_only)
class MysteryBoxSource(ItemSource):
amount: int
@dataclass(frozen=True, **kw_only)
class ArtifactTroveSource(ItemSource):
amount: int
@dataclass(frozen=True, **kw_only)
class PrizeMachineSource(ItemSource):
amount: int
@dataclass(frozen=True, **kw_only)
class FishingTreasureChestSource(ItemSource):
amount: int

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass, field
from ..data.game_item import kw_only
@dataclass(frozen=True)
class Skill:
name: str
has_mastery: bool = field(**kw_only)

View File

@ -1,10 +1,10 @@
from dataclasses import dataclass
from typing import List, Tuple, Optional, Dict, Callable, Set
from typing import Tuple, Optional
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
from ..strings.region_names import Region, SVERegion, AlectoRegion, BoardingHouseRegion, LaceyRegion, LogicRegion
from ..strings.season_names import Season
from ..strings.villager_names import NPC, ModNPC
@ -36,7 +36,7 @@ carpenter = (Region.carpenter,)
alex_house = (Region.alex_house,)
elliott_house = (Region.elliott_house,)
ranch = (Region.ranch,)
mines_dwarf_shop = (Region.mines_dwarf_shop,)
mines_dwarf_shop = (LogicRegion.mines_dwarf_shop,)
desert = (Region.desert,)
oasis = (Region.oasis,)
sewers = (Region.sewer,)
@ -355,28 +355,10 @@ scarlett_loves = goat_cheese + duck_feather + goat_milk + cherry + maple_syrup +
susan_loves = pancakes + chocolate_cake + pink_cake + ice_cream + cookie + pumpkin_pie + rhubarb_pie + \
blueberry_tart + blackberry_cobbler + cranberry_candy + red_plate
all_villagers: List[Villager] = []
villager_modifications_by_mod: Dict[str, Dict[str, Callable[[str, Villager], Villager]]] = {}
def villager(name: str, bachelor: bool, locations: Tuple[str, ...], birthday: str, gifts: Tuple[str, ...],
available: bool, mod_name: Optional[str] = None) -> Villager:
npc = Villager(name, bachelor, locations, birthday, gifts, available, mod_name)
all_villagers.append(npc)
return npc
def adapt_wizard_to_sve(mod_name: str, npc: Villager):
if npc.mod_name:
mod_name = npc.mod_name
# The wizard leaves his tower on sunday, for like 1 hour... Good enough to meet him!
return Villager(npc.name, True, npc.locations + forest, npc.birthday, npc.gifts, npc.available, mod_name)
def register_villager_modification(mod_name: str, npc: Villager, modification_function):
if mod_name not in villager_modifications_by_mod:
villager_modifications_by_mod[mod_name] = {}
villager_modifications_by_mod[mod_name][npc.name] = modification_function
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)
@ -385,18 +367,18 @@ harvey = villager(NPC.harvey, True, town + hospital, Season.winter, universal_lo
sam = villager(NPC.sam, True, town, 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)
best_girl = villager(NPC.abigail, True, town, Season.fall, universal_loves + abigail_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)
hoe = villager(NPC.haley, True, town, Season.spring, universal_loves_no_prismatic_shard + haley_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)
nerd = villager(NPC.maru, True, carpenter + hospital + town, Season.summer, universal_loves + maru_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)
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)
gilf = villager(NPC.evelyn, False, town, Season.winter, universal_loves + evelyn_loves, True)
boomer = villager(NPC.george, False, town, Season.fall, universal_loves + george_loves, True)
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)
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)
@ -408,7 +390,7 @@ linus = villager(NPC.linus, False, mountain, Season.winter, universal_loves + li
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)
milf = villager(NPC.robin, False, carpenter, Season.fall, universal_loves + robin_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)
@ -443,54 +425,10 @@ sophia = villager(ModNPC.sophia, True, bluemoon, Season.winter, universal_loves_
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)
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.jasper_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.jasper_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)
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)
# Modified villagers; not included in all villagers
register_villager_modification(ModNames.sve, wizard, adapt_wizard_to_sve)
all_villagers_by_name: Dict[str, Villager] = {villager.name: villager for villager in all_villagers}
all_villagers_by_mod: Dict[str, List[Villager]] = {}
all_villagers_by_mod_by_name: Dict[str, Dict[str, Villager]] = {}
for npc in all_villagers:
mod = npc.mod_name
name = npc.name
if mod in all_villagers_by_mod:
all_villagers_by_mod[mod].append(npc)
all_villagers_by_mod_by_name[mod][name] = npc
else:
all_villagers_by_mod[mod] = [npc]
all_villagers_by_mod_by_name[mod] = {}
all_villagers_by_mod_by_name[mod][name] = npc
def villager_included_for_any_mod(npc: Villager, mods: Set[str]):
if not npc.mod_name:
return True
for mod in npc.mod_name.split(","):
if mod in mods:
return True
return False
def get_villagers_for_mods(mods: Set[str]) -> List[Villager]:
villagers_for_current_mods = []
for npc in all_villagers:
if not villager_included_for_any_mod(npc, mods):
continue
modified_npc = npc
for active_mod in mods:
if (active_mod not in villager_modifications_by_mod or
npc.name not in villager_modifications_by_mod[active_mod]):
continue
modification = villager_modifications_by_mod[active_mod][npc.name]
modified_npc = modification(active_mod, modified_npc)
villagers_for_current_mods.append(modified_npc)
return villagers_for_current_mods

View File

@ -17,6 +17,7 @@ may be useful to the player.
## What is the goal of Stardew Valley?
The player can choose from a number of goals, using their YAML options.
- Complete the [Community Center](https://stardewvalleywiki.com/Bundles)
- Succeed [Grandpa's Evaluation](https://stardewvalleywiki.com/Grandpa) with 4 lit candles
- Reach the bottom of the [Pelican Town Mineshaft](https://stardewvalleywiki.com/The_Mines)
@ -45,6 +46,7 @@ to "Exclude Legendaries", and pick the Master Angler goal, you will not need to
## What are location checks in Stardew Valley?
Location checks in Stardew Valley always include:
- [Community Center Bundles](https://stardewvalleywiki.com/Bundles)
- [Mineshaft Chest Rewards](https://stardewvalleywiki.com/The_Mines#Remixed_Rewards)
- [Traveling Merchant Items](https://stardewvalleywiki.com/Traveling_Cart)
@ -53,11 +55,12 @@ Location checks in Stardew Valley always include:
[Grim Reaper Statue](https://stardewvalleywiki.com/Golden_Scythe), etc
There also are a number of location checks that are optional, and individual players choose to include them or not in their shuffling:
- [Tools and Fishing Rod Upgrades](https://stardewvalleywiki.com/Tools)
- [Carpenter Buildings](https://stardewvalleywiki.com/Carpenter%27s_Shop#Farm_Buildings)
- [Backpack Upgrades](https://stardewvalleywiki.com/Tools#Other_Tools)
- [Mine Elevator Levels](https://stardewvalleywiki.com/The_Mines#Staircases)
- [Skill Levels](https://stardewvalleywiki.com/Skills)
- [Skill Levels](https://stardewvalleywiki.com/Skills) and [Masteries](https://stardewvalleywiki.com/Mastery_Cave#Masteries)
- Arcade Machines
- [Story Quests](https://stardewvalleywiki.com/Quests#List_of_Story_Quests)
- [Help Wanted Quests](https://stardewvalleywiki.com/Quests#Help_Wanted_Quests)
@ -73,6 +76,8 @@ There also are a number of location checks that are optional, and individual pla
- [Chefsanity](https://stardewvalleywiki.com/Cooking#Recipes): Learning cooking recipes
- [Craftsanity](https://stardewvalleywiki.com/Crafting): Crafting individual items
- [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
## Which items can be in another player's world?
@ -80,31 +85,39 @@ Every normal reward from the above locations can be in another player's world.
For the locations which do not include a normal reward, Resource Packs and traps are instead added to the pool. Traps are optional.
A player can enable some options that will add some items to the pool that are relevant to progression
- Seasons Randomizer:
- All 4 seasons will be items, and one of them will be selected randomly and be added to the player's start inventory.
- At the end of each month, the player can choose the next season, instead of following the vanilla season order. On Seasons Randomizer, they can only choose from the seasons they have received.
- At the end of each month, the player can choose the next season, instead of following the vanilla season order. On Seasons Randomizer, they can only
choose from the seasons they have received.
- Cropsanity:
- Every single seed in the game starts off locked and cannot be purchased from any merchant. Their unlocks are received as multiworld items. Growing each seed and harvesting the resulting crop sends a location check
- The way merchants sell seeds is considerably changed. Pierre sells fewer seeds at a high price, while Joja sells unlimited seeds but in huge discount packs, not individually.
- Every single seed in the game starts off locked and cannot be purchased from any merchant. Their unlocks are received as multiworld items. Growing each
seed and harvesting the resulting crop sends a location check
- The way merchants sell seeds is considerably changed. Pierre sells fewer seeds at a high price, while Joja sells unlimited seeds but in huge discount
packs, not individually.
- Museumsanity:
- The items that are normally obtained from museum donation milestones are added to the item pool. Some items, like the magic rock candy, are duplicated for convenience.
- The Traveling Merchant now sells artifacts and minerals, with a bias towards undonated ones, to mitigate randomness. She will sell these items as the player receives "Traveling Merchant Metal Detector" items.
- The items that are normally obtained from museum donation milestones are added to the item pool. Some items, like the magic rock candy, are duplicated for
convenience.
- The Traveling Merchant now sells artifacts and minerals, with a bias towards undonated ones, to mitigate randomness. She will sell these items as the
player receives "Traveling Merchant Metal Detector" items.
- TV Channels
- Babies
- Only if Friendsanity is enabled
There are a few extra vanilla items, which are added to the pool for convenience, but do not have a matching location. These include
- [Wizard Buildings](https://stardewvalleywiki.com/Wizard%27s_Tower#Buildings)
- [Return Scepter](https://stardewvalleywiki.com/Return_Scepter)
- [Qi Walnut Room QoL items](https://stardewvalleywiki.com/Qi%27s_Walnut_Room#Stock)
And lastly, some Archipelago-exclusive items exist in the pool, which are designed around game balance and QoL. These include:
- Arcade Machine buffs (Only if the arcade machines are randomized)
- Journey of the Prairie King has drop rate increases, extra lives, and equipment
- Junimo Kart has extra lives.
- Permanent Movement Speed Bonuses (customizable)
- Permanent Luck Bonuses (customizable)
- Traveling Merchant buffs
- Various Permanent Player Buffs (customizable)
- Traveling Merchant modifiers
## When the player receives an item, what happens?
@ -131,17 +144,14 @@ List of supported mods:
- General
- [Stardew Valley Expanded](https://www.nexusmods.com/stardewvalley/mods/3753)
- [DeepWoods](https://www.nexusmods.com/stardewvalley/mods/2571)
- [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)
- [Distant Lands - Witch Swamp Overhaul](https://www.nexusmods.com/stardewvalley/mods/18109)
- Skills
- [Magic](https://www.nexusmods.com/stardewvalley/mods/2007)
- [Luck Skill](https://www.nexusmods.com/stardewvalley/mods/521)
- [Socializing Skill](https://www.nexusmods.com/stardewvalley/mods/14142)
- [Archaeology](https://www.nexusmods.com/stardewvalley/mods/15793)
- [Cooking Skill](https://www.nexusmods.com/stardewvalley/mods/522)
- [Archaeology](https://www.nexusmods.com/stardewvalley/mods/22199)
- [Binning Skill](https://www.nexusmods.com/stardewvalley/mods/14073)
- NPCs
- [Ayeisha - The Postal Worker (Custom NPC)](https://www.nexusmods.com/stardewvalley/mods/6427)
@ -149,12 +159,7 @@ List of supported mods:
- [Juna - Roommate NPC](https://www.nexusmods.com/stardewvalley/mods/8606)
- [Professor Jasper Thomas](https://www.nexusmods.com/stardewvalley/mods/5599)
- [Alec Revisited](https://www.nexusmods.com/stardewvalley/mods/10697)
- [Custom NPC - Yoba](https://www.nexusmods.com/stardewvalley/mods/14871)
- [Custom NPC Eugene](https://www.nexusmods.com/stardewvalley/mods/9222)
- ['Prophet' Wellwick](https://www.nexusmods.com/stardewvalley/mods/6462)
- [Shiko - New Custom NPC](https://www.nexusmods.com/stardewvalley/mods/3732)
- [Delores - Custom NPC](https://www.nexusmods.com/stardewvalley/mods/5510)
- [Custom NPC - Riley](https://www.nexusmods.com/stardewvalley/mods/5811)
- [Alecto the Witch](https://www.nexusmods.com/stardewvalley/mods/10671)
Some of these mods might need a patch mod to tie the randomizer with the mod. These can be found
@ -164,5 +169,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 player, 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.
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.

View File

@ -2,14 +2,10 @@
## Required Software
- Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/))
- You need version 1.5.6. It is available in a public beta branch on Steam ![image](https://i.imgur.com/uKAUmF0.png).
- If your Stardew is not on Steam, you are responsible for finding a way to downgrade it.
- This measure is temporary. We are working hard to bring the mod to Stardew 1.6 as soon as possible.
- SMAPI 3.x.x ([Mod loader for Stardew Valley](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files))
- Same as Stardew Valley itself, SMAPI needs a slightly older version to be compatible with Stardew Valley 1.5.6 ![image](https://i.imgur.com/kzgObHy.png)
- [StardewArchipelago Mod Release 5.x.x](https://github.com/agilbert1412/StardewArchipelago/releases)
- It is important to use a mod release of version 5.x.x to play seeds that have been generated here. Later releases
- 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
can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet.
## Optional Software
@ -38,7 +34,7 @@ You can customize your options by visiting the [Stardew Valley Player Options Pa
### Installing the mod
- Install [SMAPI version 3.x.x](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files) by following the instructions on the mod page
- Install [SMAPI](https://www.nexusmods.com/stardewvalley/mods/2400?tab=files) by following the instructions on the mod page
- Download and extract the [StardewArchipelago](https://github.com/agilbert1412/StardewArchipelago/releases) mod into
your Stardew Valley "Mods" folder
- *OPTIONAL*: If you want to launch your game through Steam, add the following to your Stardew Valley launch options: `"[PATH TO STARDEW VALLEY]\Stardew Valley\StardewModdingAPI.exe" %command%`
@ -93,7 +89,7 @@ Stardew-exclusive commands.
### Playing with supported mods
See the [Supported mods documentation](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md)
See the [Supported mods documentation](https://github.com/agilbert1412/StardewArchipelago/blob/6.x.x/Documentation/Supported%20Mods.md)
### Multiplayer

View File

@ -1,51 +1,69 @@
from random import Random
from .options import BuildingProgression, StardewValleyOptions, BackpackProgression, ExcludeGingerIsland, SeasonRandomization, SpecialOrderLocations, \
Monstersanity, ToolProgression, SkillProgression, Cooksanity, Chefsanity
from . import options as stardew_options
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.tv_channel_names import Channel
from .strings.wallet_item_names import Wallet
early_candidate_rate = 4
always_early_candidates = ["Greenhouse", "Desert Obelisk", "Rusty Key"]
seasons = ["Spring", "Summer", "Fall", "Winter"]
always_early_candidates = [Region.greenhouse, Transportation.desert_obelisk, Wallet.rusty_key]
seasons = [Season.spring, Season.summer, Season.fall, Season.winter]
def setup_early_items(multiworld, options: StardewValleyOptions, player: int, random: Random):
def setup_early_items(multiworld, options: stardew_options.StardewValleyOptions, player: int, random: Random):
early_forced = []
early_candidates = []
early_candidates.extend(always_early_candidates)
add_seasonal_candidates(early_candidates, options)
if options.building_progression & BuildingProgression.option_progressive:
early_forced.append("Shipping Bin")
if options.building_progression & stardew_options.BuildingProgression.option_progressive:
early_forced.append(Building.shipping_bin)
if options.farm_type != stardew_options.FarmType.option_meadowlands:
early_candidates.append("Progressive Coop")
early_candidates.append("Progressive Barn")
if options.backpack_progression == BackpackProgression.option_early_progressive:
if options.backpack_progression == stardew_options.BackpackProgression.option_early_progressive:
early_forced.append("Progressive Backpack")
if options.tool_progression & ToolProgression.option_progressive:
early_forced.append("Progressive Fishing Rod")
if options.tool_progression & stardew_options.ToolProgression.option_progressive:
if options.fishsanity != stardew_options.Fishsanity.option_none:
early_candidates.append("Progressive Fishing Rod")
early_forced.append("Progressive Pickaxe")
if options.skill_progression == SkillProgression.option_progressive:
if options.skill_progression == stardew_options.SkillProgression.option_progressive:
early_forced.append("Fishing Level")
if options.quest_locations >= 0:
early_candidates.append("Magnifying Glass")
early_candidates.append(Wallet.magnifying_glass)
if options.special_order_locations != SpecialOrderLocations.option_disabled:
if options.special_order_locations & stardew_options.SpecialOrderLocations.option_board:
early_candidates.append("Special Order Board")
if options.cooksanity != Cooksanity.option_none | options.chefsanity & Chefsanity.option_queen_of_sauce:
early_candidates.append("The Queen of Sauce")
if options.cooksanity != stardew_options.Cooksanity.option_none or options.chefsanity & stardew_options.Chefsanity.option_queen_of_sauce:
early_candidates.append(Channel.queen_of_sauce)
if options.monstersanity == Monstersanity.option_none:
early_candidates.append("Progressive Weapon")
if options.craftsanity != stardew_options.Craftsanity.option_none:
early_candidates.append("Furnace Recipe")
if options.monstersanity == stardew_options.Monstersanity.option_none:
early_candidates.append(APWeapon.weapon)
else:
early_candidates.append("Progressive Sword")
early_candidates.append(APWeapon.sword)
if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
early_candidates.append("Island Obelisk")
if options.exclude_ginger_island == stardew_options.ExcludeGingerIsland.option_false:
early_candidates.append(Transportation.island_obelisk)
if options.walnutsanity.value:
early_candidates.append("Island North Turtle")
early_candidates.append("Island West Turtle")
if options.museumsanity != stardew_options.Museumsanity.option_none or options.shipsanity >= stardew_options.Shipsanity.option_full_shipment:
early_candidates.append(Wallet.metal_detector)
early_forced.extend(random.sample(early_candidates, len(early_candidates) // early_candidate_rate))
@ -56,10 +74,10 @@ def setup_early_items(multiworld, options: StardewValleyOptions, player: int, ra
def add_seasonal_candidates(early_candidates, options):
if options.season_randomization == SeasonRandomization.option_progressive:
early_candidates.extend(["Progressive Season"] * 3)
if options.season_randomization == stardew_options.SeasonRandomization.option_progressive:
early_candidates.extend([Season.progressive] * 3)
return
if options.season_randomization == SeasonRandomization.option_disabled:
if options.season_randomization == stardew_options.SeasonRandomization.option_disabled:
return
early_candidates.extend(seasons)

View File

@ -8,18 +8,20 @@ from typing import Dict, List, Protocol, Union, Set, Optional
from BaseClasses import Item, ItemClassification
from . import data
from .data.villagers_data import get_villagers_for_mods
from .content.feature import friendsanity
from .content.game_content import StardewContent
from .data.game_item import ItemTag
from .logic.logic_event import all_events
from .mods.mod_data import ModNames
from .options import StardewValleyOptions, TrapItems, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Cropsanity, \
Friendsanity, Museumsanity, \
Fishsanity, BuildingProgression, SkillProgression, ToolProgression, ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \
Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity
from .options import StardewValleyOptions, TrapItems, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Museumsanity, \
BuildingProgression, SkillProgression, ToolProgression, ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \
Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, EnabledFillerBuffs
from .strings.ap_names.ap_option_names import OptionName
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.event_names import Event
from .strings.ap_names.mods.mod_items import SVEQuestItem
from .strings.villager_names import NPC, ModNPC
from .strings.currency_names import Currency
from .strings.wallet_item_names import Wallet
ITEM_CODE_OFFSET = 717000
@ -44,6 +46,7 @@ class Group(enum.Enum):
WEAPON_SLINGSHOT = enum.auto()
PROGRESSIVE_TOOLS = enum.auto()
SKILL_LEVEL_UP = enum.auto()
SKILL_MASTERY = enum.auto()
BUILDING = enum.auto()
WIZARD_BUILDING = enum.auto()
ARCADE_MACHINE_BUFFS = enum.auto()
@ -62,6 +65,7 @@ class Group(enum.Enum):
FESTIVAL = enum.auto()
RARECROW = enum.auto()
TRAP = enum.auto()
BONUS = enum.auto()
MAXIMUM_ONE = enum.auto()
EXACTLY_TWO = enum.auto()
DEPRECATED = enum.auto()
@ -80,6 +84,9 @@ class Group(enum.Enum):
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()
@ -135,11 +142,8 @@ def load_item_csv():
events = [
ItemData(None, Event.victory, ItemClassification.progression),
ItemData(None, Event.can_construct_buildings, ItemClassification.progression),
ItemData(None, Event.start_dark_talisman_quest, ItemClassification.progression),
ItemData(None, Event.can_ship_items, ItemClassification.progression),
ItemData(None, Event.can_shop_at_pierre, ItemClassification.progression),
ItemData(None, e, ItemClassification.progression)
for e in sorted(all_events)
]
all_items: List[ItemData] = load_item_csv() + events
@ -168,9 +172,9 @@ def get_too_many_items_error_message(locations_count: int, items_count: int) ->
def create_items(item_factory: StardewItemFactory, item_deleter: StardewItemDeleter, locations_count: int, items_to_exclude: List[Item],
options: StardewValleyOptions, random: Random) -> List[Item]:
options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
items = []
unique_items = create_unique_items(item_factory, options, random)
unique_items = create_unique_items(item_factory, options, content, random)
remove_items(item_deleter, items_to_exclude, unique_items)
@ -213,11 +217,12 @@ def remove_items_if_no_room_for_them(item_deleter: StardewItemDeleter, unique_it
remove_items(item_deleter, items_to_remove, unique_items)
def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random) -> List[Item]:
def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
items = []
items.extend(item_factory(item) for item in items_by_group[Group.COMMUNITY_REWARD])
items.append(item_factory(CommunityUpgrade.movie_theater)) # It is a community reward, but we need two of them
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)
@ -233,25 +238,30 @@ def create_unique_items(item_factory: StardewItemFactory, options: StardewValley
items.append(item_factory(CommunityUpgrade.mushroom_boxes))
items.append(item_factory("Beach Bridge"))
create_tv_channels(item_factory, options, items)
create_special_quest_rewards(item_factory, options, items)
create_stardrops(item_factory, options, items)
create_quest_rewards(item_factory, options, items)
create_stardrops(item_factory, options, content, items)
create_museum_items(item_factory, options, items)
create_arcade_machine_items(item_factory, options, items)
create_player_buffs(item_factory, options, items)
create_movement_buffs(item_factory, options, items)
create_traveling_merchant_items(item_factory, items)
items.append(item_factory("Return Scepter"))
create_seasons(item_factory, options, items)
create_seeds(item_factory, options, items)
create_friendsanity_items(item_factory, options, items, random)
create_seeds(item_factory, content, items)
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_shipsanity_items(item_factory, options, items)
create_booksanity_items(item_factory, content, 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)
@ -259,6 +269,14 @@ def create_unique_items(item_factory: StardewItemFactory, options: StardewValley
return items
def create_raccoons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
number_progressive_raccoons = 9
if options.quest_locations < 0:
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):
@ -310,16 +328,29 @@ def create_tools(item_factory: StardewItemFactory, options: StardewValleyOptions
items.append(item_factory(item_data, ItemClassification.useful))
else:
items.extend([item_factory(item) for item in [item_data] * 4])
items.append(item_factory("Golden Scythe"))
if options.skill_progression == SkillProgression.option_progressive_with_masteries:
items.append(item_factory("Progressive Scythe"))
items.append(item_factory("Progressive Fishing Rod"))
items.append(item_factory("Progressive Scythe"))
def create_skills(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.skill_progression == SkillProgression.option_progressive:
if options.skill_progression == SkillProgression.option_vanilla:
return
for item in items_by_group[Group.SKILL_LEVEL_UP]:
if item.mod_name not in options.mods and item.mod_name is not None:
continue
items.extend(item_factory(item) for item in [item.name] * 10)
if options.skill_progression != SkillProgression.option_progressive_with_masteries:
return
for item in items_by_group[Group.SKILL_MASTERY]:
if item.mod_name not in options.mods and item.mod_name is not None:
continue
items.append(item_factory(item))
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
@ -360,6 +391,13 @@ def create_carpenter_buildings(item_factory: StardewItemFactory, options: Starde
items.append(item_factory("Tractor Garage"))
def create_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
create_special_quest_rewards(item_factory, options, items)
create_help_wanted_quest_rewards(item_factory, options, items)
create_quest_rewards_sve(item_factory, options, items)
def create_special_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.quest_locations < 0:
return
@ -373,21 +411,28 @@ def create_special_quest_rewards(item_factory: StardewItemFactory, options: Star
items.append(item_factory(Wallet.iridium_snake_milk))
items.append(item_factory("Fairy Dust Recipe"))
items.append(item_factory("Dark Talisman"))
create_special_quest_rewards_sve(item_factory, options, items)
create_distant_lands_quest_rewards(item_factory, options, items)
create_boarding_house_quest_rewards(item_factory, options, items)
def create_stardrops(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
def create_help_wanted_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.quest_locations <= 0:
return
number_help_wanted = options.quest_locations.value
quest_per_prize_ticket = 3
number_prize_tickets = number_help_wanted // quest_per_prize_ticket
items.extend(item_factory(item) for item in [Currency.prize_ticket] * number_prize_tickets)
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
if options.fishsanity != Fishsanity.option_none:
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
if options.friendsanity != Friendsanity.option_none:
if content.features.friendsanity.is_enabled:
items.append(item_factory("Stardrop", stardrops_classification)) # Spouse Stardrop
@ -403,39 +448,23 @@ def create_museum_items(item_factory: StardewItemFactory, options: StardewValley
items.append(item_factory(Wallet.metal_detector))
def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item], random: Random):
island_villagers = [NPC.leo, ModNPC.lance]
if options.friendsanity == Friendsanity.option_none:
def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item], random: Random):
if not content.features.friendsanity.is_enabled:
return
create_babies(item_factory, items, random)
exclude_non_bachelors = options.friendsanity == Friendsanity.option_bachelors
exclude_locked_villagers = options.friendsanity == Friendsanity.option_starting_npcs or \
options.friendsanity == Friendsanity.option_bachelors
include_post_marriage_hearts = options.friendsanity == Friendsanity.option_all_with_marriage
exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
mods = options.mods
heart_size = options.friendsanity_heart_size
for villager in get_villagers_for_mods(mods.value):
if not villager.available and exclude_locked_villagers:
continue
if not villager.bachelor and exclude_non_bachelors:
continue
if villager.name in island_villagers and exclude_ginger_island:
continue
heart_cap = 8 if villager.bachelor else 10
if include_post_marriage_hearts and villager.bachelor:
heart_cap = 14
classification = ItemClassification.progression
for heart in range(1, 15):
if heart > heart_cap:
break
if heart % heart_size == 0 or heart == heart_cap:
items.append(item_factory(f"{villager.name} <3", classification))
if not exclude_non_bachelors:
for villager in content.villagers.values():
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
for heart in range(1, 6):
if heart % heart_size == 0 or heart == 5:
items.append(item_factory(f"Pet <3", ItemClassification.progression_skip_balancing if need_pet else ItemClassification.useful))
pet_item_classification = ItemClassification.progression_skip_balancing if need_pet else ItemClassification.useful
for _ in content.features.friendsanity.get_pet_randomized_hearts():
items.append(item_factory(friendsanity.pet_heart_item_name, pet_item_classification))
def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random):
@ -462,26 +491,14 @@ def create_arcade_machine_items(item_factory: StardewItemFactory, options: Stard
items.extend(item_factory(item) for item in ["Junimo Kart: Extra Life"] * 8)
def create_player_buffs(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
def create_movement_buffs(item_factory, options: StardewValleyOptions, items: List[Item]):
movement_buffs: int = options.movement_buff_number.value
luck_buffs: int = options.luck_buff_number.value
need_all_buffs = options.special_order_locations == SpecialOrderLocations.option_board_qi
need_half_buffs = options.festival_locations == FestivalLocations.option_easy
create_player_buff(item_factory, Buff.movement, movement_buffs, need_all_buffs, need_half_buffs, items)
create_player_buff(item_factory, Buff.luck, luck_buffs, True, need_half_buffs, items)
def create_player_buff(item_factory, buff: str, amount: int, need_all_buffs: bool, need_half_buffs: bool, items: List[Item]):
progression_buffs = amount if need_all_buffs else (amount // 2 if need_half_buffs else 0)
useful_buffs = amount - progression_buffs
items.extend(item_factory(item) for item in [buff] * progression_buffs)
items.extend(item_factory(item, ItemClassification.useful) for item in [buff] * useful_buffs)
items.extend(item_factory(item) for item in [Buff.movement] * movement_buffs)
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
items.extend([*(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]),
*(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6),
*(item_factory(item) for item in ["Traveling Merchant Discount"] * 8)])
*(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6)])
def create_seasons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
@ -495,14 +512,11 @@ def create_seasons(item_factory: StardewItemFactory, options: StardewValleyOptio
items.extend([item_factory(item) for item in items_by_group[Group.SEASON]])
def create_seeds(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.cropsanity == Cropsanity.option_disabled:
def create_seeds(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
if not content.features.cropsanity.is_enabled:
return
base_seed_items = [item for item in items_by_group[Group.CROPSANITY]]
filtered_seed_items = remove_excluded_items(base_seed_items, options)
seed_items = [item_factory(item) for item in filtered_seed_items]
items.extend(seed_items)
items.extend(item_factory(item_table[seed.name]) for seed in content.find_tagged_items(ItemTag.CROPSANITY_SEED))
def create_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
@ -514,6 +528,35 @@ def create_festival_rewards(item_factory: StardewItemFactory, options: StardewVa
items.extend([*festival_rewards, item_factory("Stardrop", get_stardrop_classification(options))])
def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
walnutsanity = options.walnutsanity
if options.exclude_ginger_island == ExcludeGingerIsland.option_true or walnutsanity == Walnutsanity.preset_none:
return
# Give baseline walnuts just to be nice
num_single_walnuts = 0
num_triple_walnuts = 2
num_penta_walnuts = 1
# https://stardewvalleywiki.com/Golden_Walnut
# Totals should be accurate, but distribution is slightly offset to make room for baseline walnuts
if OptionName.walnutsanity_puzzles in walnutsanity: # 61
num_single_walnuts += 6 # 6
num_triple_walnuts += 5 # 15
num_penta_walnuts += 8 # 40
if OptionName.walnutsanity_bushes in walnutsanity: # 25
num_single_walnuts += 16 # 16
num_triple_walnuts += 3 # 9
if OptionName.walnutsanity_dig_spots in walnutsanity: # 18
num_single_walnuts += 18 # 18
if OptionName.walnutsanity_repeatables in walnutsanity: # 33
num_single_walnuts += 30 # 30
num_triple_walnuts += 1 # 3
items.extend([item_factory(item) for item in ["Golden Walnut"] * num_single_walnuts])
items.extend([item_factory(item) for item in ["3 Golden Walnuts"] * num_triple_walnuts])
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:
return
@ -526,11 +569,8 @@ def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: St
def create_special_order_board_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.special_order_locations == SpecialOrderLocations.option_disabled:
return
if options.special_order_locations & SpecialOrderLocations.option_board:
special_order_board_items = [item for item in items_by_group[Group.SPECIAL_ORDER_BOARD]]
items.extend([item_factory(item) for item in special_order_board_items])
@ -554,7 +594,7 @@ def create_special_order_qi_rewards(item_factory: StardewItemFactory, options: S
qi_gem_rewards.append("15 Qi Gems")
qi_gem_rewards.append("15 Qi Gems")
if options.special_order_locations == SpecialOrderLocations.option_board_qi:
if options.special_order_locations & SpecialOrderLocations.value_qi:
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"])
@ -607,6 +647,16 @@ 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]):
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())
def create_goal_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
goal = options.goal
if goal != Goal.option_perfection and goal != Goal.option_complete_collection:
@ -643,37 +693,31 @@ def create_deepwoods_pendants(item_factory: StardewItemFactory, options: Stardew
items.extend([item_factory(item) for item in ["Pendant of Elders", "Pendant of Community", "Pendant of Depths"]])
def create_special_quest_rewards_sve(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
def create_sve_special_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if ModNames.sve not in options.mods:
return
items.extend([item_factory(item) for item in items_by_group[Group.MOD_WARP] if item.mod_name == ModNames.sve])
if options.quest_locations < 0:
def create_quest_rewards_sve(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if ModNames.sve not in options.mods:
return
exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items])
if not exclude_ginger_island:
items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items_ginger_island])
if options.quest_locations < 0:
return
items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items])
if exclude_ginger_island:
return
items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items_ginger_island])
def create_distant_lands_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.quest_locations < 0 or ModNames.distant_lands not in options.mods:
return
items.append(item_factory("Crayfish Soup Recipe"))
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return
items.append(item_factory("Ginger Tincture Recipe"))
def create_boarding_house_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.quest_locations < 0 or ModNames.boarding_house not in options.mods:
return
items.append(item_factory("Special Pumpkin Soup Recipe"))
def create_unique_filler_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
available_item_slots: int) -> List[Item]:
items = []
@ -699,18 +743,21 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options
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 = [pack for pack in items_by_group[Group.TRAP]
if pack.name not in items_already_added_names and
(pack.mod_name is None or pack.mod_name in options.mods)]
trap_items = [trap for trap in items_by_group[Group.TRAP]
if trap.name not in items_already_added_names and
(trap.mod_name is None or trap.mod_name in options.mods)]
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)
@ -776,7 +823,7 @@ def remove_limited_amount_packs(packs):
return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.EXACTLY_TWO not in pack.groups]
def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool):
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:
@ -785,6 +832,33 @@ def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool):
return all_filler_items
def get_allowed_player_buffs(buff_option: EnabledFillerBuffs) -> List[ItemData]:
allowed_buffs = []
if OptionName.buff_luck in buff_option:
allowed_buffs.append(item_table[Buff.luck])
if OptionName.buff_damage in buff_option:
allowed_buffs.append(item_table[Buff.damage])
if OptionName.buff_defense in buff_option:
allowed_buffs.append(item_table[Buff.defense])
if OptionName.buff_immunity in buff_option:
allowed_buffs.append(item_table[Buff.immunity])
if OptionName.buff_health in buff_option:
allowed_buffs.append(item_table[Buff.health])
if OptionName.buff_energy in buff_option:
allowed_buffs.append(item_table[Buff.energy])
if OptionName.buff_bite in buff_option:
allowed_buffs.append(item_table[Buff.bite_rate])
if OptionName.buff_fish_trap in buff_option:
allowed_buffs.append(item_table[Buff.fish_trap])
if OptionName.buff_fishing_bar in buff_option:
allowed_buffs.append(item_table[Buff.fishing_bar])
if OptionName.buff_quality in buff_option:
allowed_buffs.append(item_table[Buff.quality])
if OptionName.buff_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

View File

@ -6,17 +6,17 @@ from typing import Optional, Dict, Protocol, List, FrozenSet, Iterable
from . import data
from .bundles.bundle_room import BundleRoom
from .data.fish_data import special_fish, get_fish_for_mods
from .content.game_content import StardewContent
from .data.game_item import ItemTag
from .data.museum_data import all_museum_items
from .data.villagers_data import get_villagers_for_mods
from .mods.mod_data import ModNames
from .options import ExcludeGingerIsland, Friendsanity, ArcadeMachineLocations, SpecialOrderLocations, Cropsanity, Fishsanity, Museumsanity, FestivalLocations, \
SkillProgression, BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression
from .options import ExcludeGingerIsland, ArcadeMachineLocations, SpecialOrderLocations, Museumsanity, \
FestivalLocations, SkillProgression, BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression, FarmType
from .options import StardewValleyOptions, Craftsanity, Chefsanity, Cooksanity, Shipsanity, Monstersanity
from .strings.goal_names import Goal
from .strings.quest_names import ModQuest
from .strings.region_names import Region
from .strings.villager_names import NPC, ModNPC
from .strings.quest_names import ModQuest, Quest
from .strings.region_names import Region, LogicRegion
from .strings.villager_names import NPC
LOCATION_CODE_OFFSET = 717000
@ -32,6 +32,7 @@ class LocationTags(enum.Enum):
BULLETIN_BOARD_BUNDLE = enum.auto()
VAULT_BUNDLE = enum.auto()
COMMUNITY_CENTER_ROOM = enum.auto()
RACCOON_BUNDLES = enum.auto()
BACKPACK = enum.auto()
TOOL_UPGRADE = enum.auto()
HOE_UPGRADE = enum.auto()
@ -40,6 +41,7 @@ class LocationTags(enum.Enum):
WATERING_CAN_UPGRADE = enum.auto()
TRASH_CAN_UPGRADE = enum.auto()
FISHING_ROD_UPGRADE = enum.auto()
PAN_UPGRADE = enum.auto()
THE_MINES_TREASURE = enum.auto()
CROPSANITY = enum.auto()
ELEVATOR = enum.auto()
@ -49,6 +51,7 @@ class LocationTags(enum.Enum):
FORAGING_LEVEL = enum.auto()
COMBAT_LEVEL = enum.auto()
MINING_LEVEL = enum.auto()
MASTERY_LEVEL = enum.auto()
BUILDING_BLUEPRINT = enum.auto()
STORY_QUEST = enum.auto()
ARCADE_MACHINE = enum.auto()
@ -63,11 +66,18 @@ class LocationTags(enum.Enum):
FRIENDSANITY = enum.auto()
FESTIVAL = enum.auto()
FESTIVAL_HARD = enum.auto()
DESERT_FESTIVAL_CHEF = enum.auto()
SPECIAL_ORDER_BOARD = enum.auto()
SPECIAL_ORDER_QI = enum.auto()
REQUIRES_QI_ORDERS = enum.auto()
REQUIRES_MASTERIES = enum.auto()
GINGER_ISLAND = enum.auto()
WALNUT_PURCHASE = enum.auto()
WALNUTSANITY = enum.auto()
WALNUTSANITY_PUZZLE = enum.auto()
WALNUTSANITY_BUSH = enum.auto()
WALNUTSANITY_DIG = enum.auto()
WALNUTSANITY_REPEATABLE = enum.auto()
BABY = enum.auto()
MONSTERSANITY = enum.auto()
@ -87,6 +97,10 @@ class LocationTags(enum.Enum):
CHEFSANITY_SKILL = enum.auto()
CHEFSANITY_STARTER = enum.auto()
CRAFTSANITY = enum.auto()
BOOKSANITY = enum.auto()
BOOKSANITY_POWER = enum.auto()
BOOKSANITY_SKILL = enum.auto()
BOOKSANITY_LOST = enum.auto()
# Mods
# Skill Mods
LUCK_LEVEL = enum.auto()
@ -143,10 +157,10 @@ events_locations = [
LocationData(None, Region.farm_house, Goal.full_house),
LocationData(None, Region.island_west, Goal.greatest_walnut_hunter),
LocationData(None, Region.adventurer_guild, Goal.protector_of_the_valley),
LocationData(None, Region.shipping, Goal.full_shipment),
LocationData(None, Region.kitchen, Goal.gourmet_chef),
LocationData(None, LogicRegion.shipping, Goal.full_shipment),
LocationData(None, LogicRegion.kitchen, Goal.gourmet_chef),
LocationData(None, Region.farm, Goal.craft_master),
LocationData(None, Region.shipping, Goal.legend),
LocationData(None, LogicRegion.shipping, Goal.legend),
LocationData(None, Region.farm, Goal.mystery_of_the_stardrops),
LocationData(None, Region.farm, Goal.allsanity),
LocationData(None, Region.qi_walnut_room, Goal.perfection),
@ -168,13 +182,13 @@ def initialize_groups():
initialize_groups()
def extend_cropsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.cropsanity == Cropsanity.option_disabled:
def extend_cropsanity_locations(randomized_locations: List[LocationData], content: StardewContent):
cropsanity = content.features.cropsanity
if not cropsanity.is_enabled:
return
cropsanity_locations = [item for item in locations_by_tag[LocationTags.CROPSANITY] if not item.mod_name or item.mod_name in options.mods]
cropsanity_locations = filter_ginger_island(options, cropsanity_locations)
randomized_locations.extend(cropsanity_locations)
randomized_locations.extend(location_table[cropsanity.to_location_name(item.name)]
for item in content.find_tagged_items(ItemTag.CROPSANITY))
def extend_quests_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
@ -199,32 +213,19 @@ def extend_quests_locations(randomized_locations: List[LocationData], options: S
randomized_locations.append(location_table[f"Help Wanted: Gathering {batch + 1}"])
def extend_fishsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
prefix = "Fishsanity: "
fishsanity = options.fishsanity
active_fish = get_fish_for_mods(options.mods.value)
if fishsanity == Fishsanity.option_none:
def extend_fishsanity_locations(randomized_locations: List[LocationData], content: StardewContent, random: Random):
fishsanity = content.features.fishsanity
if not fishsanity.is_enabled:
return
elif fishsanity == Fishsanity.option_legendaries:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if fish.legendary]
randomized_locations.extend(filter_disabled_locations(options, fish_locations))
elif fishsanity == Fishsanity.option_special:
randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish)
elif fishsanity == Fishsanity.option_randomized:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if random.random() < 0.4]
randomized_locations.extend(filter_disabled_locations(options, fish_locations))
elif fishsanity == Fishsanity.option_all:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish]
randomized_locations.extend(filter_disabled_locations(options, fish_locations))
elif fishsanity == Fishsanity.option_exclude_legendaries:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if not fish.legendary]
randomized_locations.extend(filter_disabled_locations(options, fish_locations))
elif fishsanity == Fishsanity.option_exclude_hard_fish:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if fish.difficulty < 80]
randomized_locations.extend(filter_disabled_locations(options, fish_locations))
elif options.fishsanity == Fishsanity.option_only_easy_fish:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if fish.difficulty < 50]
randomized_locations.extend(filter_disabled_locations(options, fish_locations))
for fish in content.fishes.values():
if not fishsanity.is_included(fish):
continue
if fishsanity.is_randomized and random.random() >= fishsanity.randomization_ratio:
continue
randomized_locations.append(location_table[fishsanity.to_location_name(fish.name)])
def extend_museumsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
@ -240,38 +241,20 @@ def extend_museumsanity_locations(randomized_locations: List[LocationData], opti
randomized_locations.extend(location_table[f"{prefix}{museum_item.item_name}"] for museum_item in all_museum_items)
def extend_friendsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
island_villagers = [NPC.leo, ModNPC.lance]
if options.friendsanity == Friendsanity.option_none:
def extend_friendsanity_locations(randomized_locations: List[LocationData], content: StardewContent):
friendsanity = content.features.friendsanity
if not friendsanity.is_enabled:
return
randomized_locations.append(location_table[f"Spouse Stardrop"])
extend_baby_locations(randomized_locations)
exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_non_bachelors = options.friendsanity == Friendsanity.option_bachelors
exclude_locked_villagers = options.friendsanity == Friendsanity.option_starting_npcs or \
options.friendsanity == Friendsanity.option_bachelors
include_post_marriage_hearts = options.friendsanity == Friendsanity.option_all_with_marriage
heart_size = options.friendsanity_heart_size
for villager in get_villagers_for_mods(options.mods.value):
if not villager.available and exclude_locked_villagers:
continue
if not villager.bachelor and exclude_non_bachelors:
continue
if villager.name in island_villagers and exclude_ginger_island:
continue
heart_cap = 8 if villager.bachelor else 10
if include_post_marriage_hearts and villager.bachelor:
heart_cap = 14
for heart in range(1, 15):
if heart > heart_cap:
break
if heart % heart_size == 0 or heart == heart_cap:
randomized_locations.append(location_table[f"Friendsanity: {villager.name} {heart} <3"])
if not exclude_non_bachelors:
for heart in range(1, 6):
if heart % heart_size == 0 or heart == 5:
randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"])
for villager in content.villagers.values():
for heart in friendsanity.get_randomized_hearts(villager):
randomized_locations.append(location_table[friendsanity.to_location_name(villager.name, heart)])
for heart in friendsanity.get_pet_randomized_hearts():
randomized_locations.append(location_table[friendsanity.to_location_name(NPC.pet, heart)])
def extend_baby_locations(randomized_locations: List[LocationData]):
@ -279,16 +262,17 @@ def extend_baby_locations(randomized_locations: List[LocationData]):
randomized_locations.extend(baby_locations)
def extend_festival_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
def extend_festival_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
if options.festival_locations == FestivalLocations.option_disabled:
return
festival_locations = locations_by_tag[LocationTags.FESTIVAL]
randomized_locations.extend(festival_locations)
extend_hard_festival_locations(randomized_locations, options)
extend_desert_festival_chef_locations(randomized_locations, options, random)
def extend_hard_festival_locations(randomized_locations, options: StardewValleyOptions):
def extend_hard_festival_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.festival_locations != FestivalLocations.option_hard:
return
@ -296,14 +280,20 @@ def extend_hard_festival_locations(randomized_locations, options: StardewValleyO
randomized_locations.extend(hard_festival_locations)
def extend_special_order_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.special_order_locations == SpecialOrderLocations.option_disabled:
return
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)
include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false
def extend_special_order_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.special_order_locations & SpecialOrderLocations.option_board:
board_locations = filter_disabled_locations(options, locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD])
randomized_locations.extend(board_locations)
if options.special_order_locations == SpecialOrderLocations.option_board_qi and include_island:
include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false
if options.special_order_locations & SpecialOrderLocations.value_qi and include_island:
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]
@ -440,13 +430,43 @@ def extend_craftsanity_locations(randomized_locations: List[LocationData], optio
return
craftsanity_locations = [craft for craft in locations_by_tag[LocationTags.CRAFTSANITY]]
filtered_chefsanity_locations = filter_disabled_locations(options, craftsanity_locations)
randomized_locations.extend(filtered_chefsanity_locations)
filtered_craftsanity_locations = filter_disabled_locations(options, craftsanity_locations)
randomized_locations.extend(filtered_craftsanity_locations)
def extend_book_locations(randomized_locations: List[LocationData], content: StardewContent):
booksanity = content.features.booksanity
if not booksanity.is_enabled:
return
book_locations = []
for book in content.find_tagged_items(ItemTag.BOOK):
if booksanity.is_included(book):
book_locations.append(location_table[booksanity.to_location_name(book.name)])
book_locations.extend(location_table[booksanity.to_location_name(book)] for book in booksanity.get_randomized_lost_books())
randomized_locations.extend(book_locations)
def extend_walnutsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if not options.walnutsanity:
return
if "Puzzles" in options.walnutsanity:
randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_PUZZLE])
if "Bushes" in options.walnutsanity:
randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_BUSH])
if "Dig Spots" in options.walnutsanity:
randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_DIG])
if "Repeatables" in options.walnutsanity:
randomized_locations.extend(locations_by_tag[LocationTags.WALNUTSANITY_REPEATABLE])
def create_locations(location_collector: StardewLocationCollector,
bundle_rooms: List[BundleRoom],
options: StardewValleyOptions,
content: StardewContent,
random: Random):
randomized_locations = []
@ -461,7 +481,10 @@ def create_locations(location_collector: StardewLocationCollector,
if not options.skill_progression == SkillProgression.option_vanilla:
for location in locations_by_tag[LocationTags.SKILL_LEVEL]:
if location.mod_name is None or location.mod_name in options.mods:
if location.mod_name is not None and location.mod_name not in options.mods:
continue
if LocationTags.MASTERY_LEVEL in location.tags and options.skill_progression != SkillProgression.option_progressive_with_masteries:
continue
randomized_locations.append(location_table[location.name])
if options.building_progression & BuildingProgression.option_progressive:
@ -475,12 +498,12 @@ def create_locations(location_collector: StardewLocationCollector,
if options.arcade_machine_locations == ArcadeMachineLocations.option_full_shuffling:
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE])
extend_cropsanity_locations(randomized_locations, options)
extend_fishsanity_locations(randomized_locations, options, random)
extend_cropsanity_locations(randomized_locations, content)
extend_fishsanity_locations(randomized_locations, content, random)
extend_museumsanity_locations(randomized_locations, options, random)
extend_friendsanity_locations(randomized_locations, options)
extend_friendsanity_locations(randomized_locations, content)
extend_festival_locations(randomized_locations, options)
extend_festival_locations(randomized_locations, options, random)
extend_special_order_locations(randomized_locations, options)
extend_walnut_purchase_locations(randomized_locations, options)
@ -490,28 +513,47 @@ def create_locations(location_collector: StardewLocationCollector,
extend_chefsanity_locations(randomized_locations, options)
extend_craftsanity_locations(randomized_locations, options)
extend_quests_locations(randomized_locations, options)
extend_book_locations(randomized_locations, content)
extend_walnutsanity_locations(randomized_locations, options)
# Mods
extend_situational_quest_locations(randomized_locations, options)
for location_data in randomized_locations:
location_collector(location_data.name, location_data.code, location_data.region)
def filter_farm_type(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)
else:
return (location for location in locations if location.name != Quest.feeding_animals)
def filter_ginger_island(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]:
include_island = options.exclude_ginger_island == ExcludeGingerIsland.option_false
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.option_board_qi
include_qi_orders = options.special_order_locations & SpecialOrderLocations.value_qi
return (location for location in locations if include_qi_orders or LocationTags.REQUIRES_QI_ORDERS not in location.tags)
def filter_masteries_locations(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]:
include_masteries = options.skill_progression == SkillProgression.option_progressive_with_masteries
return (location for location in locations if include_masteries or 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_disabled_locations(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]:
locations_island_filter = filter_ginger_island(options, locations)
locations_farm_filter = filter_farm_type(options, locations)
locations_island_filter = filter_ginger_island(options, locations_farm_filter)
locations_qi_filter = filter_qi_order_locations(options, locations_island_filter)
locations_mod_filter = filter_modded_locations(options, locations_qi_filter)
locations_masteries_filter = filter_masteries_locations(options, locations_qi_filter)
locations_mod_filter = filter_modded_locations(options, locations_masteries_filter)
return locations_mod_filter

View File

@ -1,6 +1,7 @@
from typing import Union
from .base_logic import BaseLogicMixin, BaseLogic
from .cooking_logic import CookingLogicMixin
from .mine_logic import MineLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin

View File

@ -5,10 +5,13 @@ from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from ..stardew_rule import StardewRule, True_, Or
from .tool_logic import ToolLogicMixin
from ..options import ToolProgression
from ..stardew_rule import StardewRule, True_
from ..strings.generic_names import Generic
from ..strings.geode_names import Geode
from ..strings.region_names import Region
from ..strings.tool_names import Tool
class ActionLogicMixin(BaseLogicMixin):
@ -17,7 +20,7 @@ class ActionLogicMixin(BaseLogicMixin):
self.action = ActionLogic(*args, **kwargs)
class ActionLogic(BaseLogic[Union[ActionLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
class ActionLogic(BaseLogic[Union[ActionLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, ToolLogicMixin]]):
def can_watch(self, channel: str = None):
tv_rule = True_()
@ -25,16 +28,13 @@ class ActionLogic(BaseLogic[Union[ActionLogicMixin, RegionLogicMixin, ReceivedLo
return tv_rule
return self.logic.received(channel) & tv_rule
def can_pan(self) -> StardewRule:
return self.logic.received("Glittering Boulder Removed") & self.logic.region.can_reach(Region.mountain)
def can_pan_at(self, region: str) -> StardewRule:
return self.logic.region.can_reach(region) & self.logic.action.can_pan()
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)
@cache_self1
def can_open_geode(self, geode: str) -> StardewRule:
blacksmith_access = self.logic.region.can_reach(Region.blacksmith)
geodes = [Geode.geode, Geode.frozen, Geode.magma, Geode.omni]
if geode == Generic.any:
return blacksmith_access & Or(*(self.logic.has(geode_type) for geode_type in geodes))
return blacksmith_access & self.logic.or_(*(self.logic.has(geode_type) for geode_type in geodes))
return blacksmith_access & self.logic.has(geode)

View File

@ -3,8 +3,13 @@ from typing import Union
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .time_logic import TimeLogicMixin
from ..data.artisan import MachineSource
from ..data.game_item import ItemTag
from ..stardew_rule import StardewRule
from ..strings.crop_names import all_vegetables, all_fruits, Vegetable, Fruit
from ..strings.artisan_good_names import ArtisanGood
from ..strings.crop_names import Vegetable, Fruit
from ..strings.fish_names import Fish, all_fish
from ..strings.forageable_names import Mushroom
from ..strings.generic_names import Generic
from ..strings.machine_names import Machine
@ -16,6 +21,10 @@ class ArtisanLogicMixin(BaseLogicMixin):
class ArtisanLogic(BaseLogic[Union[ArtisanLogicMixin, TimeLogicMixin, HasLogicMixin]]):
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})
def has_jelly(self) -> StardewRule:
return self.logic.artisan.can_preserves_jar(Fruit.any)
@ -23,31 +32,62 @@ class ArtisanLogic(BaseLogic[Union[ArtisanLogicMixin, TimeLogicMixin, HasLogicMi
def has_pickle(self) -> StardewRule:
return self.logic.artisan.can_preserves_jar(Vegetable.any)
def has_smoked_fish(self) -> StardewRule:
return self.logic.artisan.can_smoke(Fish.any)
def has_targeted_bait(self) -> StardewRule:
return self.logic.artisan.can_bait(Fish.any)
def has_dried_fruits(self) -> StardewRule:
return self.logic.artisan.can_dehydrate(Fruit.any)
def has_dried_mushrooms(self) -> StardewRule:
return self.logic.artisan.can_dehydrate(Mushroom.any_edible)
def has_raisins(self) -> StardewRule:
return self.logic.artisan.can_dehydrate(Fruit.grape)
def can_produce_from(self, source: MachineSource) -> StardewRule:
return self.logic.has(source.item) & self.logic.has(source.machine)
def can_preserves_jar(self, item: str) -> StardewRule:
machine_rule = self.logic.has(Machine.preserves_jar)
if item == Generic.any:
return machine_rule
if item == Fruit.any:
return machine_rule & self.logic.has_any(*all_fruits)
return machine_rule & self.logic.has_any(*(fruit.name for fruit in self.content.find_tagged_items(ItemTag.FRUIT)))
if item == Vegetable.any:
return machine_rule & self.logic.has_any(*all_vegetables)
return machine_rule & self.logic.has_any(*(vege.name for vege in self.content.find_tagged_items(ItemTag.VEGETABLE)))
return machine_rule & self.logic.has(item)
def has_wine(self) -> StardewRule:
return self.logic.artisan.can_keg(Fruit.any)
def has_juice(self) -> StardewRule:
return self.logic.artisan.can_keg(Vegetable.any)
def can_keg(self, item: str) -> StardewRule:
machine_rule = self.logic.has(Machine.keg)
if item == Generic.any:
return machine_rule
if item == Fruit.any:
return machine_rule & self.logic.has_any(*all_fruits)
return machine_rule & self.logic.has_any(*(fruit.name for fruit in self.content.find_tagged_items(ItemTag.FRUIT)))
if item == Vegetable.any:
return machine_rule & self.logic.has_any(*all_vegetables)
return machine_rule & self.logic.has_any(*(vege.name for vege in self.content.find_tagged_items(ItemTag.VEGETABLE)))
return machine_rule & self.logic.has(item)
def can_mayonnaise(self, item: str) -> StardewRule:
return self.logic.has(Machine.mayonnaise_machine) & self.logic.has(item)
def can_smoke(self, item: str) -> StardewRule:
machine_rule = self.logic.has(Machine.fish_smoker)
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)
def can_dehydrate(self, item: str) -> StardewRule:
machine_rule = self.logic.has(Machine.dehydrator)
if item == Generic.any:
return machine_rule
if item == Fruit.any:
# Grapes make raisins
return machine_rule & self.logic.has_any(*(fruit.name for fruit in self.content.find_tagged_items(ItemTag.FRUIT) if fruit.name != Fruit.grape))
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)

View File

@ -2,6 +2,7 @@ from __future__ import annotations
from typing import TypeVar, Generic, Dict, Collection
from ..content.game_content import StardewContent
from ..options import StardewValleyOptions
from ..stardew_rule import StardewRule
@ -10,12 +11,11 @@ class LogicRegistry:
def __init__(self):
self.item_rules: Dict[str, StardewRule] = {}
self.sapling_rules: Dict[str, StardewRule] = {}
self.tree_fruit_rules: Dict[str, StardewRule] = {}
self.seed_rules: Dict[str, StardewRule] = {}
self.cooking_rules: Dict[str, StardewRule] = {}
self.crafting_rules: Dict[str, StardewRule] = {}
self.crop_rules: Dict[str, StardewRule] = {}
self.artisan_good_rules: Dict[str, StardewRule] = {}
self.fish_rules: Dict[str, StardewRule] = {}
self.museum_rules: Dict[str, StardewRule] = {}
self.festival_rules: Dict[str, StardewRule] = {}
@ -38,13 +38,15 @@ class BaseLogic(BaseLogicMixin, Generic[T]):
player: int
registry: LogicRegistry
options: StardewValleyOptions
content: StardewContent
regions: Collection[str]
logic: T
def __init__(self, player: int, registry: LogicRegistry, options: StardewValleyOptions, regions: Collection[str], logic: T):
super().__init__(player, registry, options, regions, logic)
def __init__(self, player: int, registry: LogicRegistry, options: StardewValleyOptions, content: StardewContent, regions: Collection[str], logic: T):
super().__init__(player, registry, options, content, regions, logic)
self.player = player
self.registry = registry
self.options = options
self.content = content
self.regions = regions
self.logic = logic

View File

@ -0,0 +1,24 @@
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from ..stardew_rule import StardewRule
class BookLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.book = BookLogic(*args, **kwargs)
class BookLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin]]):
@cache_self1
def has_book_power(self, book: str) -> StardewRule:
booksanity = self.content.features.booksanity
if booksanity.is_included(self.content.game_items[book]):
return self.logic.received(booksanity.to_item_name(book))
else:
return self.logic.has(book)

View File

@ -1,23 +0,0 @@
from typing import Union
from .base_logic import BaseLogicMixin, BaseLogic
from .received_logic import ReceivedLogicMixin
from ..stardew_rule import StardewRule
from ..strings.ap_names.buff_names import Buff
class BuffLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.buff = BuffLogic(*args, **kwargs)
class BuffLogic(BaseLogic[Union[ReceivedLogicMixin]]):
def has_max_buffs(self) -> StardewRule:
return self.has_max_speed() & self.has_max_luck()
def has_max_speed(self) -> StardewRule:
return self.logic.received(Buff.movement, self.options.movement_buff_number.value)
def has_max_luck(self) -> StardewRule:
return self.logic.received(Buff.luck, self.options.luck_buff_number.value)

View File

@ -15,6 +15,8 @@ from ..strings.fish_names import WaterItem
from ..strings.material_names import Material
from ..strings.metal_names import MetalBar
has_group = "building"
class BuildingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
@ -42,7 +44,7 @@ class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionL
Building.well: self.logic.money.can_spend(1000) & self.logic.has(Material.stone),
Building.shipping_bin: self.logic.money.can_spend(250) & self.logic.has(Material.wood),
Building.kitchen: self.logic.money.can_spend(10000) & self.logic.has(Material.wood) & self.logic.building.has_house(0),
Building.kids_room: self.logic.money.can_spend(50000) & self.logic.has(Material.hardwood) & self.logic.building.has_house(1),
Building.kids_room: self.logic.money.can_spend(65000) & self.logic.has(Material.hardwood) & self.logic.building.has_house(1),
Building.cellar: self.logic.money.can_spend(100000) & self.logic.building.has_house(2),
# @formatter:on
})
@ -60,7 +62,7 @@ class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionL
carpenter_rule = self.logic.received(Event.can_construct_buildings)
if not self.options.building_progression & BuildingProgression.option_progressive:
return Has(building, self.registry.building_rules) & carpenter_rule
return Has(building, self.registry.building_rules, has_group) & carpenter_rule
count = 1
if building in [Building.coop, Building.barn, Building.shed]:
@ -86,10 +88,10 @@ class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionL
return carpenter_rule & self.logic.received(f"Progressive House", upgrade_level)
if upgrade_level == 1:
return carpenter_rule & Has(Building.kitchen, self.registry.building_rules)
return carpenter_rule & Has(Building.kitchen, self.registry.building_rules, has_group)
if upgrade_level == 2:
return carpenter_rule & Has(Building.kids_room, self.registry.building_rules)
return carpenter_rule & Has(Building.kids_room, self.registry.building_rules, has_group)
# if upgrade_level == 3:
return carpenter_rule & Has(Building.cellar, self.registry.building_rules)
return carpenter_rule & Has(Building.cellar, self.registry.building_rules, has_group)

View File

@ -2,17 +2,22 @@ from functools import cached_property
from typing import Union, List
from .base_logic import BaseLogicMixin, BaseLogic
from .farming_logic import FarmingLogicMixin
from .fishing_logic import FishingLogicMixin
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from .quality_logic import QualityLogicMixin
from .quest_logic import QuestLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .skill_logic import SkillLogicMixin
from .time_logic import TimeLogicMixin
from ..bundles.bundle import Bundle
from ..stardew_rule import StardewRule, And, True_
from ..stardew_rule import StardewRule, True_
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.currency_names import Currency
from ..strings.machine_names import Machine
from ..strings.quality_names import CropQuality, ForageQuality, FishQuality, ArtisanQuality
from ..strings.quest_names import Quest
from ..strings.region_names import Region
@ -22,21 +27,26 @@ class BundleLogicMixin(BaseLogicMixin):
self.bundle = BundleLogic(*args, **kwargs)
class BundleLogic(BaseLogic[Union[HasLogicMixin, RegionLogicMixin, MoneyLogicMixin, FarmingLogicMixin, FishingLogicMixin, SkillLogicMixin]]):
class BundleLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, MoneyLogicMixin, QualityLogicMixin, FishingLogicMixin, SkillLogicMixin,
QuestLogicMixin]]):
# Should be cached
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)
for bundle_item in bundle.items:
if Currency.is_currency(bundle_item.item_name):
return can_speak_junimo & self.logic.money.can_trade(bundle_item.item_name, bundle_item.amount)
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.item_name)
item_rules.append(bundle_item.get_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)
return can_speak_junimo & item_rules & quality_rules
time_rule = True_() if time_to_grind <= 0 else self.logic.time.has_lived_months(time_to_grind)
return can_speak_junimo & item_rules & quality_rules & time_rule
def get_quality_rules(self, qualities: List[str]) -> StardewRule:
crop_quality = CropQuality.get_highest(qualities)
@ -45,7 +55,7 @@ class BundleLogic(BaseLogic[Union[HasLogicMixin, RegionLogicMixin, MoneyLogicMix
artisan_quality = ArtisanQuality.get_highest(qualities)
quality_rules = []
if crop_quality != CropQuality.basic:
quality_rules.append(self.logic.farming.can_grow_crop_quality(crop_quality))
quality_rules.append(self.logic.quality.can_grow_crop_quality(crop_quality))
if fish_quality != FishQuality.basic:
quality_rules.append(self.logic.fishing.can_catch_quality_fish(fish_quality))
if forage_quality != ForageQuality.basic:
@ -54,7 +64,7 @@ class BundleLogic(BaseLogic[Union[HasLogicMixin, RegionLogicMixin, MoneyLogicMix
quality_rules.append(self.logic.has(Machine.cask))
if not quality_rules:
return True_()
return And(*quality_rules)
return self.logic.and_(*quality_rules)
@cached_property
def can_complete_community_center(self) -> StardewRule:
@ -64,3 +74,11 @@ class BundleLogic(BaseLogic[Union[HasLogicMixin, RegionLogicMixin, MoneyLogicMix
self.logic.region.can_reach_location("Complete Bulletin Board") &
self.logic.region.can_reach_location("Complete Vault") &
self.logic.region.can_reach_location("Complete Boiler Room"))
def can_access_raccoon_bundles(self) -> StardewRule:
if self.options.quest_locations < 0:
return self.logic.received(CommunityUpgrade.raccoon, 1) & self.logic.quest.can_complete_quest(Quest.giant_stump)
# 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)

View File

@ -3,10 +3,11 @@ from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from ..mods.logic.magic_logic import MagicLogicMixin
from ..stardew_rule import StardewRule, Or, False_
from ..stardew_rule import StardewRule, False_
from ..strings.ap_names.ap_weapon_names import APWeapon
from ..strings.performance_names import Performance
@ -19,7 +20,7 @@ class CombatLogicMixin(BaseLogicMixin):
self.combat = CombatLogic(*args, **kwargs)
class CombatLogic(BaseLogic[Union[CombatLogicMixin, RegionLogicMixin, ReceivedLogicMixin, MagicLogicMixin]]):
class CombatLogic(BaseLogic[Union[HasLogicMixin, CombatLogicMixin, RegionLogicMixin, ReceivedLogicMixin, MagicLogicMixin]]):
@cache_self1
def can_fight_at_level(self, level: str) -> StardewRule:
if level == Performance.basic:
@ -42,16 +43,20 @@ class CombatLogic(BaseLogic[Union[CombatLogicMixin, RegionLogicMixin, ReceivedLo
@cached_property
def has_decent_weapon(self) -> StardewRule:
return Or(*(self.logic.received(weapon, 2) for weapon in valid_weapons))
return self.logic.or_(*(self.logic.received(weapon, 2) for weapon in valid_weapons))
@cached_property
def has_good_weapon(self) -> StardewRule:
return Or(*(self.logic.received(weapon, 3) for weapon in valid_weapons))
return self.logic.or_(*(self.logic.received(weapon, 3) for weapon in valid_weapons))
@cached_property
def has_great_weapon(self) -> StardewRule:
return Or(*(self.logic.received(weapon, 4) for weapon in valid_weapons))
return self.logic.or_(*(self.logic.received(weapon, 4) for weapon in valid_weapons))
@cached_property
def has_galaxy_weapon(self) -> StardewRule:
return Or(*(self.logic.received(weapon, 5) for weapon in valid_weapons))
return self.logic.or_(*(self.logic.received(weapon, 5) for weapon in valid_weapons))
@cached_property
def has_slingshot(self) -> StardewRule:
return self.logic.received(APWeapon.slingshot)

View File

@ -19,8 +19,8 @@ from ..data.recipe_source import CutsceneSource, ShopTradeSource
from ..locations import locations_by_tag, LocationTags
from ..options import Chefsanity
from ..options import ExcludeGingerIsland
from ..stardew_rule import StardewRule, True_, False_, And
from ..strings.region_names import Region
from ..stardew_rule import StardewRule, True_, False_
from ..strings.region_names import LogicRegion
from ..strings.skill_names import Skill
from ..strings.tv_channel_names import Channel
@ -39,7 +39,7 @@ BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]
# Should be cached
def can_cook(self, recipe: CookingRecipe = None) -> StardewRule:
cook_rule = self.logic.region.can_reach(Region.kitchen)
cook_rule = self.logic.region.can_reach(LogicRegion.kitchen)
if recipe is None:
return cook_rule
@ -65,7 +65,7 @@ BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, QueenOfSauceSource) and self.options.chefsanity & Chefsanity.option_queen_of_sauce:
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, ShopFriendshipSource) and self.options.chefsanity & Chefsanity.option_friendship:
if isinstance(source, ShopFriendshipSource) and self.options.chefsanity & Chefsanity.option_purchases:
return self.logic.cooking.received_recipe(meal_name)
return self.logic.cooking.can_learn_recipe(source)
@ -105,4 +105,4 @@ BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]
continue
all_recipes_names.append(location.name[len(cooksanity_prefix):])
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return And(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))
return self.logic.and_(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))

View File

@ -13,12 +13,11 @@ from .skill_logic import SkillLogicMixin
from .special_order_logic import SpecialOrderLogicMixin
from .. import options
from ..data.craftable_data import CraftingRecipe, all_crafting_recipes_by_name
from ..data.recipe_data import StarterSource, ShopSource, SkillSource, FriendshipSource
from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \
FestivalShopSource, QuestSource
FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource
from ..locations import locations_by_tag, LocationTags
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland
from ..stardew_rule import StardewRule, True_, False_, And
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland, SkillProgression
from ..stardew_rule import StardewRule, True_, False_
from ..strings.region_names import Region
@ -58,7 +57,7 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
if isinstance(recipe.source, StarterSource) or isinstance(recipe.source, ShopTradeSource) or isinstance(
recipe.source, ShopSource):
return self.logic.crafting.received_recipe(recipe.item)
if isinstance(recipe.source, SpecialOrderSource) and self.options.special_order_locations != SpecialOrderLocations.option_disabled:
if isinstance(recipe.source, SpecialOrderSource) and self.options.special_order_locations & SpecialOrderLocations.option_board:
return self.logic.crafting.received_recipe(recipe.item)
return self.logic.crafting.can_learn_recipe(recipe)
@ -74,6 +73,8 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price)
if isinstance(recipe.source, SkillSource):
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level)
if isinstance(recipe.source, MasterySource):
return self.logic.skill.has_mastery(recipe.source.skill)
if isinstance(recipe.source, CutsceneSource):
return self.logic.region.can_reach(recipe.source.region) & self.logic.relationship.has_hearts(recipe.source.friend, recipe.source.hearts)
if isinstance(recipe.source, FriendshipSource):
@ -81,9 +82,9 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
if isinstance(recipe.source, QuestSource):
return self.logic.quest.can_complete_quest(recipe.source.quest)
if isinstance(recipe.source, SpecialOrderSource):
if self.options.special_order_locations == SpecialOrderLocations.option_disabled:
return self.logic.special_order.can_complete_special_order(recipe.source.special_order)
if self.options.special_order_locations & SpecialOrderLocations.option_board:
return self.logic.crafting.received_recipe(recipe.item)
return self.logic.special_order.can_complete_special_order(recipe.source.special_order)
if isinstance(recipe.source, LogicSource):
if recipe.source.logic_rule == "Cellar":
return self.logic.region.can_reach(Region.cellar)
@ -99,13 +100,16 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
craftsanity_prefix = "Craft "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_masteries = self.options.skill_progression != SkillProgression.option_progressive_with_masteries
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
if exclude_masteries and LocationTags.REQUIRES_MASTERIES in location.tags:
continue
if location.mod_name and location.mod_name not in self.options.mods:
continue
all_recipes_names.append(location.name[len(craftsanity_prefix):])
all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return And(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))
return self.logic.and_(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))

View File

@ -1,72 +0,0 @@
from typing import Union, Iterable
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from .tool_logic import ToolLogicMixin
from .traveling_merchant_logic import TravelingMerchantLogicMixin
from ..data import CropItem, SeedItem
from ..options import Cropsanity, ExcludeGingerIsland
from ..stardew_rule import StardewRule, True_, False_
from ..strings.craftable_names import Craftable
from ..strings.forageable_names import Forageable
from ..strings.machine_names import Machine
from ..strings.metal_names import Fossil
from ..strings.region_names import Region
from ..strings.seed_names import Seed
from ..strings.tool_names import Tool
class CropLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.crop = CropLogic(*args, **kwargs)
class CropLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, TravelingMerchantLogicMixin, SeasonLogicMixin, MoneyLogicMixin,
ToolLogicMixin, CropLogicMixin]]):
@cache_self1
def can_grow(self, crop: CropItem) -> StardewRule:
season_rule = self.logic.season.has_any(crop.farm_growth_seasons)
seed_rule = self.logic.has(crop.seed.name)
farm_rule = self.logic.region.can_reach(Region.farm) & season_rule
tool_rule = self.logic.tool.has_tool(Tool.hoe) & self.logic.tool.has_tool(Tool.watering_can)
region_rule = farm_rule | self.logic.region.can_reach(Region.greenhouse) | self.logic.crop.has_island_farm()
if crop.name == Forageable.cactus_fruit:
region_rule = self.logic.region.can_reach(Region.greenhouse) | self.logic.has(Craftable.garden_pot)
return seed_rule & region_rule & tool_rule
def can_plant_and_grow_item(self, seasons: Union[str, Iterable[str]]) -> StardewRule:
if isinstance(seasons, str):
seasons = [seasons]
season_rule = self.logic.season.has_any(seasons) | self.logic.region.can_reach(Region.greenhouse) | self.logic.crop.has_island_farm()
farm_rule = self.logic.region.can_reach(Region.farm) | self.logic.region.can_reach(Region.greenhouse) | self.logic.crop.has_island_farm()
return season_rule & farm_rule
def has_island_farm(self) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_false:
return self.logic.region.can_reach(Region.island_west)
return False_()
@cache_self1
def can_buy_seed(self, seed: SeedItem) -> StardewRule:
if seed.requires_island and self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return False_()
if self.options.cropsanity == Cropsanity.option_disabled or seed.name == Seed.qi_bean:
item_rule = True_()
else:
item_rule = self.logic.received(seed.name)
if seed.name == Seed.coffee:
item_rule = item_rule & self.logic.traveling_merchant.has_days(3)
season_rule = self.logic.season.has_any(seed.seasons)
region_rule = self.logic.region.can_reach_all(seed.regions)
currency_rule = self.logic.money.can_spend(1000)
if seed.name == Seed.pineapple:
currency_rule = self.logic.has(Forageable.magma_cap)
if seed.name == Seed.taro:
currency_rule = self.logic.has(Fossil.bone_fragment)
return season_rule & region_rule & item_rule & currency_rule

View File

@ -1,11 +1,27 @@
from typing import Union
from functools import cached_property
from typing import Union, Tuple
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .skill_logic import SkillLogicMixin
from ..stardew_rule import StardewRule, True_, False_
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..stardew_rule import StardewRule, True_, false_
from ..strings.ap_names.event_names import Event
from ..strings.fertilizer_names import Fertilizer
from ..strings.quality_names import CropQuality
from ..strings.region_names import Region
from ..strings.season_names import Season
from ..strings.tool_names import Tool
farming_event_by_season = {
Season.spring: Event.spring_farming,
Season.summer: Event.summer_farming,
Season.fall: Event.fall_farming,
Season.winter: Event.winter_farming,
}
class FarmingLogicMixin(BaseLogicMixin):
@ -14,7 +30,12 @@ class FarmingLogicMixin(BaseLogicMixin):
self.farming = FarmingLogic(*args, **kwargs)
class FarmingLogic(BaseLogic[Union[HasLogicMixin, SkillLogicMixin, FarmingLogicMixin]]):
class FarmingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, FarmingLogicMixin]]):
@cached_property
def has_farming_tools(self) -> StardewRule:
return self.logic.tool.has_tool(Tool.hoe) & self.logic.tool.can_water(0)
def has_fertilizer(self, tier: int) -> StardewRule:
if tier <= 0:
return True_()
@ -25,17 +46,17 @@ class FarmingLogic(BaseLogic[Union[HasLogicMixin, SkillLogicMixin, FarmingLogicM
if tier >= 3:
return self.logic.has(Fertilizer.deluxe)
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)
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))
if quality == CropQuality.iridium:
return self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(4)
return False_()
@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
if isinstance(seasons, str):
seasons = (seasons,)
return self.logic.or_(*(self.logic.received(farming_event_by_season[season]) for season in seasons))
def has_island_farm(self) -> StardewRule:
if self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_false:
return self.logic.region.can_reach(Region.island_west)
return false_

View File

@ -1,18 +1,21 @@
from typing import Union, List
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from .skill_logic import SkillLogicMixin
from .tool_logic import ToolLogicMixin
from ..data import FishItem, fish_data
from ..locations import LocationTags, locations_by_tag
from ..options import ExcludeGingerIsland, Fishsanity
from ..data import fish_data
from ..data.fish_data import FishItem
from ..options import ExcludeGingerIsland
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, True_, False_, And
from ..stardew_rule import StardewRule, True_, False_
from ..strings.ap_names.mods.mod_items import SVEQuestItem
from ..strings.fish_names import SVEFish
from ..strings.machine_names import Machine
from ..strings.quality_names import FishQuality
from ..strings.region_names import Region
from ..strings.skill_names import Skill
@ -24,17 +27,16 @@ class FishingLogicMixin(BaseLogicMixin):
self.fishing = FishingLogic(*args, **kwargs)
class FishingLogic(BaseLogic[Union[FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, SkillLogicMixin]]):
class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin,
SkillLogicMixin]]):
def can_fish_in_freshwater(self) -> StardewRule:
return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain))
def has_max_fishing(self) -> StardewRule:
skill_rule = self.logic.skill.has_level(Skill.fishing, 10)
return self.logic.tool.has_fishing_rod(4) & skill_rule
return self.logic.tool.has_fishing_rod(4) & self.logic.skill.has_level(Skill.fishing, 10)
def can_fish_chests(self) -> StardewRule:
skill_rule = self.logic.skill.has_level(Skill.fishing, 6)
return self.logic.tool.has_fishing_rod(4) & skill_rule
return self.logic.tool.has_fishing_rod(4) & self.logic.skill.has_level(Skill.fishing, 6)
def can_fish_at(self, region: str) -> StardewRule:
return self.logic.skill.can_fish() & self.logic.region.can_reach(region)
@ -51,17 +53,23 @@ class FishingLogic(BaseLogic[Union[FishingLogicMixin, ReceivedLogicMixin, Region
else:
difficulty_rule = self.logic.skill.can_fish(difficulty=(120 if fish.legendary else fish.difficulty))
if fish.name == SVEFish.kittyfish:
item_rule = self.logic.received("Kittyfish Spell")
item_rule = self.logic.received(SVEQuestItem.kittyfish_spell)
else:
item_rule = True_()
return quest_rule & region_rule & season_rule & difficulty_rule & item_rule
def can_catch_fish_for_fishsanity(self, fish: FishItem) -> StardewRule:
""" Rule could be different from the basic `can_catch_fish`. Imagine a fishsanity setting where you need to catch every fish with gold quality.
"""
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 self.options.special_order_locations != SpecialOrderLocations.option_board_qi:
if not self.options.special_order_locations & SpecialOrderLocations.value_qi:
return False_()
return self.logic.region.can_reach(Region.qi_walnut_room) & And(*(self.logic.fishing.can_catch_fish(fish) for fish in fish_data.legendary_fish))
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)))
def can_catch_quality_fish(self, fish_quality: str) -> StardewRule:
if fish_quality == FishQuality.basic:
@ -78,24 +86,27 @@ class FishingLogic(BaseLogic[Union[FishingLogicMixin, ReceivedLogicMixin, Region
def can_catch_every_fish(self) -> StardewRule:
rules = [self.has_max_fishing()]
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_extended_family = self.options.special_order_locations != SpecialOrderLocations.option_board_qi
for fish in fish_data.get_fish_for_mods(self.options.mods.value):
if exclude_island and fish in fish_data.island_fish:
continue
if exclude_extended_family and fish in fish_data.extended_family:
continue
rules.append(self.logic.fishing.can_catch_fish(fish))
return And(*rules)
def can_catch_every_fish_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
if self.options.fishsanity == Fishsanity.option_none:
rules.extend(
self.logic.fishing.can_catch_fish(fish)
for fish in self.content.fishes.values()
)
return self.logic.and_(*rules)
def can_catch_every_fish_for_fishsanity(self) -> StardewRule:
if not self.content.features.fishsanity.is_enabled:
return self.can_catch_every_fish()
rules = [self.has_max_fishing()]
for fishsanity_location in locations_by_tag[LocationTags.FISHSANITY]:
if fishsanity_location.name not in all_location_names_in_slot:
continue
rules.append(self.logic.region.can_reach_location(fishsanity_location.name))
return And(*rules)
rules.extend(
self.logic.fishing.can_catch_fish_for_fishsanity(fish)
for fish in self.content.fishes.values()
if self.content.features.fishsanity.is_included(fish)
)
return self.logic.and_(*rules)
def has_specific_bait(self, fish: FishItem) -> StardewRule:
return self.can_catch_fish(fish) & self.logic.has(Machine.bait_maker)

View File

@ -0,0 +1,74 @@
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 .time_logic import TimeLogicMixin
from ..options import Booksanity
from ..stardew_rule import StardewRule, HasProgressionPercent
from ..strings.book_names import Book
from ..strings.craftable_names import Consumable
from ..strings.currency_names import Currency
from ..strings.fish_names import WaterChest
from ..strings.geode_names import Geode
from ..strings.tool_names import Tool
if TYPE_CHECKING:
from .tool_logic import ToolLogicMixin
else:
ToolLogicMixin = object
MIN_ITEMS = 10
MAX_ITEMS = 999
PERCENT_REQUIRED_FOR_MAX_ITEM = 24
class GrindLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.grind = GrindLogic(*args, **kwargs)
class GrindLogic(BaseLogic[Union[GrindLogicMixin, HasLogicMixin, ReceivedLogicMixin, BookLogicMixin, TimeLogicMixin, ToolLogicMixin]]):
def can_grind_mystery_boxes(self, quantity: int) -> StardewRule:
mystery_box_rule = self.logic.has(Consumable.mystery_box)
book_of_mysteries_rule = self.logic.true_ \
if self.options.booksanity == Booksanity.option_none \
else self.logic.book.has_book_power(Book.book_of_mysteries)
# Assuming one box per day, but halved because we don't know how many months have passed before Mr. Qi's Plane Ride.
time_rule = self.logic.time.has_lived_months(quantity // 14)
return self.logic.and_(mystery_box_rule,
book_of_mysteries_rule,
time_rule)
def can_grind_artifact_troves(self, quantity: int) -> StardewRule:
return self.logic.and_(self.logic.has(Geode.artifact_trove),
# Assuming one per month if the player does not grind it.
self.logic.time.has_lived_months(quantity))
def can_grind_prize_tickets(self, quantity: int) -> StardewRule:
return self.logic.and_(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))
def can_grind_fishing_treasure_chests(self, quantity: int) -> StardewRule:
return self.logic.and_(self.logic.has(WaterChest.fishing_chest),
# Assuming one per week if the player does not grind it.
self.logic.time.has_lived_months(quantity // 4))
def can_grind_artifact_spots(self, quantity: int) -> StardewRule:
return self.logic.and_(self.logic.tool.has_tool(Tool.hoe),
# Assuming twelve per month if the player does not grind it.
self.logic.time.has_lived_months(quantity // 12))
@cache_self1
def can_grind_item(self, quantity: int) -> StardewRule:
if quantity <= MIN_ITEMS:
return self.logic.true_
quantity = min(quantity, MAX_ITEMS)
price = max(1, quantity * PERCENT_REQUIRED_FOR_MAX_ITEM // MAX_ITEMS)
return HasProgressionPercent(self.player, price)

View File

@ -0,0 +1,56 @@
from functools import cached_property
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .farming_logic import FarmingLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from ..data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource
from ..stardew_rule import StardewRule
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.region_names import Region
class HarvestingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.harvesting = HarvestingLogic(*args, **kwargs)
class HarvestingLogic(BaseLogic[Union[HarvestingLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin,
FarmingLogicMixin, TimeLogicMixin]]):
@cached_property
def can_harvest_from_fruit_bats(self) -> StardewRule:
return self.logic.region.can_reach(Region.farm_cave) & self.logic.received(CommunityUpgrade.fruit_bats)
@cached_property
def can_harvest_from_mushroom_cave(self) -> StardewRule:
return self.logic.region.can_reach(Region.farm_cave) & self.logic.received(CommunityUpgrade.mushroom_boxes)
@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
@cache_self1
def can_harvest_tree_from(self, source: HarvestFruitTreeSource) -> StardewRule:
# FIXME tool not required for this
region_to_grow_rule = self.logic.farming.can_plant_and_grow_item(source.seasons)
sapling_rule = self.logic.has(source.sapling)
# Because it takes 1 month to grow the sapling
time_rule = self.logic.time.has_lived_months(1)
return region_to_grow_rule & sapling_rule & time_rule
@cache_self1
def can_harvest_crop_from(self, source: HarvestCropSource) -> StardewRule:
region_to_grow_rule = self.logic.farming.can_plant_and_grow_item(source.seasons)
seed_rule = self.logic.has(source.seed)
return region_to_grow_rule & seed_rule

View File

@ -1,8 +1,11 @@
from .base_logic import BaseLogic
from ..stardew_rule import StardewRule, And, Or, Has, Count
from ..stardew_rule import StardewRule, And, Or, Has, Count, true_, false_
class HasLogicMixin(BaseLogic[None]):
true_ = true_
false_ = false_
# Should be cached
def has(self, item: str) -> StardewRule:
return Has(item, self.registry.item_rules)
@ -10,12 +13,12 @@ class HasLogicMixin(BaseLogic[None]):
def has_all(self, *items: str):
assert items, "Can't have all of no items."
return And(*(self.has(item) for item in items))
return self.logic.and_(*(self.has(item) for item in items))
def has_any(self, *items: str):
assert items, "Can't have any of no items."
return Or(*(self.has(item) for item in items))
return self.logic.or_(*(self.has(item) for item in items))
def has_n(self, *items: str, count: int):
return self.count(count, *(self.has(item) for item in items))
@ -24,6 +27,16 @@ class HasLogicMixin(BaseLogic[None]):
def count(count: int, *rules: StardewRule) -> StardewRule:
assert rules, "Can't create a Count conditions without rules"
assert len(rules) >= count, "Count need at least as many rules as the count"
assert count > 0, "Count can't be negative"
count -= sum(r is true_ for r in rules)
rules = list(r for r in rules if r is not true_)
if count <= 0:
return true_
if len(rules) == 1:
return rules[0]
if count == 1:
return Or(*rules)
@ -31,4 +44,22 @@ class HasLogicMixin(BaseLogic[None]):
if count == len(rules):
return And(*rules)
return Count(list(rules), count)
return Count(rules, count)
@staticmethod
def and_(*rules: StardewRule) -> StardewRule:
assert rules, "Can't create a And conditions without rules"
if len(rules) == 1:
return rules[0]
return And(*rules)
@staticmethod
def or_(*rules: StardewRule) -> StardewRule:
assert rules, "Can't create a Or conditions without rules"
if len(rules) == 1:
return rules[0]
return Or(*rules)

View File

@ -1,7 +1,8 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Collection
import logging
from functools import cached_property
from typing import Collection, Callable
from .ability_logic import AbilityLogicMixin
from .action_logic import ActionLogicMixin
@ -9,50 +10,53 @@ from .animal_logic import AnimalLogicMixin
from .arcade_logic import ArcadeLogicMixin
from .artisan_logic import ArtisanLogicMixin
from .base_logic import LogicRegistry
from .buff_logic import BuffLogicMixin
from .book_logic import BookLogicMixin
from .building_logic import BuildingLogicMixin
from .bundle_logic import BundleLogicMixin
from .combat_logic import CombatLogicMixin
from .cooking_logic import CookingLogicMixin
from .crafting_logic import CraftingLogicMixin
from .crop_logic import CropLogicMixin
from .farming_logic import FarmingLogicMixin
from .fishing_logic import FishingLogicMixin
from .gift_logic import GiftLogicMixin
from .grind_logic import GrindLogicMixin
from .harvesting_logic import HarvestingLogicMixin
from .has_logic import HasLogicMixin
from .logic_event import all_logic_events
from .mine_logic import MineLogicMixin
from .money_logic import MoneyLogicMixin
from .monster_logic import MonsterLogicMixin
from .museum_logic import MuseumLogicMixin
from .pet_logic import PetLogicMixin
from .quality_logic import QualityLogicMixin
from .quest_logic import QuestLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
from .requirement_logic import RequirementLogicMixin
from .season_logic import SeasonLogicMixin
from .shipping_logic import ShippingLogicMixin
from .skill_logic import SkillLogicMixin
from .source_logic import SourceLogicMixin
from .special_order_logic import SpecialOrderLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .traveling_merchant_logic import TravelingMerchantLogicMixin
from .wallet_logic import WalletLogicMixin
from ..data import all_purchasable_seeds, all_crops
from ..content.game_content import StardewContent
from ..data.craftable_data import all_crafting_recipes
from ..data.crops_data import crops_by_name
from ..data.fish_data import get_fish_for_mods
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 ..mods.mod_data import ModNames
from ..options import Cropsanity, SpecialOrderLocations, ExcludeGingerIsland, FestivalLocations, Fishsanity, Friendsanity, StardewValleyOptions
from ..stardew_rule import False_, Or, True_, And, StardewRule
from ..options import SpecialOrderLocations, ExcludeGingerIsland, FestivalLocations, StardewValleyOptions, Walnutsanity
from ..stardew_rule import False_, True_, StardewRule
from ..strings.animal_names import Animal
from ..strings.animal_product_names import AnimalProduct
from ..strings.ap_names.ap_weapon_names import APWeapon
from ..strings.ap_names.buff_names import Buff
from ..strings.ap_names.ap_option_names import OptionName
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.ap_names.event_names import Event
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.craftable_names import Consumable, Furniture, Ring, Fishing, Lighting, WildSeeds
@ -72,10 +76,10 @@ 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.metal_names import Ore, MetalBar, Mineral, Fossil
from ..strings.metal_names import Ore, MetalBar, Mineral, Fossil, Artifact
from ..strings.monster_drop_names import Loot
from ..strings.monster_names import Monster
from ..strings.region_names import Region
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
@ -83,23 +87,26 @@ from ..strings.tool_names import Tool, ToolMaterial
from ..strings.villager_names import NPC
from ..strings.wallet_item_names import Wallet
logger = logging.getLogger(__name__)
@dataclass(frozen=False, repr=False)
class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogicMixin, TravelingMerchantLogicMixin, TimeLogicMixin,
class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, TravelingMerchantLogicMixin, TimeLogicMixin,
SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, GiftLogicMixin,
BuildingLogicMixin, ShippingLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, WalletLogicMixin, AnimalLogicMixin,
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, CropLogicMixin,
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin,
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin):
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin,
RequirementLogicMixin, BookLogicMixin, GrindLogicMixin):
player: int
options: StardewValleyOptions
content: StardewContent
regions: Collection[str]
def __init__(self, player: int, options: StardewValleyOptions, regions: Collection[str]):
def __init__(self, player: int, options: StardewValleyOptions, content: StardewContent, regions: Collection[str]):
self.registry = LogicRegistry()
super().__init__(player, self.registry, options, regions, self)
super().__init__(player, self.registry, options, content, regions, self)
self.registry.fish_rules.update({fish.name: self.fishing.can_catch_fish(fish) for fish in get_fish_for_mods(self.options.mods.value)})
self.registry.fish_rules.update({fish.name: self.fishing.can_catch_fish(fish) for fish in content.fishes.values()})
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:
@ -118,37 +125,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
can_craft_rule = can_craft_rule | self.registry.crafting_rules[recipe.item]
self.registry.crafting_rules[recipe.item] = can_craft_rule
self.registry.sapling_rules.update({
Sapling.apple: self.can_buy_sapling(Fruit.apple),
Sapling.apricot: self.can_buy_sapling(Fruit.apricot),
Sapling.cherry: self.can_buy_sapling(Fruit.cherry),
Sapling.orange: self.can_buy_sapling(Fruit.orange),
Sapling.peach: self.can_buy_sapling(Fruit.peach),
Sapling.pomegranate: self.can_buy_sapling(Fruit.pomegranate),
Sapling.banana: self.can_buy_sapling(Fruit.banana),
Sapling.mango: self.can_buy_sapling(Fruit.mango),
})
self.registry.tree_fruit_rules.update({
Fruit.apple: self.crop.can_plant_and_grow_item(Season.fall),
Fruit.apricot: self.crop.can_plant_and_grow_item(Season.spring),
Fruit.cherry: self.crop.can_plant_and_grow_item(Season.spring),
Fruit.orange: self.crop.can_plant_and_grow_item(Season.summer),
Fruit.peach: self.crop.can_plant_and_grow_item(Season.summer),
Fruit.pomegranate: self.crop.can_plant_and_grow_item(Season.fall),
Fruit.banana: self.crop.can_plant_and_grow_item(Season.summer),
Fruit.mango: self.crop.can_plant_and_grow_item(Season.summer),
})
for tree_fruit in self.registry.tree_fruit_rules:
existing_rules = self.registry.tree_fruit_rules[tree_fruit]
sapling = f"{tree_fruit} Sapling"
self.registry.tree_fruit_rules[tree_fruit] = existing_rules & self.has(sapling) & self.time.has_lived_months(1)
self.registry.seed_rules.update({seed.name: self.crop.can_buy_seed(seed) for seed in all_purchasable_seeds})
self.registry.crop_rules.update({crop.name: self.crop.can_grow(crop) for crop in all_crops})
self.registry.crop_rules.update({
Seed.coffee: (self.season.has(Season.spring) | self.season.has(Season.summer)) & self.crop.can_buy_seed(crops_by_name[Seed.coffee].seed),
Fruit.ancient_fruit: (self.received("Ancient Seeds") | self.received("Ancient Seeds Recipe")) &
self.region.can_reach(Region.greenhouse) & self.has(Machine.seed_maker),
})
@ -157,6 +134,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
self.registry.item_rules.update({
"Energy Tonic": self.money.can_spend_at(Region.hospital, 1000),
WaterChest.fishing_chest: self.fishing.can_fish_chests(),
WaterChest.golden_fishing_chest: self.fishing.can_fish_chests() & self.skill.has_mastery(Skill.fishing),
WaterChest.treasure: self.fishing.can_fish_chests(),
Ring.hot_java_ring: self.region.can_reach(Region.volcano_floor_10),
"Galaxy Soul": self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 40),
@ -197,7 +175,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
AnimalProduct.large_goat_milk: self.animal.has_happy_animal(Animal.goat),
AnimalProduct.large_milk: self.animal.has_happy_animal(Animal.cow),
AnimalProduct.milk: self.animal.has_animal(Animal.cow),
AnimalProduct.ostrich_egg: self.tool.can_forage(Generic.any, Region.island_north, True),
AnimalProduct.ostrich_egg: self.tool.can_forage(Generic.any, Region.island_north, True) & self.has(Forageable.journal_scrap) & self.region.can_reach(Region.volcano_floor_5),
AnimalProduct.rabbit_foot: self.animal.has_happy_animal(Animal.rabbit),
AnimalProduct.roe: self.skill.can_fish() & 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)),
@ -218,29 +196,35 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
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),
ArtisanGood.green_tea: self.artisan.can_keg(Vegetable.tea_leaves),
ArtisanGood.honey: self.money.can_spend_at(Region.oasis, 200) | (self.has(Machine.bee_house) & self.season.has_any_not_winter()),
ArtisanGood.jelly: self.artisan.has_jelly(),
ArtisanGood.juice: self.artisan.has_juice(),
ArtisanGood.maple_syrup: self.has(Machine.tapper),
ArtisanGood.mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.chicken_egg),
ArtisanGood.mead: self.artisan.can_keg(ArtisanGood.honey),
ArtisanGood.mystic_syrup: self.has(Machine.tapper) & self.has(TreeSeed.mystic),
ArtisanGood.oak_resin: self.has(Machine.tapper),
ArtisanGood.pale_ale: self.artisan.can_keg(Vegetable.hops),
ArtisanGood.pickles: self.artisan.has_pickle(),
ArtisanGood.pine_tar: self.has(Machine.tapper),
ArtisanGood.smoked_fish: self.artisan.has_smoked_fish(),
ArtisanGood.targeted_bait: self.artisan.has_targeted_bait(),
ArtisanGood.stardrop_tea: self.has(WaterChest.golden_fishing_chest),
ArtisanGood.truffle_oil: self.has(AnimalProduct.truffle) & self.has(Machine.oil_maker),
ArtisanGood.void_mayonnaise: (self.skill.can_fish(Region.witch_swamp)) | (self.artisan.can_mayonnaise(AnimalProduct.void_egg)),
ArtisanGood.wine: self.artisan.has_wine(),
Beverage.beer: self.artisan.can_keg(Vegetable.wheat) | self.money.can_spend_at(Region.saloon, 400),
Beverage.coffee: self.artisan.can_keg(Seed.coffee) | self.has(Machine.coffee_maker) | (self.money.can_spend_at(Region.saloon, 300)) | self.has("Hot Java Ring"),
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.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),
Currency.golden_tag: self.region.can_reach(LogicRegion.trout_derby),
Currency.prize_ticket: self.time.has_lived_months(2), # Time to do a few help wanted quests
Decoration.rotten_plant: self.has(Lighting.jack_o_lantern) & self.season.has(Season.winter),
Fertilizer.basic: self.money.can_spend_at(Region.pierre_store, 100),
Fertilizer.quality: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
Fertilizer.tree: self.skill.has_level(Skill.foraging, 7) & self.has(Material.fiber) & self.has(Material.stone),
Fish.any: Or(*(self.fishing.can_catch_fish(fish) for fish in get_fish_for_mods(self.options.mods.value))),
Fish.any: self.logic.or_(*(self.fishing.can_catch_fish(fish) for fish in content.fishes.values())),
Fish.crab: self.skill.can_crab_pot_at(Region.beach),
Fish.crayfish: self.skill.can_crab_pot_at(Region.town),
Fish.lobster: self.skill.can_crab_pot_at(Region.beach),
@ -252,44 +236,15 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
Fish.snail: self.skill.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.blackberry: self.tool.can_forage(Season.fall) | self.has_fruit_bats(),
Forageable.cactus_fruit: self.tool.can_forage(Generic.any, Region.desert),
Forageable.cave_carrot: self.tool.can_forage(Generic.any, Region.mines_floor_10, True),
Forageable.chanterelle: self.tool.can_forage(Season.fall, Region.secret_woods) | self.has_mushroom_cave(),
Forageable.coconut: self.tool.can_forage(Generic.any, Region.desert),
Forageable.common_mushroom: self.tool.can_forage(Season.fall) | (self.tool.can_forage(Season.spring, Region.secret_woods)) | self.has_mushroom_cave(),
Forageable.crocus: self.tool.can_forage(Season.winter),
Forageable.crystal_fruit: self.tool.can_forage(Season.winter),
Forageable.daffodil: self.tool.can_forage(Season.spring),
Forageable.dandelion: self.tool.can_forage(Season.spring),
Forageable.dragon_tooth: self.tool.can_forage(Generic.any, Region.volcano_floor_10),
Forageable.fiddlehead_fern: self.tool.can_forage(Season.summer, Region.secret_woods),
Forageable.ginger: self.tool.can_forage(Generic.any, Region.island_west, True),
Forageable.hay: self.building.has_building(Building.silo) & self.tool.has_tool(Tool.scythe),
Forageable.hazelnut: self.tool.can_forage(Season.fall),
Forageable.holly: self.tool.can_forage(Season.winter),
Forageable.journal_scrap: self.region.can_reach_all((Region.island_west, Region.island_north, Region.island_south, Region.volcano_floor_10)) & self.quest.has_magnifying_glass() & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),
Forageable.leek: self.tool.can_forage(Season.spring),
Forageable.magma_cap: self.tool.can_forage(Generic.any, Region.volcano_floor_5),
Forageable.morel: self.tool.can_forage(Season.spring, Region.secret_woods) | self.has_mushroom_cave(),
Forageable.purple_mushroom: self.tool.can_forage(Generic.any, Region.mines_floor_95) | self.tool.can_forage(Generic.any, Region.skull_cavern_25) | self.has_mushroom_cave(),
Forageable.rainbow_shell: self.tool.can_forage(Season.summer, Region.beach),
Forageable.red_mushroom: self.tool.can_forage(Season.summer, Region.secret_woods) | self.tool.can_forage(Season.fall, Region.secret_woods) | self.has_mushroom_cave(),
Forageable.salmonberry: self.tool.can_forage(Season.spring) | self.has_fruit_bats(),
Forageable.secret_note: self.quest.has_magnifying_glass() & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),
Forageable.snow_yam: self.tool.can_forage(Season.winter, Region.beach, True),
Forageable.spice_berry: self.tool.can_forage(Season.summer) | self.has_fruit_bats(),
Forageable.spring_onion: self.tool.can_forage(Season.spring),
Forageable.sweet_pea: self.tool.can_forage(Season.summer),
Forageable.wild_horseradish: self.tool.can_forage(Season.spring),
Forageable.wild_plum: self.tool.can_forage(Season.fall) | self.has_fruit_bats(),
Forageable.winter_root: self.tool.can_forage(Season.winter, Region.forest, True),
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()), #
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),
Fossil.fossilized_skull: self.action.can_open_geode(Geode.golden_coconut),
Fossil.fossilized_spine: self.skill.can_fish(Region.dig_site),
Fossil.fossilized_tail: self.action.can_pan_at(Region.dig_site),
Fossil.fossilized_tail: self.action.can_pan_at(Region.dig_site, ToolMaterial.copper),
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.snake_skull: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.hoe),
@ -299,10 +254,10 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
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.action.can_pan() | self.received(Wallet.rusty_key) | (self.has(Fish.octopus) & self.building.has_building(Building.fish_pond)) | self.region.can_reach(Region.volcano_floor_10),
Gift.bouquet: self.relationship.has_hearts(Generic.bachelor, 8) & self.money.can_spend_at(Region.pierre_store, 100),
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),
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(Generic.bachelor, 10) & self.building.has_house(1) & self.has(Consumable.rain_totem),
Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts_with_any_bachelor(10) & self.building.has_house(1) & self.has(Consumable.rain_totem),
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.tea_set: self.season.has(Season.winter) & self.time.has_lived_max_months,
@ -312,45 +267,27 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
Ingredient.qi_seasoning: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 10),
Ingredient.rice: self.money.can_spend_at(Region.pierre_store, 200) | (self.building.has_building(Building.mill) & self.has(Vegetable.unmilled_rice)),
Ingredient.sugar: self.money.can_spend_at(Region.pierre_store, 100) | (self.building.has_building(Building.mill) & self.has(Vegetable.beet)),
Ingredient.vinegar: self.money.can_spend_at(Region.pierre_store, 200),
Ingredient.vinegar: self.money.can_spend_at(Region.pierre_store, 200) | self.artisan.can_keg(Ingredient.rice),
Ingredient.wheat_flour: self.money.can_spend_at(Region.pierre_store, 100) | (self.building.has_building(Building.mill) & self.has(Vegetable.wheat)),
Loot.bat_wing: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern(),
Loot.bug_meat: self.mine.can_mine_in_the_mines_floor_1_40(),
Loot.slime: self.mine.can_mine_in_the_mines_floor_1_40(),
Loot.solar_essence: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern(),
Loot.void_essence: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern(),
Machine.bee_house: self.skill.has_farming_level(3) & self.has(MetalBar.iron) & self.has(ArtisanGood.maple_syrup) & self.has(Material.coal) & self.has(Material.wood),
Machine.cask: self.building.has_house(3) & self.region.can_reach(Region.cellar) & self.has(Material.wood) & self.has(Material.hardwood),
Machine.cheese_press: self.skill.has_farming_level(6) & self.has(Material.wood) & self.has(Material.stone) & self.has(Material.hardwood) & self.has(MetalBar.copper),
Machine.coffee_maker: self.received(Machine.coffee_maker),
Machine.crab_pot: self.skill.has_level(Skill.fishing, 3) & (self.money.can_spend_at(Region.fish_shop, 1500) | (self.has(MetalBar.iron) & self.has(Material.wood))),
Machine.furnace: self.has(Material.stone) & self.has(Ore.copper),
Machine.keg: self.skill.has_farming_level(8) & self.has(Material.wood) & self.has(MetalBar.iron) & self.has(MetalBar.copper) & self.has(ArtisanGood.oak_resin),
Machine.lightning_rod: self.skill.has_level(Skill.foraging, 6) & self.has(MetalBar.iron) & self.has(MetalBar.quartz) & self.has(Loot.bat_wing),
Machine.loom: self.skill.has_farming_level(7) & self.has(Material.wood) & self.has(Material.fiber) & self.has(ArtisanGood.pine_tar),
Machine.mayonnaise_machine: self.skill.has_farming_level(2) & self.has(Material.wood) & self.has(Material.stone) & self.has("Earth Crystal") & self.has(MetalBar.copper),
Machine.ostrich_incubator: self.received("Ostrich Incubator Recipe") & self.has(Fossil.bone_fragment) & self.has(Material.hardwood) & self.has(Material.cinder_shard),
Machine.preserves_jar: self.skill.has_farming_level(4) & self.has(Material.wood) & self.has(Material.stone) & self.has(Material.coal),
Machine.recycling_machine: self.skill.has_level(Skill.fishing, 4) & self.has(Material.wood) & self.has(Material.stone) & self.has(MetalBar.iron),
Machine.seed_maker: self.skill.has_farming_level(9) & self.has(Material.wood) & self.has(MetalBar.gold) & self.has(Material.coal),
Machine.solar_panel: self.received("Solar Panel Recipe") & self.has(MetalBar.quartz) & self.has(MetalBar.iron) & self.has(MetalBar.gold),
Machine.tapper: self.skill.has_level(Skill.foraging, 3) & self.has(Material.wood) & self.has(MetalBar.copper),
Machine.worm_bin: self.skill.has_level(Skill.fishing, 8) & self.has(Material.hardwood) & self.has(MetalBar.gold) & self.has(MetalBar.iron) & self.has(Material.fiber),
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),
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.action.can_pan(),
Material.coal: self.mine.can_mine_in_the_mines_floor_41_80() | self.tool.has_tool(Tool.pan),
Material.fiber: True_(),
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: True_(),
Material.sap: self.ability.can_chop_trees(),
Material.stone: self.tool.has_tool(Tool.pickaxe),
Material.wood: self.tool.has_tool(Tool.axe),
Meal.bread: self.money.can_spend_at(Region.saloon, 120),
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.pizza: self.money.can_spend_at(Region.saloon, 600),
Meal.salad: self.money.can_spend_at(Region.saloon, 220),
Meal.spaghetti: self.money.can_spend_at(Region.saloon, 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),
MetalBar.gold: self.can_smelt(Ore.gold),
@ -358,15 +295,14 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
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.action.can_pan(),
Ore.gold: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern() | self.action.can_pan(),
Ore.iridium: self.mine.can_mine_in_the_skull_cavern() | self.can_fish_pond(Fish.super_cucumber),
Ore.iron: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern() | self.action.can_pan(),
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.iron),
Ore.iridium: self.mine.can_mine_in_the_skull_cavern() | self.can_fish_pond(Fish.super_cucumber) | self.tool.has_tool(Tool.pan, ToolMaterial.gold),
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.copper),
Ore.radioactive: self.ability.can_mine_perfectly() & self.region.can_reach(Region.qi_walnut_room),
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),
Sapling.tea: self.relationship.has_hearts(NPC.caroline, 2) & self.has(Material.fiber) & self.has(Material.wood),
Seed.mixed: self.tool.has_tool(Tool.scythe) & self.region.can_reach_all((Region.farm, Region.forest, Region.town)),
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.skill.can_crab_pot,
@ -380,24 +316,33 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
TreeSeed.maple: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
TreeSeed.mushroom: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 5),
TreeSeed.pine: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
Vegetable.tea_leaves: self.has(Sapling.tea) & self.time.has_lived_months(2) & self.season.has_any_not_winter(),
TreeSeed.mossy: self.ability.can_chop_trees() & self.season.has(Season.summer),
Fish.clam: self.tool.can_forage(Generic.any, Region.beach),
Fish.cockle: self.tool.can_forage(Generic.any, Region.beach),
WaterItem.coral: self.tool.can_forage(Generic.any, Region.tide_pools) | self.tool.can_forage(Season.summer, Region.beach),
WaterItem.green_algae: self.fishing.can_fish_in_freshwater(),
WaterItem.nautilus_shell: self.tool.can_forage(Season.winter, Region.beach),
WaterItem.sea_urchin: self.tool.can_forage(Generic.any, Region.tide_pools),
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.seaweed: self.skill.can_fish(Region.tide_pools),
WaterItem.white_algae: self.skill.can_fish(Region.mines_floor_20),
WildSeeds.grass_starter: self.money.can_spend_at(Region.pierre_store, 100),
})
# @formatter:on
content_rules = {
item_name: self.source.has_access_to_item(game_item)
for item_name, game_item in self.content.game_items.items()
}
for item in set(content_rules.keys()).intersection(self.registry.item_rules.keys()):
logger.warning(f"Rule for {item} already exists in the registry, overwriting it.")
self.registry.item_rules.update(content_rules)
self.registry.item_rules.update(self.registry.fish_rules)
self.registry.item_rules.update(self.registry.museum_rules)
self.registry.item_rules.update(self.registry.sapling_rules)
self.registry.item_rules.update(self.registry.tree_fruit_rules)
self.registry.item_rules.update(self.registry.seed_rules)
self.registry.item_rules.update(self.registry.crop_rules)
self.artisan.initialize_rules()
self.registry.item_rules.update(self.registry.artisan_good_rules)
self.registry.item_rules.update(self.mod.item.get_modded_item_rules())
self.mod.item.modify_vanilla_item_rules_with_mod_additions(self.registry.item_rules) # New regions and content means new ways to obtain old items
@ -423,7 +368,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
self.registry.festival_rules.update({
FestivalCheck.egg_hunt: self.can_win_egg_hunt(),
FestivalCheck.strawberry_seeds: self.money.can_spend(1000),
FestivalCheck.dance: self.relationship.has_hearts(Generic.bachelor, 4),
FestivalCheck.dance: self.relationship.has_hearts_with_any_bachelor(4),
FestivalCheck.tub_o_flowers: self.money.can_spend(2000),
FestivalCheck.rarecrow_5: self.money.can_spend(2500),
FestivalCheck.luau_soup: self.can_succeed_luau_soup(),
@ -457,43 +402,90 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
FestivalCheck.legend_of_the_winter_star: True_(),
FestivalCheck.rarecrow_3: True_(),
FestivalCheck.all_rarecrows: self.region.can_reach(Region.farm) & self.has_all_rarecrows(),
FestivalCheck.calico_race: True_(),
FestivalCheck.mummy_mask: True_(),
FestivalCheck.calico_statue: True_(),
FestivalCheck.emily_outfit_service: True_(),
FestivalCheck.earthy_mousse: True_(),
FestivalCheck.sweet_bean_cake: True_(),
FestivalCheck.skull_cave_casserole: True_(),
FestivalCheck.spicy_tacos: True_(),
FestivalCheck.mountain_chili: True_(),
FestivalCheck.crystal_cake: True_(),
FestivalCheck.cave_kebab: True_(),
FestivalCheck.hot_log: True_(),
FestivalCheck.sour_salad: True_(),
FestivalCheck.superfood_cake: True_(),
FestivalCheck.warrior_smoothie: True_(),
FestivalCheck.rumpled_fruit_skin: True_(),
FestivalCheck.calico_pizza: True_(),
FestivalCheck.stuffed_mushrooms: True_(),
FestivalCheck.elf_quesadilla: True_(),
FestivalCheck.nachos_of_the_desert: True_(),
FestivalCheck.cloppino: True_(),
FestivalCheck.rainforest_shrimp: True_(),
FestivalCheck.shrimp_donut: True_(),
FestivalCheck.smell_of_the_sea: True_(),
FestivalCheck.desert_gumbo: True_(),
FestivalCheck.free_cactis: True_(),
FestivalCheck.monster_hunt: self.monster.can_kill(Monster.serpent),
FestivalCheck.deep_dive: self.region.can_reach(Region.skull_cavern_50),
FestivalCheck.treasure_hunt: self.region.can_reach(Region.skull_cavern_25),
FestivalCheck.touch_calico_statue: self.region.can_reach(Region.skull_cavern_25),
FestivalCheck.real_calico_egg_hunter: self.region.can_reach(Region.skull_cavern_100),
FestivalCheck.willy_challenge: self.fishing.can_catch_fish(content.fishes[Fish.scorpion_carp]),
FestivalCheck.desert_scholar: True_(),
FestivalCheck.squidfest_day_1_copper: self.fishing.can_catch_fish(content.fishes[Fish.squid]),
FestivalCheck.squidfest_day_1_iron: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.bait),
FestivalCheck.squidfest_day_1_gold: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.deluxe_bait),
FestivalCheck.squidfest_day_1_iridium: self.fishing.can_catch_fish(content.fishes[Fish.squid]) &
self.fishing.has_specific_bait(content.fishes[Fish.squid]),
FestivalCheck.squidfest_day_2_copper: self.fishing.can_catch_fish(content.fishes[Fish.squid]),
FestivalCheck.squidfest_day_2_iron: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.bait),
FestivalCheck.squidfest_day_2_gold: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.deluxe_bait),
FestivalCheck.squidfest_day_2_iridium: self.fishing.can_catch_fish(content.fishes[Fish.squid]) &
self.fishing.has_specific_bait(content.fishes[Fish.squid]),
})
for i in range(1, 11):
self.registry.festival_rules[f"{FestivalCheck.trout_derby_reward_pattern}{i}"] = self.fishing.can_catch_fish(content.fishes[Fish.rainbow_trout])
self.special_order.initialize_rules()
self.special_order.update_rules(self.mod.special_order.get_modded_special_orders_rules())
def can_buy_sapling(self, fruit: str) -> StardewRule:
sapling_prices = {Fruit.apple: 4000, Fruit.apricot: 2000, Fruit.cherry: 3400, Fruit.orange: 4000,
Fruit.peach: 6000,
Fruit.pomegranate: 6000, Fruit.banana: 0, Fruit.mango: 0}
received_sapling = self.received(f"{fruit} Sapling")
if self.options.cropsanity == Cropsanity.option_disabled:
allowed_buy_sapling = True_()
else:
allowed_buy_sapling = received_sapling
can_buy_sapling = self.money.can_spend_at(Region.pierre_store, sapling_prices[fruit])
if fruit == Fruit.banana:
can_buy_sapling = self.has_island_trader() & self.has(Forageable.dragon_tooth)
elif fruit == Fruit.mango:
can_buy_sapling = self.has_island_trader() & self.has(Fish.mussel_node)
return allowed_buy_sapling & can_buy_sapling
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)
def can_smelt(self, item: str) -> StardewRule:
return self.has(Machine.furnace) & self.has(item)
def can_complete_field_office(self) -> StardewRule:
@cached_property
def can_start_field_office(self) -> StardewRule:
field_office = self.region.can_reach(Region.field_office)
professor_snail = self.received("Open Professor Snail Cave")
tools = self.tool.has_tool(Tool.pickaxe) & self.tool.has_tool(Tool.hoe) & self.tool.has_tool(Tool.scythe)
leg_and_snake_skull = self.has_all(Fossil.fossilized_leg, Fossil.snake_skull)
ribs_and_spine = self.has_all(Fossil.fossilized_ribs, Fossil.fossilized_spine)
skull = self.has(Fossil.fossilized_skull)
tail = self.has(Fossil.fossilized_tail)
frog = self.has(Fossil.mummified_frog)
bat = self.has(Fossil.mummified_bat)
snake_vertebrae = self.has(Fossil.snake_vertebrae)
return field_office & professor_snail & tools & leg_and_snake_skull & ribs_and_spine & skull & tail & frog & bat & snake_vertebrae
return field_office & professor_snail
def can_complete_large_animal_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.fossilized_leg, Fossil.fossilized_ribs, Fossil.fossilized_skull, Fossil.fossilized_spine, Fossil.fossilized_tail)
return self.can_start_field_office & fossils
def can_complete_snake_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.snake_skull, Fossil.snake_vertebrae)
return self.can_start_field_office & fossils
def can_complete_frog_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.mummified_frog)
return self.can_start_field_office & fossils
def can_complete_bat_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.mummified_bat)
return self.can_start_field_office & fossils
def can_complete_field_office(self) -> StardewRule:
return self.can_complete_large_animal_collection() & self.can_complete_snake_collection() & \
self.can_complete_frog_collection() & self.can_complete_bat_collection()
def can_finish_grandpa_evaluation(self) -> StardewRule:
# https://stardewvalleywiki.com/Grandpa
@ -511,9 +503,9 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
# Catching every fish not expected
# Shipping every item not expected
self.relationship.can_get_married() & self.building.has_house(2),
self.relationship.has_hearts("5", 8), # 5 Friends
self.relationship.has_hearts("10", 8), # 10 friends
self.pet.has_hearts(5), # Max Pet
self.relationship.has_hearts_with_n(5, 8), # 5 Friends
self.relationship.has_hearts_with_n(10, 8), # 10 friends
self.pet.has_pet_hearts(5), # Max Pet
self.bundle.can_complete_community_center, # Community Center Completion
self.bundle.can_complete_community_center, # CC Ceremony first point
self.bundle.can_complete_community_center, # CC Ceremony second point
@ -523,23 +515,20 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
return self.count(12, *rules_worth_a_point)
def can_win_egg_hunt(self) -> StardewRule:
number_of_movement_buffs = self.options.movement_buff_number
if self.options.festival_locations == FestivalLocations.option_hard or number_of_movement_buffs < 2:
return True_()
return self.received(Buff.movement, number_of_movement_buffs // 2)
def can_succeed_luau_soup(self) -> StardewRule:
if self.options.festival_locations != FestivalLocations.option_hard:
return True_()
eligible_fish = [Fish.blobfish, Fish.crimsonfish, "Ice Pip", Fish.lava_eel, Fish.legend, Fish.angler, Fish.catfish, Fish.glacierfish, Fish.mutant_carp,
Fish.spookfish, Fish.stingray, Fish.sturgeon, "Super Cucumber"]
fish_rule = self.has_any(*eligible_fish)
eligible_kegables = [Fruit.ancient_fruit, Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.melon,
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.has_any(*(f for f in eligible_fish if f in self.content.fishes)) # To filter stingray
eligible_kegables = (Fruit.ancient_fruit, Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.melon,
Fruit.orange, Fruit.peach, Fruit.pineapple, Fruit.pomegranate, Fruit.rhubarb, Fruit.starfruit, Fruit.strawberry,
Forageable.cactus_fruit, Fruit.cherry, Fruit.cranberries, Fruit.grape, Forageable.spice_berry, Forageable.wild_plum,
Vegetable.hops, Vegetable.wheat]
keg_rules = [self.artisan.can_keg(kegable) for kegable in eligible_kegables]
aged_rule = self.has(Machine.cask) & Or(*keg_rules)
Vegetable.hops, Vegetable.wheat)
keg_rules = [self.artisan.can_keg(kegable) for kegable in eligible_kegables if kegable in self.content.game_items]
aged_rule = self.has(Machine.cask) & self.logic.or_(*keg_rules)
# There are a few other valid items, but I don't feel like coding them all
return fish_rule | aged_rule
@ -553,11 +542,17 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
fish_rule = self.skill.can_fish(difficulty=50)
forage_rule = self.region.can_reach_any((Region.forest, Region.backwoods)) # Hazelnut always available since the grange display is in fall
mineral_rule = self.action.can_open_geode(Generic.any) # More than half the minerals are good enough
good_fruits = [Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.orange, Fruit.peach, Fruit.pomegranate,
Fruit.strawberry, Fruit.melon, Fruit.rhubarb, Fruit.pineapple, Fruit.ancient_fruit, Fruit.starfruit, ]
good_fruits = (fruit
for fruit in
(Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.orange, Fruit.peach, Fruit.pomegranate,
Fruit.strawberry, Fruit.melon, Fruit.rhubarb, Fruit.pineapple, Fruit.ancient_fruit, Fruit.starfruit)
if fruit in self.content.game_items)
fruit_rule = self.has_any(*good_fruits)
good_vegetables = [Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.cauliflower, Forageable.fiddlehead_fern, Vegetable.kale,
Vegetable.radish, Vegetable.taro_root, Vegetable.yam, Vegetable.red_cabbage, Vegetable.pumpkin]
good_vegetables = (vegeteable
for vegeteable in
(Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.cauliflower, Forageable.fiddlehead_fern, Vegetable.kale,
Vegetable.radish, Vegetable.taro_root, Vegetable.yam, Vegetable.red_cabbage, Vegetable.pumpkin)
if vegeteable in self.content.game_items)
vegetable_rule = self.has_any(*good_vegetables)
return animal_rule & artisan_rule & cooking_rule & fish_rule & \
@ -576,6 +571,44 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
return False_()
if number <= 0:
return True_()
if self.options.walnutsanity == Walnutsanity.preset_none:
return self.can_get_walnuts(number)
if self.options.walnutsanity == Walnutsanity.preset_all:
return self.has_received_walnuts(number)
puzzle_walnuts = 61
bush_walnuts = 25
dig_walnuts = 18
repeatable_walnuts = 33
total_walnuts = puzzle_walnuts + bush_walnuts + dig_walnuts + repeatable_walnuts
walnuts_to_receive = 0
walnuts_to_collect = number
if OptionName.walnutsanity_puzzles in self.options.walnutsanity:
puzzle_walnut_rate = puzzle_walnuts / total_walnuts
puzzle_walnuts_required = round(puzzle_walnut_rate * number)
walnuts_to_receive += puzzle_walnuts_required
walnuts_to_collect -= puzzle_walnuts_required
if OptionName.walnutsanity_bushes in self.options.walnutsanity:
bush_walnuts_rate = bush_walnuts / total_walnuts
bush_walnuts_required = round(bush_walnuts_rate * number)
walnuts_to_receive += bush_walnuts_required
walnuts_to_collect -= bush_walnuts_required
if OptionName.walnutsanity_dig_spots in self.options.walnutsanity:
dig_walnuts_rate = dig_walnuts / total_walnuts
dig_walnuts_required = round(dig_walnuts_rate * number)
walnuts_to_receive += dig_walnuts_required
walnuts_to_collect -= dig_walnuts_required
if OptionName.walnutsanity_repeatables in self.options.walnutsanity:
repeatable_walnuts_rate = repeatable_walnuts / total_walnuts
repeatable_walnuts_required = round(repeatable_walnuts_rate * number)
walnuts_to_receive += repeatable_walnuts_required
walnuts_to_collect -= repeatable_walnuts_required
return self.has_received_walnuts(walnuts_to_receive) & self.can_get_walnuts(walnuts_to_collect)
def has_received_walnuts(self, number: int) -> StardewRule:
return self.received(Event.received_walnuts, number)
def can_get_walnuts(self, number: int) -> StardewRule:
# https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations
reach_south = self.region.can_reach(Region.island_south)
reach_north = self.region.can_reach(Region.island_north)
@ -584,28 +617,28 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
reach_southeast = self.region.can_reach(Region.island_south_east)
reach_field_office = self.region.can_reach(Region.field_office)
reach_pirate_cove = self.region.can_reach(Region.pirate_cove)
reach_outside_areas = And(reach_south, reach_north, reach_west, reach_hut)
reach_outside_areas = self.logic.and_(reach_south, reach_north, reach_west, reach_hut)
reach_volcano_regions = [self.region.can_reach(Region.volcano),
self.region.can_reach(Region.volcano_secret_beach),
self.region.can_reach(Region.volcano_floor_5),
self.region.can_reach(Region.volcano_floor_10)]
reach_volcano = Or(*reach_volcano_regions)
reach_all_volcano = And(*reach_volcano_regions)
reach_volcano = self.logic.or_(*reach_volcano_regions)
reach_all_volcano = self.logic.and_(*reach_volcano_regions)
reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office]
reach_caves = And(self.region.can_reach(Region.qi_walnut_room), self.region.can_reach(Region.dig_site),
reach_caves = self.logic.and_(self.region.can_reach(Region.qi_walnut_room), self.region.can_reach(Region.dig_site),
self.region.can_reach(Region.gourmand_frog_cave),
self.region.can_reach(Region.colored_crystals_cave),
self.region.can_reach(Region.shipwreck), self.received(APWeapon.slingshot))
reach_entire_island = And(reach_outside_areas, reach_all_volcano,
self.region.can_reach(Region.shipwreck), self.combat.has_slingshot)
reach_entire_island = self.logic.and_(reach_outside_areas, reach_all_volcano,
reach_caves, reach_southeast, reach_field_office, reach_pirate_cove)
if number <= 5:
return Or(reach_south, reach_north, reach_west, reach_volcano)
return self.logic.or_(reach_south, reach_north, reach_west, reach_volcano)
if number <= 10:
return self.count(2, *reach_walnut_regions)
if number <= 15:
return self.count(3, *reach_walnut_regions)
if number <= 20:
return And(*reach_walnut_regions)
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)
@ -621,20 +654,22 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
number_of_stardrops_to_receive += 1 # Museum Stardrop
number_of_stardrops_to_receive += 1 # Krobus Stardrop
if self.options.fishsanity == Fishsanity.option_none: # Master Angler Stardrop
other_rules.append(self.fishing.can_catch_every_fish())
else:
# Master Angler Stardrop
if self.content.features.fishsanity.is_enabled:
number_of_stardrops_to_receive += 1
else:
other_rules.append(self.fishing.can_catch_every_fish())
if self.options.festival_locations == FestivalLocations.option_disabled: # Fair Stardrop
other_rules.append(self.season.has(Season.fall))
else:
number_of_stardrops_to_receive += 1
if self.options.friendsanity == Friendsanity.option_none: # Spouse Stardrop
other_rules.append(self.relationship.has_hearts(Generic.bachelor, 13))
else:
# Spouse Stardrop
if self.content.features.friendsanity.is_enabled:
number_of_stardrops_to_receive += 1
else:
other_rules.append(self.relationship.has_hearts_with_any_bachelor(13))
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
number_of_stardrops_to_receive += 1
@ -642,18 +677,13 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
if not other_rules:
return self.received("Stardrop", number_of_stardrops_to_receive)
return self.received("Stardrop", number_of_stardrops_to_receive) & And(*other_rules)
def has_prismatic_jelly_reward_access(self) -> StardewRule:
if self.options.special_order_locations == SpecialOrderLocations.option_disabled:
return self.special_order.can_complete_special_order("Prismatic Jelly")
return self.received("Monster Musk Recipe")
return self.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules)
def has_all_rarecrows(self) -> StardewRule:
rules = []
for rarecrow_number in range(1, 9):
rules.append(self.received(f"Rarecrow #{rarecrow_number}"))
return And(*rules)
return self.logic.and_(*rules)
def has_abandoned_jojamart(self) -> StardewRule:
return self.received(CommunityUpgrade.movie_theater, 1)
@ -664,11 +694,5 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogi
def can_use_obelisk(self, obelisk: str) -> StardewRule:
return self.region.can_reach(Region.farm) & self.received(obelisk)
def has_fruit_bats(self) -> StardewRule:
return self.region.can_reach(Region.farm_cave) & self.received(CommunityUpgrade.fruit_bats)
def has_mushroom_cave(self) -> StardewRule:
return self.region.can_reach(Region.farm_cave) & self.received(CommunityUpgrade.mushroom_boxes)
def can_fish_pond(self, fish: str) -> StardewRule:
return self.building.has_building(Building.fish_pond) & self.has(fish)

View File

@ -56,3 +56,20 @@ dependencies. Vanilla would always be first, then anything that depends only on
4. In `set_rules`, the rules are applied to the AP entrances and locations. Each content pack have to apply the proper rules for their entrances and locations.
- (idea) To begin this step, sphere 0 could be simplified instantly as sphere 0 regions and items are already known.
5. Nothing to do in `generate_basic`.
## Item Sources
Instead of containing rules directly, items would contain sources that would then be transformed into rules. Using a single dispatch mechanism, the sources will
be associated to their actual logic.
This system is extensible and easily maintainable in the ways that it decouple the rule and the actual items. Any "type" of item could be used with any "type"
of source (Monster drop and fish can have foraging sources).
- Mods requiring special rules can remove sources from vanilla content or wrap them to add their own logic (Magic add sources for some items), or change the
rules for monster drop sources.
- (idea) A certain difficulty level (or maybe tags) could be added to the source, to enable or disable them given settings chosen by the player. Someone with a
high grinding tolerance can enable "hard" or "grindy" sources. Some source that are pushed back in further spheres can be replaced by less forgiving sources
if easy logic is disabled. For instance, anything that requires money could be accessible as soon as you can sell something to someone (even wood).
Items are classified by their source. An item with a fishing or a crab pot source is considered a fish, an item dropping from a monster is a monster drop. An
item with a foraging source is a forageable. Items can fit in multiple categories.

View File

@ -0,0 +1,33 @@
from dataclasses import dataclass
from ..strings.ap_names import event_names
from ..strings.metal_names import MetalBar, Ore
from ..strings.region_names import Region
all_events = event_names.all_events.copy()
all_logic_events = list()
@dataclass(frozen=True)
class LogicEvent:
name: str
region: str
@dataclass(frozen=True)
class LogicItemEvent(LogicEvent):
item: str
def __init__(self, item: str, region: str):
super().__init__(f"{item} (Logic event)", region)
super().__setattr__("item", item)
def register_item_event(item: str, region: str = Region.farm):
event = LogicItemEvent(item, region)
all_logic_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):
register_item_event(i)

View File

@ -3,13 +3,15 @@ from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .combat_logic import CombatLogicMixin
from .cooking_logic import CookingLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .skill_logic import SkillLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..options import ToolProgression
from ..stardew_rule import StardewRule, And, True_
from ..stardew_rule import StardewRule, True_
from ..strings.performance_names import Performance
from ..strings.region_names import Region
from ..strings.skill_names import Skill
@ -22,7 +24,8 @@ class MineLogicMixin(BaseLogicMixin):
self.mine = MineLogic(*args, **kwargs)
class MineLogic(BaseLogic[Union[MineLogicMixin, RegionLogicMixin, ReceivedLogicMixin, CombatLogicMixin, ToolLogicMixin, SkillLogicMixin]]):
class MineLogic(BaseLogic[Union[HasLogicMixin, MineLogicMixin, RegionLogicMixin, ReceivedLogicMixin, CombatLogicMixin, ToolLogicMixin,
SkillLogicMixin, CookingLogicMixin]]):
# Regions
def can_mine_in_the_mines_floor_1_40(self) -> StardewRule:
return self.logic.region.can_reach(Region.mines_floor_5)
@ -57,11 +60,13 @@ class MineLogic(BaseLogic[Union[MineLogicMixin, RegionLogicMixin, ReceivedLogicM
rules.append(weapon_rule)
if self.options.tool_progression & ToolProgression.option_progressive:
rules.append(self.logic.tool.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier]))
if self.options.skill_progression == options.SkillProgression.option_progressive:
if self.options.skill_progression >= options.SkillProgression.option_progressive:
skill_tier = min(10, max(0, tier * 2))
rules.append(self.logic.skill.has_level(Skill.combat, skill_tier))
rules.append(self.logic.skill.has_level(Skill.mining, skill_tier))
return And(*rules)
if tier >= 4:
rules.append(self.logic.cooking.can_cook())
return self.logic.and_(*rules)
@cache_self1
def has_mine_elevator_to_floor(self, floor: int) -> StardewRule:
@ -79,8 +84,8 @@ class MineLogic(BaseLogic[Union[MineLogicMixin, RegionLogicMixin, ReceivedLogicM
rules.append(weapon_rule)
if self.options.tool_progression & ToolProgression.option_progressive:
rules.append(self.logic.received("Progressive Pickaxe", min(4, max(0, tier + 2))))
if self.options.skill_progression == options.SkillProgression.option_progressive:
if self.options.skill_progression >= options.SkillProgression.option_progressive:
skill_tier = min(10, max(0, tier * 2 + 6))
rules.extend({self.logic.skill.has_level(Skill.combat, skill_tier),
self.logic.skill.has_level(Skill.mining, skill_tier)})
return And(*rules)
return self.logic.and_(*rules)

View File

@ -2,16 +2,18 @@ from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .buff_logic import BuffLogicMixin
from .grind_logic import GrindLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from .time_logic import TimeLogicMixin
from ..data.shop import ShopSource
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_
from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_, true_
from ..strings.ap_names.event_names import Event
from ..strings.currency_names import Currency
from ..strings.region_names import Region
from ..strings.region_names import Region, LogicRegion
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")
@ -23,7 +25,8 @@ class MoneyLogicMixin(BaseLogicMixin):
self.money = MoneyLogic(*args, **kwargs)
class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, BuffLogicMixin]]):
class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SeasonLogicMixin,
GrindLogicMixin]]):
@cache_self1
def can_have_earned_total(self, amount: int) -> StardewRule:
@ -31,7 +34,7 @@ class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMix
return 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, Region.fishing))
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.received(Event.can_ship_items)
@ -64,6 +67,20 @@ class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMix
def can_spend_at(self, region: str, amount: int) -> StardewRule:
return self.logic.region.can_reach(region) & self.logic.money.can_spend(amount)
@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_
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))
region_rule = self.logic.region.can_reach(source.shop_region)
return self.logic.and_(season_rule, money_rule, *item_rules, region_rule)
# Should be cached
def can_trade(self, currency: str, amount: int) -> StardewRule:
if amount == 0:
@ -71,11 +88,11 @@ class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMix
if currency == Currency.money:
return self.can_spend(amount)
if currency == Currency.star_token:
return self.logic.region.can_reach(Region.fair)
return self.logic.region.can_reach(LogicRegion.fair)
if currency == Currency.qi_coin:
return self.logic.region.can_reach(Region.casino) & self.logic.buff.has_max_luck()
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.option_board_qi:
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
@ -84,7 +101,7 @@ class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMix
if currency == Currency.golden_walnut:
return self.can_spend_walnut(amount)
return self.logic.has(currency) & self.logic.time.has_lived_months(amount)
return self.logic.has(currency) & self.logic.grind.can_grind_item(amount)
# Should be cached
def can_trade_at(self, region: str, currency: str, amount: int) -> StardewRule:

View File

@ -4,11 +4,13 @@ from typing import Iterable, Union, Hashable
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 ..data import monster_data
from ..stardew_rule import StardewRule, Or, And
from ..stardew_rule import StardewRule
from ..strings.generic_names import Generic
from ..strings.region_names import Region
@ -18,7 +20,7 @@ class MonsterLogicMixin(BaseLogicMixin):
self.monster = MonsterLogic(*args, **kwargs)
class MonsterLogic(BaseLogic[Union[MonsterLogicMixin, RegionLogicMixin, CombatLogicMixin, TimeLogicMixin]]):
class MonsterLogic(BaseLogic[Union[HasLogicMixin, MonsterLogicMixin, RegionLogicMixin, CombatLogicMixin, TimeLogicMixin]]):
@cached_property
def all_monsters_by_name(self):
@ -29,13 +31,18 @@ class MonsterLogic(BaseLogic[Union[MonsterLogicMixin, RegionLogicMixin, CombatLo
return monster_data.all_monsters_by_category_given_mods(self.options.mods.value)
def can_kill(self, monster: Union[str, monster_data.StardewMonster], amount_tier: int = 0) -> StardewRule:
if isinstance(monster, str):
monster = self.all_monsters_by_name[monster]
region_rule = self.logic.region.can_reach_any(monster.locations)
combat_rule = self.logic.combat.can_fight_at_level(monster.difficulty)
if amount_tier <= 0:
amount_tier = 0
time_rule = self.logic.time.has_lived_months(amount_tier)
if isinstance(monster, str):
if monster == Generic.any:
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)
combat_rule = self.logic.combat.can_fight_at_level(monster.difficulty)
return region_rule & combat_rule & time_rule
@cache_self1
@ -48,13 +55,11 @@ class MonsterLogic(BaseLogic[Union[MonsterLogicMixin, RegionLogicMixin, CombatLo
# Should be cached
def can_kill_any(self, monsters: (Iterable[monster_data.StardewMonster], Hashable), amount_tier: int = 0) -> StardewRule:
rules = [self.logic.monster.can_kill(monster, amount_tier) for monster in monsters]
return Or(*rules)
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:
rules = [self.logic.monster.can_kill(monster, amount_tier) for monster in monsters]
return And(*rules)
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]
@ -66,4 +71,4 @@ class MonsterLogic(BaseLogic[Union[MonsterLogicMixin, RegionLogicMixin, CombatLo
continue
rules.append(self.logic.monster.can_kill_any(self.all_monsters_by_category[category]))
return And(*rules)
return self.logic.and_(*rules)

View File

@ -6,10 +6,14 @@ from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..data.museum_data import MuseumItem, all_museum_items, all_museum_artifacts, all_museum_minerals
from ..stardew_rule import StardewRule, And, False_
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
class MuseumLogicMixin(BaseLogicMixin):
@ -18,7 +22,7 @@ class MuseumLogicMixin(BaseLogicMixin):
self.museum = MuseumLogic(*args, **kwargs)
class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, ActionLogicMixin, MuseumLogicMixin]]):
class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, ActionLogicMixin, ToolLogicMixin, MuseumLogicMixin]]):
def can_donate_museum_items(self, number: int) -> StardewRule:
return self.logic.region.can_reach(Region.museum) & self.logic.museum.can_find_museum_items(number)
@ -33,15 +37,16 @@ class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogic
else:
region_rule = False_()
if item.geodes:
geodes_rule = And(*(self.logic.action.can_open_geode(geode) for geode in item.geodes))
geodes_rule = self.logic.and_(*(self.logic.action.can_open_geode(geode) for geode in item.geodes))
else:
geodes_rule = False_()
# monster_rule = self.can_farm_monster(item.monsters)
# extra_rule = True_()
time_needed_to_grind = (20 - item.difficulty) / 2
time_rule = self.logic.time.has_lived_months(time_needed_to_grind)
pan_rule = False_()
if item.item_name == "Earth Crystal" or item.item_name == "Fire Quartz" or item.item_name == "Frozen Tear":
pan_rule = self.logic.action.can_pan()
return pan_rule | region_rule | geodes_rule # & monster_rule & extra_rule
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
def can_find_museum_artifacts(self, number: int) -> StardewRule:
rules = []
@ -74,7 +79,7 @@ class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogic
for donation in all_museum_items:
rules.append(self.logic.museum.can_find_museum_item(donation))
return And(*rules) & self.logic.region.can_reach(Region.museum)
return self.logic.and_(*rules) & self.logic.region.can_reach(Region.museum)
def can_donate(self, item: str) -> StardewRule:
return self.logic.has(item) & self.logic.region.can_reach(Region.museum)

View File

@ -6,11 +6,9 @@ from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from ..data.villagers_data import Villager
from ..options import Friendsanity
from ..content.feature.friendsanity import pet_heart_item_name
from ..stardew_rule import StardewRule, True_
from ..strings.region_names import Region
from ..strings.villager_names import NPC
class PetLogicMixin(BaseLogicMixin):
@ -20,21 +18,25 @@ class PetLogicMixin(BaseLogicMixin):
class PetLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, TimeLogicMixin, ToolLogicMixin]]):
def has_hearts(self, hearts: int = 1) -> StardewRule:
if hearts <= 0:
def has_pet_hearts(self, hearts: int = 1) -> StardewRule:
assert hearts >= 0, "You can't have negative hearts with a pet."
if hearts == 0:
return True_()
if self.options.friendsanity == Friendsanity.option_none or self.options.friendsanity == Friendsanity.option_bachelors:
return self.can_befriend_pet(hearts)
return self.received_hearts(NPC.pet, hearts)
def received_hearts(self, npc: Union[str, Villager], hearts: int) -> StardewRule:
if isinstance(npc, Villager):
return self.received_hearts(npc.name, hearts)
return self.logic.received(self.heart(npc), math.ceil(hearts / self.options.friendsanity_heart_size))
if self.content.features.friendsanity.is_pet_randomized:
return self.received_pet_hearts(hearts)
return self.can_befriend_pet(hearts)
def received_pet_hearts(self, hearts: int) -> StardewRule:
return self.logic.received(pet_heart_item_name,
math.ceil(hearts / self.content.features.friendsanity.heart_size))
def can_befriend_pet(self, hearts: int) -> StardewRule:
if hearts <= 0:
assert hearts >= 0, "You can't have negative hearts with a pet."
if hearts == 0:
return True_()
points = hearts * 200
points_per_month = 12 * 14
points_per_water_month = 18 * 14
@ -43,8 +45,3 @@ class PetLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, TimeLogicMi
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
def heart(self, npc: Union[str, Villager]) -> str:
if isinstance(npc, str):
return f"{npc} <3"
return self.heart(npc.name)

View File

@ -0,0 +1,33 @@
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
class QualityLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.quality = QualityLogic(*args, **kwargs)
class QualityLogic(BaseLogic[Union[SkillLogicMixin, FarmingLogicMixin]]):
@cache_self1
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)
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))
if quality == CropQuality.iridium:
return self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(4)
return False_()

View File

@ -17,6 +17,7 @@ from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .wallet_logic import WalletLogicMixin
from ..stardew_rule import StardewRule, Has, True_
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.craftable_names import Craftable
@ -43,7 +44,8 @@ class QuestLogicMixin(BaseLogicMixin):
class QuestLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, MoneyLogicMixin, MineLogicMixin, RegionLogicMixin, RelationshipLogicMixin, ToolLogicMixin,
FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillLogicMixin, WalletLogicMixin, QuestLogicMixin, BuildingLogicMixin, TimeLogicMixin]]):
FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillLogicMixin, WalletLogicMixin, QuestLogicMixin,
BuildingLogicMixin, TimeLogicMixin]]):
def initialize_rules(self):
self.update_rules({
@ -52,6 +54,7 @@ FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillL
Quest.getting_started: self.logic.has(Vegetable.parsnip),
Quest.to_the_beach: self.logic.region.can_reach(Region.beach),
Quest.raising_animals: self.logic.quest.can_complete_quest(Quest.getting_started) & self.logic.building.has_building(Building.coop),
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.skill.can_fish(),
Quest.rat_problem: self.logic.region.can_reach_all((Region.town, Region.community_center)),
@ -63,7 +66,8 @@ FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillL
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) &
self.logic.relationship.can_meet(NPC.lewis),
Quest.blackberry_basket: self.logic.season.has(Season.fall) & self.logic.relationship.can_meet(NPC.linus),
Quest.blackberry_basket: self.logic.season.has(Season.fall) & self.logic.relationship.can_meet(NPC.linus) & self.logic.region.can_reach(
Region.tunnel_entrance),
Quest.marnies_request: self.logic.relationship.has_hearts(NPC.marnie, 3) & self.logic.has(Forageable.cave_carrot),
Quest.pam_is_thirsty: self.logic.season.has(Season.summer) & self.logic.has(ArtisanGood.pale_ale) & self.logic.relationship.can_meet(NPC.pam),
Quest.a_dark_reagent: self.logic.season.has(Season.winter) & self.logic.has(Loot.void_essence) & self.logic.relationship.can_meet(NPC.wizard),
@ -104,13 +108,14 @@ FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillL
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)
})
def update_rules(self, new_rules: Dict[str, StardewRule]):
self.registry.quest_rules.update(new_rules)
def can_complete_quest(self, quest: str) -> StardewRule:
return Has(quest, self.registry.quest_rules)
return Has(quest, self.registry.quest_rules, "quest")
def has_club_card(self) -> StardewRule:
if self.options.quest_locations < 0:
@ -126,3 +131,12 @@ FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillL
if self.options.quest_locations < 0:
return self.logic.quest.can_complete_quest(Quest.dark_talisman)
return self.logic.received(Wallet.dark_talisman)
def has_raccoon_shop(self) -> StardewRule:
if self.options.quest_locations < 0:
return self.logic.received(CommunityUpgrade.raccoon, 2) & self.logic.quest.can_complete_quest(Quest.giant_stump)
# 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)

View File

@ -1,26 +1,32 @@
from typing import Optional
from BaseClasses import ItemClassification
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from ..stardew_rule import StardewRule, Received, And, Or, TotalReceived
from .logic_event import all_events
from ..items import item_table
from ..stardew_rule import StardewRule, Received, TotalReceived
class ReceivedLogicMixin(BaseLogic[HasLogicMixin], BaseLogicMixin):
# Should be cached
def received(self, item: str, count: Optional[int] = 1) -> StardewRule:
assert count >= 0, "Can't receive a negative amount of item."
if item in all_events:
return Received(item, self.player, count, event=True)
assert item_table[item].classification & ItemClassification.progression, f"Item [{item_table[item].name}] has to be progression to be used in logic"
return Received(item, self.player, count)
def received_all(self, *items: str):
assert items, "Can't receive all of no items."
return And(*(self.received(item) for item in items))
return self.logic.and_(*(self.received(item) for item in items))
def received_any(self, *items: str):
assert items, "Can't receive any of no items."
return Or(*(self.received(item) for item in items))
return self.logic.or_(*(self.received(item) for item in items))
def received_once(self, *items: str, count: int):
assert items, "Can't receive once of no items."
@ -32,4 +38,7 @@ class ReceivedLogicMixin(BaseLogic[HasLogicMixin], BaseLogicMixin):
assert items, "Can't receive n of no items."
assert count >= 0, "Can't receive a negative amount of item."
for item in items:
assert item_table[item].classification & ItemClassification.progression, f"Item [{item_table[item].name}] has to be progression to be used in logic"
return TotalReceived(count, items, self.player)

View File

@ -4,7 +4,7 @@ from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from ..options import EntranceRandomization
from ..stardew_rule import StardewRule, And, Or, Reach, false_, true_
from ..stardew_rule import StardewRule, Reach, false_, true_
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,
@ -18,6 +18,7 @@ always_accessible_regions_without_er = {*main_outside_area, Region.community_cen
always_regions_by_setting = {EntranceRandomization.option_disabled: always_accessible_regions_without_er,
EntranceRandomization.option_pelican_town: always_accessible_regions_without_er,
EntranceRandomization.option_non_progression: always_accessible_regions_without_er,
EntranceRandomization.option_buildings_without_house: main_outside_area,
EntranceRandomization.option_buildings: main_outside_area,
EntranceRandomization.option_chaos: always_accessible_regions_without_er}
@ -42,11 +43,14 @@ class RegionLogic(BaseLogic[Union[RegionLogicMixin, HasLogicMixin]]):
@cache_self1
def can_reach_any(self, region_names: Tuple[str, ...]) -> StardewRule:
return Or(*(self.logic.region.can_reach(spot) for spot in region_names))
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:
return And(*(self.logic.region.can_reach(spot) for spot in region_names))
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:

View File

@ -1,6 +1,5 @@
import math
from functools import cached_property
from typing import Union, List
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
@ -11,9 +10,9 @@ from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from .time_logic import TimeLogicMixin
from ..data.villagers_data import all_villagers_by_name, Villager, get_villagers_for_mods
from ..options import Friendsanity
from ..stardew_rule import StardewRule, True_, And, Or
from ..content.feature import friendsanity
from ..data.villagers_data import Villager
from ..stardew_rule import StardewRule, True_, false_, true_
from ..strings.ap_names.mods.mod_items import SVEQuestItem
from ..strings.crop_names import Fruit
from ..strings.generic_names import Generic
@ -38,12 +37,8 @@ class RelationshipLogicMixin(BaseLogicMixin):
self.relationship = RelationshipLogic(*args, **kwargs)
class RelationshipLogic(BaseLogic[Union[
RelationshipLogicMixin, BuildingLogicMixin, SeasonLogicMixin, TimeLogicMixin, GiftLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
@cached_property
def all_villagers_given_mods(self) -> List[Villager]:
return get_villagers_for_mods(self.options.mods.value)
class RelationshipLogic(BaseLogic[Union[RelationshipLogicMixin, BuildingLogicMixin, SeasonLogicMixin, TimeLogicMixin, GiftLogicMixin, RegionLogicMixin,
ReceivedLogicMixin, HasLogicMixin]]):
def can_date(self, npc: str) -> StardewRule:
return self.logic.relationship.has_hearts(npc, 8) & self.logic.has(Gift.bouquet)
@ -52,134 +47,160 @@ class RelationshipLogic(BaseLogic[Union[
return self.logic.relationship.has_hearts(npc, 10) & self.logic.has(Gift.mermaid_pendant)
def can_get_married(self) -> StardewRule:
return self.logic.relationship.has_hearts(Generic.bachelor, 10) & self.logic.has(Gift.mermaid_pendant)
return self.logic.relationship.has_hearts_with_any_bachelor(10) & self.logic.has(Gift.mermaid_pendant)
def has_children(self, number_children: int) -> StardewRule:
if number_children <= 0:
assert number_children >= 0, "Can't have a negative amount of children."
if number_children == 0:
return True_()
if self.options.friendsanity == Friendsanity.option_none:
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_house(2)
def can_reproduce(self, number_children: int = 1) -> StardewRule:
if number_children <= 0:
assert number_children >= 0, "Can't have a negative amount of children."
if number_children == 0:
return True_()
baby_rules = [self.logic.relationship.can_get_married(), self.logic.building.has_house(2), self.logic.relationship.has_hearts(Generic.bachelor, 12),
baby_rules = [self.logic.relationship.can_get_married(),
self.logic.building.has_house(2),
self.logic.relationship.has_hearts_with_any_bachelor(12),
self.logic.relationship.has_children(number_children - 1)]
return And(*baby_rules)
return self.logic.and_(*baby_rules)
@cache_self1
def has_hearts_with_any_bachelor(self, hearts: int = 1) -> StardewRule:
assert hearts >= 0, f"Can't have a negative hearts with any bachelor."
if hearts == 0:
return True_()
return self.logic.or_(*(self.logic.relationship.has_hearts(name, hearts)
for name, villager in self.content.villagers.items()
if villager.bachelor))
@cache_self1
def has_hearts_with_any(self, hearts: int = 1) -> StardewRule:
assert hearts >= 0, f"Can't have a negative hearts with any npc."
if hearts == 0:
return True_()
return self.logic.or_(*(self.logic.relationship.has_hearts(name, hearts)
for name, villager in self.content.villagers.items()))
def has_hearts_with_n(self, amount: int, hearts: int = 1) -> StardewRule:
assert hearts >= 0, f"Can't have a negative hearts with any npc."
assert amount >= 0, f"Can't have a negative amount of npc."
if hearts == 0 or amount == 0:
return True_()
return self.logic.count(amount, *(self.logic.relationship.has_hearts(name, hearts)
for name, villager in self.content.villagers.items()))
# Should be cached
def has_hearts(self, npc: str, hearts: int = 1) -> StardewRule:
if hearts <= 0:
return True_()
if self.options.friendsanity == Friendsanity.option_none:
return self.logic.relationship.can_earn_relationship(npc, hearts)
if npc not in all_villagers_by_name:
if npc == Generic.any or npc == Generic.bachelor:
possible_friends = []
for name in all_villagers_by_name:
if not self.npc_is_in_current_slot(name):
continue
if npc == Generic.any or all_villagers_by_name[name].bachelor:
possible_friends.append(self.logic.relationship.has_hearts(name, hearts))
return Or(*possible_friends)
if npc == Generic.all:
mandatory_friends = []
for name in all_villagers_by_name:
if not self.npc_is_in_current_slot(name):
continue
mandatory_friends.append(self.logic.relationship.has_hearts(name, hearts))
return And(*mandatory_friends)
if npc.isnumeric():
possible_friends = []
for name in all_villagers_by_name:
if not self.npc_is_in_current_slot(name):
continue
possible_friends.append(self.logic.relationship.has_hearts(name, hearts))
return self.logic.count(int(npc), *possible_friends)
return self.can_earn_relationship(npc, hearts)
assert hearts >= 0, f"Can't have a negative hearts with {npc}."
if not self.npc_is_in_current_slot(npc):
return True_()
villager = all_villagers_by_name[npc]
if self.options.friendsanity == Friendsanity.option_bachelors and not villager.bachelor:
villager = self.content.villagers.get(npc)
if villager is None:
return false_
if hearts == 0:
return true_
heart_steps = self.content.features.friendsanity.get_randomized_hearts(villager)
if not heart_steps or hearts > heart_steps[-1]: # Hearts are sorted, bigger is the last one.
return self.logic.relationship.can_earn_relationship(npc, hearts)
if self.options.friendsanity == Friendsanity.option_starting_npcs and not villager.available:
return self.logic.relationship.can_earn_relationship(npc, hearts)
is_capped_at_8 = villager.bachelor and self.options.friendsanity != Friendsanity.option_all_with_marriage
if is_capped_at_8 and hearts > 8:
return self.logic.relationship.received_hearts(villager.name, 8) & self.logic.relationship.can_earn_relationship(npc, hearts)
return self.logic.relationship.received_hearts(villager.name, hearts)
return self.logic.relationship.received_hearts(villager, hearts)
# Should be cached
def received_hearts(self, npc: str, hearts: int) -> StardewRule:
heart_item = heart_item_name(npc)
number_required = math.ceil(hearts / self.options.friendsanity_heart_size)
return self.logic.received(heart_item, number_required)
def received_hearts(self, villager: Villager, hearts: int) -> StardewRule:
heart_item = friendsanity.to_item_name(villager.name)
number_required = math.ceil(hearts / self.content.features.friendsanity.heart_size)
return self.logic.received(heart_item, number_required) & self.can_meet(villager.name)
@cache_self1
def can_meet(self, npc: str) -> StardewRule:
if npc not in all_villagers_by_name or not self.npc_is_in_current_slot(npc):
return True_()
villager = all_villagers_by_name[npc]
villager = self.content.villagers.get(npc)
if villager is None:
return false_
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 West Turtle"))
rules.append(self.logic.received("Island North Turtle"))
elif npc == ModNPC.lance:
rules.append(self.logic.region.can_reach(Region.volcano_floor_10))
elif npc == ModNPC.apples:
rules.append(self.logic.has(Fruit.starfruit))
elif npc == ModNPC.scarlett:
scarlett_job = self.logic.received(SVEQuestItem.scarlett_job_offer)
scarlett_spring = self.logic.season.has(Season.spring) & self.can_meet(ModNPC.andy)
scarlett_summer = self.logic.season.has(Season.summer) & self.can_meet(ModNPC.susan)
scarlett_fall = self.logic.season.has(Season.fall) & self.can_meet(ModNPC.sophia)
rules.append(scarlett_job & (scarlett_spring | scarlett_summer | scarlett_fall))
elif npc == ModNPC.morgan:
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)))
return And(*rules)
return self.logic.and_(*rules)
def can_give_loved_gifts_to_everyone(self) -> StardewRule:
rules = []
for npc in all_villagers_by_name:
if not self.npc_is_in_current_slot(npc):
continue
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 And(*rules)
return self.logic.and_(*rules)
# Should be cached
def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule:
if hearts <= 0:
assert hearts >= 0, f"Can't have a negative hearts with {npc}."
villager = self.content.villagers.get(npc)
if villager is None:
return false_
if hearts == 0:
return True_()
previous_heart = hearts - self.options.friendsanity_heart_size
previous_heart_rule = self.logic.relationship.has_hearts(npc, previous_heart)
rules = [self.logic.relationship.can_meet(npc)]
if npc not in all_villagers_by_name or not self.npc_is_in_current_slot(npc):
return previous_heart_rule
heart_size = self.content.features.friendsanity.heart_size
max_randomized_hearts = self.content.features.friendsanity.get_randomized_hearts(villager)
if max_randomized_hearts:
if hearts > max_randomized_hearts[-1]:
rules.append(self.logic.relationship.has_hearts(npc, hearts - 1))
else:
previous_heart = max(hearts - heart_size, 0)
rules.append(self.logic.relationship.has_hearts(npc, previous_heart))
rules = [previous_heart_rule, self.logic.relationship.can_meet(npc)]
villager = all_villagers_by_name[npc]
if hearts > 2 or hearts > self.options.friendsanity_heart_size:
if hearts > 2 or hearts > heart_size:
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.bachelor:
if hearts > 8:
rules.append(self.logic.relationship.can_date(npc))
if hearts > 10:
rules.append(self.logic.relationship.can_marry(npc))
elif hearts > 8:
rules.append(self.logic.relationship.can_date(npc))
return And(*rules)
@cache_self1
def npc_is_in_current_slot(self, name: str) -> bool:
npc = all_villagers_by_name[name]
return npc in self.all_villagers_given_mods
return self.logic.and_(*rules)

View File

@ -0,0 +1,52 @@
import functools
from typing import Union, Iterable
from .base_logic import BaseLogicMixin, BaseLogic
from .book_logic import BookLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .season_logic import SeasonLogicMixin
from .skill_logic import SkillLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from ..data.game_item import Requirement
from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement
class RequirementLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.requirement = RequirementLogic(*args, **kwargs)
class RequirementLogic(BaseLogic[Union[RequirementLogicMixin, HasLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, BookLogicMixin,
SeasonLogicMixin, TimeLogicMixin]]):
def meet_all_requirements(self, requirements: Iterable[Requirement]):
if not requirements:
return self.logic.true_
return self.logic.and_(*(self.logic.requirement.meet_requirement(requirement) for requirement in requirements))
@functools.singledispatchmethod
def meet_requirement(self, requirement: Requirement):
raise ValueError(f"Requirements of type{type(requirement)} have no rule registered.")
@meet_requirement.register
def _(self, requirement: ToolRequirement):
return self.logic.tool.has_tool(requirement.tool, requirement.tier)
@meet_requirement.register
def _(self, requirement: SkillRequirement):
return self.logic.skill.has_level(requirement.skill, requirement.level)
@meet_requirement.register
def _(self, requirement: BookRequirement):
return self.logic.book.has_book_power(requirement.book)
@meet_requirement.register
def _(self, requirement: SeasonRequirement):
return self.logic.season.has(requirement.season)
@meet_requirement.register
def _(self, requirement: YearRequirement):
return self.logic.time.has_year(requirement.year)

View File

@ -1,11 +1,13 @@
from functools import cached_property
from typing import Iterable, Union
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_, Or, And
from ..stardew_rule import StardewRule, True_, true_
from ..strings.generic_names import Generic
from ..strings.season_names import Season
@ -16,7 +18,23 @@ class SeasonLogicMixin(BaseLogicMixin):
self.season = SeasonLogic(*args, **kwargs)
class SeasonLogic(BaseLogic[Union[SeasonLogicMixin, TimeLogicMixin, ReceivedLogicMixin]]):
class SeasonLogic(BaseLogic[Union[HasLogicMixin, SeasonLogicMixin, TimeLogicMixin, ReceivedLogicMixin]]):
@cached_property
def has_spring(self) -> StardewRule:
return self.logic.season.has(Season.spring)
@cached_property
def has_summer(self) -> StardewRule:
return self.logic.season.has(Season.summer)
@cached_property
def has_fall(self) -> StardewRule:
return self.logic.season.has(Season.fall)
@cached_property
def has_winter(self) -> StardewRule:
return self.logic.season.has(Season.winter)
@cache_self1
def has(self, season: str) -> StardewRule:
@ -32,13 +50,16 @@ class SeasonLogic(BaseLogic[Union[SeasonLogicMixin, TimeLogicMixin, ReceivedLogi
return self.logic.received(season)
def has_any(self, seasons: Iterable[str]):
if seasons == Season.all:
return true_
if not seasons:
# That should be false, but I'm scared.
return True_()
return Or(*(self.logic.season.has(season) for season in seasons))
return self.logic.or_(*(self.logic.season.has(season) for season in seasons))
def has_any_not_winter(self):
return self.logic.season.has_any([Season.spring, Season.summer, Season.fall])
def has_all(self):
seasons = [Season.spring, Season.summer, Season.fall, Season.winter]
return And(*(self.logic.season.has(season) for season in seasons))
return self.logic.and_(*(self.logic.season.has(season) for season in seasons))

View File

@ -10,7 +10,7 @@ from .region_logic import RegionLogicMixin
from ..locations import LocationTags, locations_by_tag
from ..options import ExcludeGingerIsland, Shipsanity
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, And
from ..stardew_rule import StardewRule
from ..strings.ap_names.event_names import Event
from ..strings.building_names import Building
@ -35,7 +35,7 @@ class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, Buil
shipsanity_prefix = "Shipsanity: "
all_items_to_ship = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_qi = self.options.special_order_locations != SpecialOrderLocations.option_board_qi
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:
@ -57,4 +57,4 @@ class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, Buil
if shipsanity_location.name not in all_location_names_in_slot:
continue
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
return And(*rules)
return self.logic.and_(*rules)

View File

@ -4,7 +4,7 @@ from typing import Union, Tuple
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .combat_logic import CombatLogicMixin
from .crop_logic import CropLogicMixin
from .harvesting_logic import HarvestingLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
@ -12,10 +12,10 @@ from .season_logic import SeasonLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..data import all_crops
from ..data.harvest import HarvestCropSource
from ..mods.logic.magic_logic import MagicLogicMixin
from ..mods.logic.mod_skills_levels import get_mod_skill_levels
from ..stardew_rule import StardewRule, True_, Or, False_
from ..stardew_rule import StardewRule, True_, False_, true_, And
from ..strings.craftable_names import Fishing
from ..strings.machine_names import Machine
from ..strings.performance_names import Performance
@ -23,8 +23,10 @@ from ..strings.quality_names import ForageQuality
from ..strings.region_names import Region
from ..strings.skill_names import Skill, all_mod_skills
from ..strings.tool_names import ToolMaterial, Tool
from ..strings.wallet_item_names import Wallet
fishing_regions = (Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west)
vanilla_skill_items = ("Farming Level", "Mining Level", "Foraging Level", "Fishing Level", "Combat Level")
class SkillLogicMixin(BaseLogicMixin):
@ -34,7 +36,8 @@ class SkillLogicMixin(BaseLogicMixin):
class SkillLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, ToolLogicMixin, SkillLogicMixin,
CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
CombatLogicMixin, MagicLogicMixin, HarvestingLogicMixin]]):
# Should be cached
def can_earn_level(self, skill: str, level: int) -> StardewRule:
if level <= 0:
@ -48,14 +51,15 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
if self.options.skill_progression != options.SkillProgression.option_vanilla:
previous_level_rule = self.logic.skill.has_level(skill, level - 1)
else:
previous_level_rule = True_()
previous_level_rule = true_
if skill == Skill.fishing:
xp_rule = self.logic.tool.has_fishing_rod(max(tool_level, 1))
xp_rule = self.logic.tool.has_fishing_rod(max(tool_level, 3))
elif skill == Skill.farming:
xp_rule = self.logic.tool.has_tool(Tool.hoe, tool_material) & self.logic.tool.can_water(tool_level)
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:
xp_rule = self.logic.tool.has_tool(Tool.axe, tool_material) | self.logic.magic.can_use_clear_debris_instead_of_tool_level(tool_level)
xp_rule = (self.can_get_foraging_xp & self.logic.tool.has_tool(Tool.axe, tool_material)) |\
self.logic.magic.can_use_clear_debris_instead_of_tool_level(tool_level)
elif skill == Skill.mining:
xp_rule = self.logic.tool.has_tool(Tool.pickaxe, tool_material) | \
self.logic.magic.can_use_clear_debris_instead_of_tool_level(tool_level)
@ -66,7 +70,7 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5)
elif skill in all_mod_skills:
# Ideal solution would be to add a logic registry, but I'm too lazy.
return self.logic.mod.skill.can_earn_mod_skill_level(skill, level)
return previous_level_rule & months_rule & self.logic.mod.skill.can_earn_mod_skill_level(skill, level)
else:
raise Exception(f"Unknown skill: {skill}")
@ -77,11 +81,11 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
if level <= 0:
return True_()
if self.options.skill_progression == options.SkillProgression.option_progressive:
return self.logic.received(f"{skill} Level", level)
if self.options.skill_progression == options.SkillProgression.option_vanilla:
return self.logic.skill.can_earn_level(skill, level)
return self.logic.received(f"{skill} Level", level)
@cache_self1
def has_farming_level(self, level: int) -> StardewRule:
return self.logic.skill.has_level(Skill.farming, level)
@ -91,8 +95,8 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
if level <= 0:
return True_()
if self.options.skill_progression == options.SkillProgression.option_progressive:
skills_items = ("Farming Level", "Mining Level", "Foraging Level", "Fishing Level", "Combat Level")
if self.options.skill_progression >= options.SkillProgression.option_progressive:
skills_items = vanilla_skill_items
if allow_modded_skills:
skills_items += get_mod_skill_levels(self.options.mods)
return self.logic.received_n(*skills_items, count=level)
@ -104,12 +108,26 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
return rule_with_fishing
return self.logic.time.has_lived_months(months_with_4_skills) | rule_with_fishing
def has_all_skills_maxed(self, included_modded_skills: bool = True) -> StardewRule:
if self.options.skill_progression == options.SkillProgression.option_vanilla:
return self.has_total_level(50)
skills_items = vanilla_skill_items
if included_modded_skills:
skills_items += get_mod_skill_levels(self.options.mods)
return And(*[self.logic.received(skill, 10) for skill in skills_items])
def can_enter_mastery_cave(self) -> StardewRule:
if self.options.skill_progression == options.SkillProgression.option_progressive_with_masteries:
return self.logic.received(Wallet.mastery_of_the_five_ways)
return self.has_all_skills_maxed()
@cached_property
def can_get_farming_xp(self) -> StardewRule:
sources = self.content.find_sources_of_type(HarvestCropSource)
crop_rules = []
for crop in all_crops:
crop_rules.append(self.logic.crop.can_grow(crop))
return Or(*crop_rules)
for crop_source in sources:
crop_rules.append(self.logic.harvesting.can_harvest_crop_from(crop_source))
return self.logic.or_(*crop_rules)
@cached_property
def can_get_foraging_xp(self) -> StardewRule:
@ -132,7 +150,7 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
@cached_property
def can_get_fishing_xp(self) -> StardewRule:
if self.options.skill_progression == options.SkillProgression.option_progressive:
if self.options.skill_progression >= options.SkillProgression.option_progressive:
return self.logic.skill.can_fish() | self.logic.skill.can_crab_pot
return self.logic.skill.can_fish()
@ -162,7 +180,7 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
@cached_property
def can_crab_pot(self) -> StardewRule:
crab_pot_rule = self.logic.has(Fishing.bait)
if self.options.skill_progression == options.SkillProgression.option_progressive:
if self.options.skill_progression >= options.SkillProgression.option_progressive:
crab_pot_rule = crab_pot_rule & self.logic.has(Machine.crab_pot)
else:
crab_pot_rule = crab_pot_rule & self.logic.skill.can_get_fishing_xp
@ -178,3 +196,14 @@ CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
if quality == ForageQuality.gold:
return self.has_level(Skill.foraging, 9)
return False_()
@cached_property
def can_earn_mastery_experience(self) -> StardewRule:
if self.options.skill_progression != options.SkillProgression.option_progressive_with_masteries:
return self.has_all_skills_maxed() & self.logic.time.has_lived_max_months
return self.logic.time.has_lived_max_months
def has_mastery(self, skill: str) -> StardewRule:
if self.options.skill_progression != options.SkillProgression.option_progressive_with_masteries:
return self.can_earn_mastery_experience and self.logic.region.can_reach(Region.mastery_cave)
return self.logic.received(f"{skill} Mastery")

View File

@ -0,0 +1,106 @@
import functools
from typing import Union, Any, Iterable
from .artisan_logic import ArtisanLogicMixin
from .base_logic import BaseLogicMixin, BaseLogic
from .grind_logic import GrindLogicMixin
from .harvesting_logic import HarvestingLogicMixin
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .requirement_logic import RequirementLogicMixin
from .tool_logic import ToolLogicMixin
from ..data.artisan import MachineSource
from ..data.game_item import GenericSource, ItemSource, GameItem, CustomRuleSource
from ..data.harvest import ForagingSource, FruitBatsSource, MushroomCaveSource, SeasonalForagingSource, \
HarvestCropSource, HarvestFruitTreeSource, ArtifactSpotSource
from ..data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource
class SourceLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.source = SourceLogic(*args, **kwargs)
class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogicMixin, HarvestingLogicMixin, MoneyLogicMixin, RegionLogicMixin,
ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
def has_access_to_item(self, item: GameItem):
rules = []
if self.content.features.cropsanity.is_included(item):
rules.append(self.logic.received(item.name))
rules.append(self.logic.source.has_access_to_any(item.sources))
return self.logic.and_(*rules)
def has_access_to_any(self, sources: Iterable[ItemSource]):
return self.logic.or_(*(self.logic.source.has_access_to(source) & self.logic.requirement.meet_all_requirements(source.other_requirements)
for source in sources))
@functools.singledispatchmethod
def has_access_to(self, source: Any):
raise ValueError(f"Sources of type{type(source)} have no rule registered.")
@has_access_to.register
def _(self, source: GenericSource):
return self.logic.region.can_reach_any(source.regions) if source.regions else self.logic.true_
@has_access_to.register
def _(self, source: CustomRuleSource):
return source.create_rule(self.logic)
@has_access_to.register
def _(self, source: ForagingSource):
return self.logic.harvesting.can_forage_from(source)
@has_access_to.register
def _(self, source: SeasonalForagingSource):
# Implementation could be different with some kind of "calendar shuffle"
return self.logic.harvesting.can_forage_from(source.as_foraging_source())
@has_access_to.register
def _(self, _: FruitBatsSource):
return self.logic.harvesting.can_harvest_from_fruit_bats
@has_access_to.register
def _(self, _: MushroomCaveSource):
return self.logic.harvesting.can_harvest_from_mushroom_cave
@has_access_to.register
def _(self, source: ShopSource):
return self.logic.money.can_shop_from(source)
@has_access_to.register
def _(self, source: HarvestFruitTreeSource):
return self.logic.harvesting.can_harvest_tree_from(source)
@has_access_to.register
def _(self, source: HarvestCropSource):
return self.logic.harvesting.can_harvest_crop_from(source)
@has_access_to.register
def _(self, source: MachineSource):
return self.logic.artisan.can_produce_from(source)
@has_access_to.register
def _(self, source: MysteryBoxSource):
return self.logic.grind.can_grind_mystery_boxes(source.amount)
@has_access_to.register
def _(self, source: ArtifactTroveSource):
return self.logic.grind.can_grind_artifact_troves(source.amount)
@has_access_to.register
def _(self, source: PrizeMachineSource):
return self.logic.grind.can_grind_prize_tickets(source.amount)
@has_access_to.register
def _(self, source: FishingTreasureChestSource):
return self.logic.grind.can_grind_fishing_treasure_chests(source.amount)
@has_access_to.register
def _(self, source: ArtifactSpotSource):
return self.logic.grind.can_grind_artifact_spots(source.amount)

View File

@ -4,7 +4,6 @@ from .ability_logic import AbilityLogicMixin
from .arcade_logic import ArcadeLogicMixin
from .artisan_logic import ArtisanLogicMixin
from .base_logic import BaseLogicMixin, BaseLogic
from .buff_logic import BuffLogicMixin
from .cooking_logic import CookingLogicMixin
from .has_logic import HasLogicMixin
from .mine_logic import MineLogicMixin
@ -18,7 +17,9 @@ from .shipping_logic import ShippingLogicMixin
from .skill_logic import SkillLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from ..stardew_rule import StardewRule, Has
from ..content.vanilla.ginger_island import ginger_island_content_pack
from ..content.vanilla.qi_board import qi_board_content_pack
from ..stardew_rule import StardewRule, Has, false_
from ..strings.animal_product_names import AnimalProduct
from ..strings.ap_names.event_names import Event
from ..strings.ap_names.transport_names import Transportation
@ -35,7 +36,6 @@ from ..strings.monster_names import Monster
from ..strings.region_names import Region
from ..strings.season_names import Season
from ..strings.special_order_names import SpecialOrder
from ..strings.tool_names import Tool
from ..strings.villager_names import NPC
@ -47,14 +47,11 @@ class SpecialOrderLogicMixin(BaseLogicMixin):
class SpecialOrderLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, MoneyLogicMixin,
ShippingLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, RelationshipLogicMixin, ToolLogicMixin, SkillLogicMixin,
MineLogicMixin, CookingLogicMixin, BuffLogicMixin,
MineLogicMixin, CookingLogicMixin,
AbilityLogicMixin, SpecialOrderLogicMixin, MonsterLogicMixin]]):
def initialize_rules(self):
self.update_rules({
SpecialOrder.island_ingredients: self.logic.relationship.can_meet(NPC.caroline) & self.logic.special_order.has_island_transport() &
self.logic.ability.can_farm_perfectly() & self.logic.shipping.can_ship(Vegetable.taro_root) &
self.logic.shipping.can_ship(Fruit.pineapple) & self.logic.shipping.can_ship(Forageable.ginger),
SpecialOrder.cave_patrol: self.logic.relationship.can_meet(NPC.clint),
SpecialOrder.aquatic_overpopulation: self.logic.relationship.can_meet(NPC.demetrius) & self.logic.ability.can_fish_perfectly(),
SpecialOrder.biome_balance: self.logic.relationship.can_meet(NPC.demetrius) & self.logic.ability.can_fish_perfectly(),
@ -66,46 +63,63 @@ AbilityLogicMixin, SpecialOrderLogicMixin, MonsterLogicMixin]]):
SpecialOrder.gus_famous_omelet: self.logic.has(AnimalProduct.any_egg),
SpecialOrder.crop_order: self.logic.ability.can_farm_perfectly() & self.logic.received(Event.can_ship_items),
SpecialOrder.community_cleanup: self.logic.skill.can_crab_pot,
SpecialOrder.the_strong_stuff: self.logic.artisan.can_keg(Vegetable.potato),
SpecialOrder.the_strong_stuff: self.logic.has(ArtisanGood.specific_juice(Vegetable.potato)),
SpecialOrder.pierres_prime_produce: self.logic.ability.can_farm_perfectly(),
SpecialOrder.robins_project: self.logic.relationship.can_meet(NPC.robin) & self.logic.ability.can_chop_perfectly() &
self.logic.has(Material.hardwood),
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),
})
if ginger_island_content_pack.name in self.content.registered_packs:
self.update_rules({
SpecialOrder.island_ingredients: self.logic.relationship.can_meet(NPC.caroline) & self.logic.special_order.has_island_transport() &
self.logic.ability.can_farm_perfectly() & self.logic.shipping.can_ship(Vegetable.taro_root) &
self.logic.shipping.can_ship(Fruit.pineapple) & self.logic.shipping.can_ship(Forageable.ginger),
SpecialOrder.tropical_fish: self.logic.relationship.can_meet(NPC.willy) & self.logic.received("Island Resort") &
self.logic.special_order.has_island_transport() &
self.logic.has(Fish.stingray) & self.logic.has(Fish.blue_discus) & self.logic.has(Fish.lionfish),
SpecialOrder.a_curious_substance: self.logic.region.can_reach(Region.wizard_tower),
SpecialOrder.prismatic_jelly: self.logic.region.can_reach(Region.wizard_tower),
})
else:
self.update_rules({
SpecialOrder.island_ingredients: false_,
SpecialOrder.tropical_fish: false_,
})
if qi_board_content_pack.name in self.content.registered_packs:
self.update_rules({
SpecialOrder.qis_crop: self.logic.ability.can_farm_perfectly() & self.logic.region.can_reach(Region.greenhouse) &
self.logic.region.can_reach(Region.island_west) & self.logic.skill.has_total_level(50) &
self.logic.has(Machine.seed_maker) & self.logic.received(Event.can_ship_items),
SpecialOrder.lets_play_a_game: self.logic.arcade.has_junimo_kart_max_level(),
SpecialOrder.four_precious_stones: self.logic.time.has_lived_max_months & self.logic.has("Prismatic Shard") &
self.logic.ability.can_mine_perfectly_in_the_skull_cavern(),
SpecialOrder.qis_hungry_challenge: self.logic.ability.can_mine_perfectly_in_the_skull_cavern() & self.logic.buff.has_max_buffs(),
SpecialOrder.qis_hungry_challenge: self.logic.ability.can_mine_perfectly_in_the_skull_cavern(),
SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.received(Event.can_ship_items) &
(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(),
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),
SpecialOrder.skull_cavern_invasion: self.logic.ability.can_mine_perfectly_in_the_skull_cavern() & self.logic.buff.has_max_buffs(),
SpecialOrder.skull_cavern_invasion: self.logic.ability.can_mine_perfectly_in_the_skull_cavern(),
SpecialOrder.qis_prismatic_grange: self.logic.has(Loot.bug_meat) & # 100 Bug Meat
self.logic.money.can_spend_at(Region.saloon, 24000) & # 100 Spaghetti
self.logic.money.can_spend_at(Region.blacksmith, 15000) & # 100 Copper Ore
self.logic.money.can_spend_at(Region.ranch, 5000) & # 100 Hay
self.logic.money.can_spend_at(Region.saloon, 22000) & # 100 Salads
self.logic.money.can_spend_at(Region.saloon, 7500) & # 100 Joja Cola
self.logic.money.can_spend(80000), # I need this extra rule because money rules aren't additive...
self.logic.money.can_spend(80000), # I need this extra rule because money rules aren't additive...)
})
def update_rules(self, new_rules: Dict[str, StardewRule]):
self.registry.special_order_rules.update(new_rules)
def can_complete_special_order(self, special_order: str) -> StardewRule:
return Has(special_order, self.registry.special_order_rules)
return Has(special_order, self.registry.special_order_rules, "special order")
def has_island_transport(self) -> StardewRule:
return self.logic.received(Transportation.island_obelisk) | self.logic.received(Transportation.boat_repair)

View File

@ -3,11 +3,17 @@ from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
from .received_logic import ReceivedLogicMixin
from ..stardew_rule import StardewRule, HasProgressionPercent, True_
from .has_logic import HasLogicMixin
from ..stardew_rule import StardewRule, HasProgressionPercent
MAX_MONTHS = 12
MONTH_COEFFICIENT = 24 // MAX_MONTHS
ONE_YEAR = 4
MAX_MONTHS = 3 * ONE_YEAR
PERCENT_REQUIRED_FOR_MAX_MONTHS = 48
MONTH_COEFFICIENT = PERCENT_REQUIRED_FOR_MAX_MONTHS // MAX_MONTHS
MIN_ITEMS = 10
MAX_ITEMS = 999
PERCENT_REQUIRED_FOR_MAX_ITEM = 24
class TimeLogicMixin(BaseLogicMixin):
@ -16,12 +22,12 @@ class TimeLogicMixin(BaseLogicMixin):
self.time = TimeLogic(*args, **kwargs)
class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]):
class TimeLogic(BaseLogic[Union[TimeLogicMixin, HasLogicMixin]]):
@cache_self1
def has_lived_months(self, number: int) -> StardewRule:
if number <= 0:
return True_()
return self.logic.true_
number = min(number, MAX_MONTHS)
return HasProgressionPercent(self.player, number * MONTH_COEFFICIENT)
@ -29,10 +35,18 @@ class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]):
def has_lived_max_months(self) -> StardewRule:
return self.logic.time.has_lived_months(MAX_MONTHS)
@cache_self1
def has_lived_year(self, number: int) -> StardewRule:
return self.logic.time.has_lived_months(number * ONE_YEAR)
@cache_self1
def has_year(self, number: int) -> StardewRule:
return self.logic.time.has_lived_year(number - 1)
@cached_property
def has_year_two(self) -> StardewRule:
return self.logic.time.has_lived_months(4)
return self.logic.time.has_year(2)
@cached_property
def has_year_three(self) -> StardewRule:
return self.logic.time.has_lived_months(8)
return self.logic.time.has_year(3)

View File

@ -1,4 +1,4 @@
from typing import Union, Iterable
from typing import Union, Iterable, Tuple
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
@ -42,9 +42,17 @@ class ToolLogicMixin(BaseLogicMixin):
class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, MagicLogicMixin]]):
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:
assert tool != Tool.fishing_rod, "Use `has_fishing_rod` instead of `has_tool`."
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
if material == ToolMaterial.basic or tool == Tool.scythe:
return True_()
@ -52,7 +60,14 @@ class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixi
if self.options.tool_progression & ToolProgression.option_progressive:
return self.logic.received(f"Progressive {tool}", tool_materials[material])
return self.logic.has(f"{material} Bar") & self.logic.money.can_spend_at(Region.blacksmith, tool_upgrade_prices[material])
can_upgrade_rule = self.logic.has(f"{material} Bar") & self.logic.money.can_spend_at(Region.blacksmith, tool_upgrade_prices[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
return can_upgrade_rule
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)

View File

@ -10,13 +10,13 @@ from ...logic.skill_logic import SkillLogicMixin
from ...logic.tool_logic import ToolLogicMixin
from ...mods.mod_data import ModNames
from ...options import ElevatorProgression
from ...stardew_rule import StardewRule, True_, And, true_
from ...strings.ap_names.mods.mod_items import DeepWoodsItem, SkillLevel
from ...stardew_rule import StardewRule, True_, true_
from ...strings.ap_names.mods.mod_items import DeepWoodsItem
from ...strings.ap_names.transport_names import ModTransportation
from ...strings.craftable_names import Bomb
from ...strings.food_names import Meal
from ...strings.performance_names import Performance
from ...strings.skill_names import Skill
from ...strings.skill_names import Skill, ModSkill
from ...strings.tool_names import Tool, ToolMaterial
@ -45,11 +45,11 @@ CookingLogicMixin]]):
self.logic.received(ModTransportation.woods_obelisk))
tier = int(depth / 25) + 1
if self.options.skill_progression == options.SkillProgression.option_progressive:
if self.options.skill_progression >= options.SkillProgression.option_progressive:
combat_tier = min(10, max(0, tier + 5))
rules.append(self.logic.skill.has_level(Skill.combat, combat_tier))
return And(*rules)
return self.logic.and_(*rules)
def has_woods_rune_to_depth(self, floor: int) -> StardewRule:
if self.options.elevator_progression == ElevatorProgression.option_vanilla:
@ -66,8 +66,8 @@ CookingLogicMixin]]):
self.logic.received(DeepWoodsItem.pendant_elder),
self.logic.skill.has_total_level(40)]
if ModNames.luck_skill in self.options.mods:
rules.append(self.logic.received(SkillLevel.luck, 7))
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.
return And(*rules)
return self.logic.and_(*rules)

View File

@ -7,7 +7,7 @@ from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.combat_logic import CombatLogicMixin
from ...logic.cooking_logic import CookingLogicMixin
from ...logic.crafting_logic import CraftingLogicMixin
from ...logic.crop_logic import CropLogicMixin
from ...logic.farming_logic import FarmingLogicMixin
from ...logic.fishing_logic import FishingLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.money_logic import MoneyLogicMixin
@ -24,11 +24,10 @@ from ...options import Cropsanity
from ...stardew_rule import StardewRule, True_
from ...strings.artisan_good_names import ModArtisanGood
from ...strings.craftable_names import ModCraftable, ModEdible, ModMachine
from ...strings.crop_names import SVEVegetable, SVEFruit, DistantLandsCrop, Fruit
from ...strings.fish_names import WaterItem
from ...strings.flower_names import Flower
from ...strings.crop_names import SVEVegetable, SVEFruit, DistantLandsCrop
from ...strings.fish_names import ModTrash, SVEFish
from ...strings.food_names import SVEMeal, SVEBeverage
from ...strings.forageable_names import SVEForage, DistantLandsForageable, Forageable
from ...strings.forageable_names import SVEForage, DistantLandsForageable
from ...strings.gift_names import SVEGift
from ...strings.ingredient_names import Ingredient
from ...strings.material_names import Material
@ -53,8 +52,9 @@ class ModItemLogicMixin(BaseLogicMixin):
self.item = ModItemLogic(*args, **kwargs)
class ModItemLogic(BaseLogic[Union[CombatLogicMixin, ReceivedLogicMixin, CropLogicMixin, CookingLogicMixin, FishingLogicMixin, HasLogicMixin, MoneyLogicMixin,
RegionLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, ToolLogicMixin, CraftingLogicMixin, SkillLogicMixin, TimeLogicMixin, QuestLogicMixin]]):
class ModItemLogic(BaseLogic[Union[CombatLogicMixin, ReceivedLogicMixin, CookingLogicMixin, FishingLogicMixin, HasLogicMixin, MoneyLogicMixin,
RegionLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, ToolLogicMixin, CraftingLogicMixin, SkillLogicMixin, TimeLogicMixin, QuestLogicMixin,
FarmingLogicMixin]]):
def get_modded_item_rules(self) -> Dict[str, StardewRule]:
items = dict()
@ -78,53 +78,53 @@ RegionLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, To
def get_sve_item_rules(self):
return {SVEGift.aged_blue_moon_wine: self.logic.money.can_spend_at(SVERegion.sophias_house, 28000),
SVEGift.blue_moon_wine: self.logic.money.can_spend_at(SVERegion.sophias_house, 3000),
SVESeed.fungus_seed: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon,
SVESeed.fungus: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon,
ModLoot.green_mushroom: self.logic.region.can_reach(SVERegion.highlands_outside) &
self.logic.tool.has_tool(Tool.axe, ToolMaterial.iron) & self.logic.season.has_any_not_winter(),
SVEFruit.monster_fruit: self.logic.season.has(Season.summer) & self.logic.has(SVESeed.stalk_seed),
SVEVegetable.monster_mushroom: self.logic.season.has(Season.fall) & self.logic.has(SVESeed.fungus_seed),
SVEForage.ornate_treasure_chest: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_galaxy_weapon &
SVEFruit.monster_fruit: self.logic.season.has(Season.summer) & self.logic.has(SVESeed.stalk),
SVEVegetable.monster_mushroom: self.logic.season.has(Season.fall) & self.logic.has(SVESeed.fungus),
ModLoot.ornate_treasure_chest: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_galaxy_weapon &
self.logic.tool.has_tool(Tool.axe, ToolMaterial.iron),
SVEFruit.slime_berry: self.logic.season.has(Season.spring) & self.logic.has(SVESeed.slime_seed),
SVESeed.slime_seed: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon,
SVESeed.stalk_seed: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon,
SVEForage.swirl_stone: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon,
SVEVegetable.void_root: self.logic.season.has(Season.winter) & self.logic.has(SVESeed.void_seed),
SVESeed.void_seed: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon,
SVEForage.void_soul: self.logic.region.can_reach(
SVEFruit.slime_berry: self.logic.season.has(Season.spring) & self.logic.has(SVESeed.slime),
SVESeed.slime: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon,
SVESeed.stalk: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon,
ModLoot.swirl_stone: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon,
SVEVegetable.void_root: self.logic.season.has(Season.winter) & self.logic.has(SVESeed.void),
SVESeed.void: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon,
ModLoot.void_soul: self.logic.region.can_reach(
SVERegion.crimson_badlands) & self.logic.combat.has_good_weapon & self.logic.cooking.can_cook(),
SVEForage.winter_star_rose: self.logic.region.can_reach(SVERegion.summit) & self.logic.season.has(Season.winter),
SVEForage.bearberrys: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.winter),
SVEForage.bearberry: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.winter),
SVEForage.poison_mushroom: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has_any([Season.summer, Season.fall]),
SVEForage.red_baneberry: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.summer),
SVEForage.ferngill_primrose: self.logic.region.can_reach(SVERegion.summit) & self.logic.season.has(Season.spring),
SVEForage.goldenrod: self.logic.region.can_reach(SVERegion.summit) & (
self.logic.season.has(Season.summer) | self.logic.season.has(Season.fall)),
SVESeed.shrub_seed: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic),
SVEFruit.salal_berry: self.logic.crop.can_plant_and_grow_item([Season.spring, Season.summer]) & self.logic.has(SVESeed.shrub_seed),
SVESeed.shrub: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic),
SVEFruit.salal_berry: self.logic.farming.can_plant_and_grow_item((Season.spring, Season.summer)) & self.logic.has(SVESeed.shrub),
ModEdible.aegis_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 28000),
ModEdible.lightning_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 12000),
ModEdible.barbarian_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 22000),
ModEdible.gravity_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 4000),
SVESeed.ancient_ferns_seed: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic),
SVEVegetable.ancient_fiber: self.logic.crop.can_plant_and_grow_item(Season.summer) & self.logic.has(SVESeed.ancient_ferns_seed),
SVEForage.big_conch: self.logic.region.can_reach_any((Region.beach, SVERegion.fable_reef)),
SVESeed.ancient_fern: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic),
SVEVegetable.ancient_fiber: self.logic.farming.can_plant_and_grow_item(Season.summer) & self.logic.has(SVESeed.ancient_fern),
SVEForage.conch: self.logic.region.can_reach_any((Region.beach, SVERegion.fable_reef)),
SVEForage.dewdrop_berry: self.logic.region.can_reach(SVERegion.enchanted_grove),
SVEForage.dried_sand_dollar: self.logic.region.can_reach(SVERegion.fable_reef) | (self.logic.region.can_reach(Region.beach) &
SVEForage.sand_dollar: self.logic.region.can_reach(SVERegion.fable_reef) | (self.logic.region.can_reach(Region.beach) &
self.logic.season.has_any([Season.summer, Season.fall])),
SVEForage.golden_ocean_flower: self.logic.region.can_reach(SVERegion.fable_reef),
SVEMeal.grampleton_orange_chicken: self.logic.money.can_spend_at(Region.saloon, 650) & self.logic.relationship.has_hearts(ModNPC.sophia, 6),
ModEdible.hero_elixir: self.logic.money.can_spend_at(SVERegion.isaac_shop, 8000),
SVEForage.lucky_four_leaf_clover: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.forest_west)) &
SVEForage.four_leaf_clover: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.forest_west)) &
self.logic.season.has_any([Season.spring, Season.summer]),
SVEForage.mushroom_colony: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.junimo_woods, SVERegion.forest_west)) &
self.logic.season.has(Season.fall),
SVEForage.rusty_blade: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon,
SVEForage.smelly_rafflesia: self.logic.region.can_reach(Region.secret_woods),
SVEForage.rafflesia: self.logic.region.can_reach(Region.secret_woods),
SVEBeverage.sports_drink: self.logic.money.can_spend_at(Region.hospital, 750),
"Stamina Capsule": self.logic.money.can_spend_at(Region.hospital, 4000),
SVEForage.thistle: self.logic.region.can_reach(SVERegion.summit),
SVEForage.void_pebble: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon,
ModLoot.void_pebble: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon,
ModLoot.void_shard: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_galaxy_weapon &
self.logic.skill.has_level(Skill.combat, 10) & self.logic.region.can_reach(Region.saloon) & self.logic.time.has_year_three
}
@ -135,49 +135,17 @@ RegionLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, To
Loot.void_essence: items[Loot.void_essence] | self.logic.region.can_reach(SVERegion.highlands_cavern) | self.logic.region.can_reach(
SVERegion.crimson_badlands),
Loot.solar_essence: items[Loot.solar_essence] | self.logic.region.can_reach(SVERegion.crimson_badlands),
Flower.tulip: items[Flower.tulip] | self.logic.tool.can_forage(Season.spring, SVERegion.sprite_spring),
Flower.blue_jazz: items[Flower.blue_jazz] | self.logic.tool.can_forage(Season.spring, SVERegion.sprite_spring),
Flower.summer_spangle: items[Flower.summer_spangle] | self.logic.tool.can_forage(Season.summer, SVERegion.sprite_spring),
Flower.sunflower: items[Flower.sunflower] | self.logic.tool.can_forage((Season.summer, Season.fall), SVERegion.sprite_spring),
Flower.fairy_rose: items[Flower.fairy_rose] | self.logic.tool.can_forage(Season.fall, SVERegion.sprite_spring),
Fruit.ancient_fruit: items[Fruit.ancient_fruit] | (
self.logic.tool.can_forage((Season.spring, Season.summer, Season.fall), SVERegion.sprite_spring) &
self.logic.time.has_year_three) | self.logic.region.can_reach(SVERegion.sprite_spring_cave),
Fruit.sweet_gem_berry: items[Fruit.sweet_gem_berry] | (
self.logic.tool.can_forage((Season.spring, Season.summer, Season.fall), SVERegion.sprite_spring) &
self.logic.time.has_year_three),
WaterItem.coral: items[WaterItem.coral] | self.logic.region.can_reach(SVERegion.fable_reef),
Forageable.rainbow_shell: items[Forageable.rainbow_shell] | self.logic.region.can_reach(SVERegion.fable_reef),
WaterItem.sea_urchin: items[WaterItem.sea_urchin] | self.logic.region.can_reach(SVERegion.fable_reef),
Forageable.red_mushroom: items[Forageable.red_mushroom] | self.logic.tool.can_forage((Season.summer, Season.fall), SVERegion.forest_west) |
self.logic.region.can_reach(SVERegion.sprite_spring_cave),
Forageable.purple_mushroom: items[Forageable.purple_mushroom] | self.logic.tool.can_forage(Season.fall, SVERegion.forest_west) |
self.logic.region.can_reach(SVERegion.sprite_spring_cave),
Forageable.morel: items[Forageable.morel] | self.logic.tool.can_forage(Season.fall, SVERegion.forest_west),
Forageable.chanterelle: items[Forageable.chanterelle] | self.logic.tool.can_forage(Season.fall, SVERegion.forest_west) |
self.logic.region.can_reach(SVERegion.sprite_spring_cave),
Ore.copper: items[Ore.copper] | (self.logic.tool.can_use_tool_at(Tool.pickaxe, ToolMaterial.basic, SVERegion.highlands_cavern) &
self.logic.combat.can_fight_at_level(Performance.great)),
Ore.iron: items[Ore.iron] | (self.logic.tool.can_use_tool_at(Tool.pickaxe, ToolMaterial.basic, SVERegion.highlands_cavern) &
self.logic.combat.can_fight_at_level(Performance.great)),
Ore.iridium: items[Ore.iridium] | (self.logic.tool.can_use_tool_at(Tool.pickaxe, ToolMaterial.basic, SVERegion.crimson_badlands) &
self.logic.combat.can_fight_at_level(Performance.maximum)),
SVEFish.dulse_seaweed: self.logic.fishing.can_fish_at(Region.beach) & self.logic.season.has_any([Season.spring, Season.summer, Season.winter])
}
def get_modified_item_rules_for_deep_woods(self, items: Dict[str, StardewRule]):
options_to_update = {
Fruit.apple: items[Fruit.apple] | self.logic.region.can_reach(DeepWoodsRegion.floor_10), # Deep enough to have seen such a tree at least once
Fruit.apricot: items[Fruit.apricot] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Fruit.cherry: items[Fruit.cherry] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Fruit.orange: items[Fruit.orange] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Fruit.peach: items[Fruit.peach] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Fruit.pomegranate: items[Fruit.pomegranate] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Fruit.mango: items[Fruit.mango] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Flower.tulip: items[Flower.tulip] | self.logic.tool.can_forage(Season.not_winter, DeepWoodsRegion.floor_10),
Flower.blue_jazz: items[Flower.blue_jazz] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Flower.summer_spangle: items[Flower.summer_spangle] | self.logic.tool.can_forage(Season.not_winter, DeepWoodsRegion.floor_10),
Flower.poppy: items[Flower.poppy] | self.logic.tool.can_forage(Season.not_winter, DeepWoodsRegion.floor_10),
Flower.fairy_rose: items[Flower.fairy_rose] | self.logic.region.can_reach(DeepWoodsRegion.floor_10),
Material.hardwood: items[Material.hardwood] | self.logic.tool.can_use_tool_at(Tool.axe, ToolMaterial.iron, DeepWoodsRegion.floor_10),
Ingredient.sugar: items[Ingredient.sugar] | self.logic.tool.can_use_tool_at(Tool.axe, ToolMaterial.gold, DeepWoodsRegion.floor_50),
# Gingerbread House
@ -207,6 +175,7 @@ RegionLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, To
archaeology_item_rules[location_name] = display_item_rule & preservation_chamber_rule
else:
archaeology_item_rules[location_name] = display_item_rule & hardwood_preservation_chamber_rule
archaeology_item_rules[ModTrash.rusty_scrap] = self.logic.has(ModMachine.grinder) & self.logic.has_any(*all_artifacts)
return archaeology_item_rules
def get_distant_lands_item_rules(self):

Some files were not shown because too many files have changed in this diff Show More