Stardew Valley: 5.x.x - The Allsanity Update (#2764)

Major Content update for Stardew Valley, including the following features

- Major performance improvements all across the Stardew Valley apworld, including a significant reduction in the test time
- Randomized Farm Type
- Bundles rework (Remixed Bundles and Missing Bundle!)
- New Settings:
  * Shipsanity - Shipping individual items
  * Monstersanity - Slaying monsters
  * Cooksanity - Cooking individual recipes
  * Chefsanity - Learning individual recipes
  * Craftsanity - Crafting individual items
- New Goals:
  * Protector of the Valley - Complete every monster slayer goal
  * Full Shipment - Ship every item
  * Craftmaster - Craft every item
  * Gourmet Chef - Cook every recipe
  * Legend - Earn 10 000 000g
  * Mystery of the Stardrops - Find every stardrop (Maguffin Hunt)
  * Allsanity - Complete every check in your slot
- Building Shuffle: Cheaper options
- Tool Shuffle: Cheaper options
- Money rework
- New traps
- New isolated checks and items, including the farm cave, the movie theater, etc
- Mod Support: SVE [Albrekka]
- Mod Support: Distant Lands [Albrekka]
- Mod Support: Hat Mouse Lacey [Albrekka]
- Mod Support: Boarding House [Albrekka]

Co-authored-by: Witchybun <elnendil@gmail.com>
Co-authored-by: Witchybun <96719127+Witchybun@users.noreply.github.com>
Co-authored-by: Jouramie <jouramie@hotmail.com>
Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
This commit is contained in:
agilbert1412 2024-03-15 15:05:14 +03:00 committed by GitHub
parent f7da833572
commit 52e65e208e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
177 changed files with 17815 additions and 6863 deletions

View File

@ -1,21 +1,28 @@
import logging
from typing import Dict, Any, Iterable, Optional, Union, Set, List
from typing import Dict, Any, Iterable, Optional, Union, List, TextIO
from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState, ItemClassification, MultiWorld, Group as ItemLinkGroup
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld
from Options import PerGameCommonOptions
from worlds.AutoWorld import World, WebWorld
from . import rules
from .bundles import get_all_bundles, Bundle
from .bundles.bundle_room import BundleRoom
from .bundles.bundles import get_all_bundles
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
from .logic import StardewLogic, StardewRule, True_, MAX_MONTHS
from .locations import location_table, create_locations, LocationData, locations_by_tag
from .logic.bundle_logic import BundleLogic
from .logic.logic import StardewLogic
from .logic.time_logic import MAX_MONTHS
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, NumberOfLuckBuffs, NumberOfMovementBuffs, \
BackpackProgression, BuildingProgression, ExcludeGingerIsland, TrapItems
BackpackProgression, BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization
from .presets import sv_options_presets
from .regions import create_regions
from .rules import set_rules
from worlds.generic.Rules import set_rule
from .stardew_rule import True_, StardewRule, HasProgressionPercent
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
client_version = 0
@ -59,6 +66,15 @@ class StardewValleyWorld(World):
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.code for name, data in location_table.items()}
item_name_groups = {
group.name.replace("_", " ").title() + (" Group" if group.name.replace("_", " ").title() in item_table else ""):
[item.name for item in items] for group, items in items_by_group.items()
}
location_name_groups = {
group.name.replace("_", " ").title() + (" Group" if group.name.replace("_", " ").title() in locations_by_tag else ""):
[location.name for location in locations] for group, locations in locations_by_tag.items()
}
data_version = 3
required_client_version = (0, 4, 0)
@ -67,24 +83,21 @@ class StardewValleyWorld(World):
logic: StardewLogic
web = StardewWebWorld()
modified_bundles: Dict[str, Bundle]
modified_bundles: List[BundleRoom]
randomized_entrances: Dict[str, str]
all_progression_items: Set[str]
total_progression_items: int
def __init__(self, world: MultiWorld, player: int):
super().__init__(world, player)
self.all_progression_items = set()
# all_progression_items: Dict[str, int] # If you need to debug total_progression_items, uncommenting this will help tremendously
def __init__(self, multiworld: MultiWorld, player: int):
super().__init__(multiworld, player)
self.filler_item_pool_names = []
self.total_progression_items = 0
# self.all_progression_items = dict()
def generate_early(self):
self.force_change_options_if_incompatible()
self.logic = StardewLogic(self.player, self.options)
self.modified_bundles = get_all_bundles(self.multiworld.random,
self.logic,
self.options.bundle_randomization,
self.options.bundle_price)
def force_change_options_if_incompatible(self):
goal_is_walnut_hunter = self.options.goal == Goal.option_greatest_walnut_hunter
goal_is_perfection = self.options.goal == Goal.option_perfection
@ -94,7 +107,8 @@ class StardewValleyWorld(World):
self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false
goal_name = self.options.goal.current_key
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})")
logging.warning(
f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
def create_regions(self):
def create_region(name: str, exits: Iterable[str]) -> Region:
@ -102,15 +116,19 @@ class StardewValleyWorld(World):
region.exits = [Entrance(self.player, exit_name, region) for exit_name in exits]
return region
world_regions, self.randomized_entrances = create_regions(create_region, self.multiworld.random, self.options)
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.modified_bundles = get_all_bundles(self.random,
self.logic,
self.options)
def add_location(name: str, code: Optional[int], region: str):
region = world_regions[region]
location = StardewLocation(self.player, name, code, region)
location.access_rule = lambda _: True
region.locations.append(location)
create_locations(add_location, self.options, self.multiworld.random)
create_locations(add_location, self.modified_bundles, self.options, self.random)
self.multiworld.regions.extend(world_regions.values())
def create_items(self):
@ -128,16 +146,16 @@ class StardewValleyWorld(World):
for location in self.multiworld.get_locations(self.player)
if not location.event])
created_items = create_items(self.create_item, locations_count, items_to_exclude, self.options,
self.multiworld.random)
created_items = create_items(self.create_item, self.delete_item, locations_count, items_to_exclude, self.options,
self.random)
self.multiworld.itempool += created_items
self.setup_early_items()
self.setup_month_events()
setup_early_items(self.multiworld, self.options, self.player, self.random)
self.setup_player_events()
self.setup_victory()
def precollect_starting_season(self) -> Optional[StardewItem]:
def precollect_starting_season(self):
if self.options.season_randomization == SeasonRandomization.option_progressive:
return
@ -145,7 +163,7 @@ class StardewValleyWorld(World):
if self.options.season_randomization == SeasonRandomization.option_disabled:
for season in season_pool:
self.multiworld.push_precollected(self.create_item(season))
self.multiworld.push_precollected(self.create_starting_item(season))
return
if [item for item in self.multiworld.precollected_items[self.player]
@ -155,75 +173,128 @@ class StardewValleyWorld(World):
if self.options.season_randomization == SeasonRandomization.option_randomized_not_winter:
season_pool = [season for season in season_pool if season.name != "Winter"]
starting_season = self.create_item(self.multiworld.random.choice(season_pool))
starting_season = self.create_starting_item(self.random.choice(season_pool))
self.multiworld.push_precollected(starting_season)
def setup_early_items(self):
if (self.options.building_progression ==
BuildingProgression.option_progressive_early_shipping_bin):
self.multiworld.early_items[self.player]["Shipping Bin"] = 1
def setup_player_events(self):
self.setup_construction_events()
self.setup_quest_events()
self.setup_action_events()
if self.options.backpack_progression == BackpackProgression.option_early_progressive:
self.multiworld.early_items[self.player]["Progressive Backpack"] = 1
def setup_construction_events(self):
can_construct_buildings = LocationData(None, RegionName.carpenter, Event.can_construct_buildings)
self.create_event_location(can_construct_buildings, True_(), Event.can_construct_buildings)
def setup_month_events(self):
for i in range(0, MAX_MONTHS):
month_end = LocationData(None, "Stardew Valley", f"Month End {i + 1}")
if i == 0:
self.create_event_location(month_end, True_(), "Month End")
continue
def setup_quest_events(self):
start_dark_talisman_quest = LocationData(None, RegionName.railroad, Event.start_dark_talisman_quest)
self.create_event_location(start_dark_talisman_quest, self.logic.wallet.has_rusty_key(), Event.start_dark_talisman_quest)
self.create_event_location(month_end, self.logic.received("Month End", i).simplify(), "Month End")
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_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)
def setup_victory(self):
if self.options.goal == Goal.option_community_center:
self.create_event_location(location_table[GoalName.community_center],
self.logic.can_complete_community_center().simplify(),
"Victory")
self.logic.bundle.can_complete_community_center,
Event.victory)
elif self.options.goal == Goal.option_grandpa_evaluation:
self.create_event_location(location_table[GoalName.grandpa_evaluation],
self.logic.can_finish_grandpa_evaluation().simplify(),
"Victory")
self.logic.can_finish_grandpa_evaluation(),
Event.victory)
elif self.options.goal == Goal.option_bottom_of_the_mines:
self.create_event_location(location_table[GoalName.bottom_of_the_mines],
self.logic.can_mine_to_floor(120).simplify(),
"Victory")
True_(),
Event.victory)
elif self.options.goal == Goal.option_cryptic_note:
self.create_event_location(location_table[GoalName.cryptic_note],
self.logic.can_complete_quest("Cryptic Note").simplify(),
"Victory")
self.logic.quest.can_complete_quest("Cryptic Note"),
Event.victory)
elif self.options.goal == Goal.option_master_angler:
self.create_event_location(location_table[GoalName.master_angler],
self.logic.can_catch_every_fish().simplify(),
"Victory")
self.logic.fishing.can_catch_every_fish_in_slot(self.get_all_location_names()),
Event.victory)
elif self.options.goal == Goal.option_complete_collection:
self.create_event_location(location_table[GoalName.complete_museum],
self.logic.can_complete_museum().simplify(),
"Victory")
self.logic.museum.can_complete_museum(),
Event.victory)
elif self.options.goal == Goal.option_full_house:
self.create_event_location(location_table[GoalName.full_house],
(self.logic.has_children(2) & self.logic.can_reproduce()).simplify(),
"Victory")
(self.logic.relationship.has_children(2) & self.logic.relationship.can_reproduce()),
Event.victory)
elif self.options.goal == Goal.option_greatest_walnut_hunter:
self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
self.logic.has_walnut(130).simplify(),
"Victory")
self.logic.has_walnut(130),
Event.victory)
elif self.options.goal == Goal.option_protector_of_the_valley:
self.create_event_location(location_table[GoalName.protector_of_the_valley],
self.logic.monster.can_complete_all_monster_slaying_goals(),
Event.victory)
elif self.options.goal == Goal.option_full_shipment:
self.create_event_location(location_table[GoalName.full_shipment],
self.logic.shipping.can_ship_everything_in_slot(self.get_all_location_names()),
Event.victory)
elif self.options.goal == Goal.option_gourmet_chef:
self.create_event_location(location_table[GoalName.gourmet_chef],
self.logic.cooking.can_cook_everything,
Event.victory)
elif self.options.goal == Goal.option_craft_master:
self.create_event_location(location_table[GoalName.craft_master],
self.logic.crafting.can_craft_everything,
Event.victory)
elif self.options.goal == Goal.option_legend:
self.create_event_location(location_table[GoalName.legend],
self.logic.money.can_have_earned_total(10_000_000),
Event.victory)
elif self.options.goal == Goal.option_mystery_of_the_stardrops:
self.create_event_location(location_table[GoalName.mystery_of_the_stardrops],
self.logic.has_all_stardrops(),
Event.victory)
elif self.options.goal == Goal.option_allsanity:
self.create_event_location(location_table[GoalName.allsanity],
HasProgressionPercent(self.player, 100),
Event.victory)
elif self.options.goal == Goal.option_perfection:
self.create_event_location(location_table[GoalName.perfection],
self.logic.has_everything(self.all_progression_items).simplify(),
"Victory")
HasProgressionPercent(self.player, 100),
Event.victory)
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
self.multiworld.completion_condition[self.player] = lambda state: state.has(Event.victory, self.player)
def create_item(self, item: Union[str, ItemData]) -> StardewItem:
def get_all_location_names(self) -> List[str]:
return list(location.name for location in self.multiworld.get_locations(self.player))
def create_item(self, item: Union[str, ItemData], override_classification: ItemClassification = None) -> StardewItem:
if isinstance(item, str):
item = item_table[item]
if override_classification is None:
override_classification = item.classification
if override_classification == ItemClassification.progression and item.name != Event.victory:
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):
item = item_table[item]
if item.classification == ItemClassification.progression:
self.all_progression_items.add(item.name)
return StardewItem(item.name, item.classification, item.code, self.player)
def create_event_location(self, location_data: LocationData, rule: StardewRule, item: Optional[str] = None):
def create_event_location(self, location_data: LocationData, rule: StardewRule = None, item: Optional[str] = None):
if rule is None:
rule = True_()
if item is None:
item = location_data.name
@ -235,37 +306,6 @@ class StardewValleyWorld(World):
def set_rules(self):
set_rules(self)
self.force_first_month_once_all_early_items_are_found()
def force_first_month_once_all_early_items_are_found(self):
"""
The Fill algorithm sweeps all event when calculating the early location. This causes an issue where
location only locked behind event are considered early, which they are not really...
This patches the issue, by adding a dependency to the first month end on all early items, so all the locations
that depends on it will not be considered early. This requires at least one early item to be progression, or
it just won't work...
"""
early_items = []
for player, item_count in self.multiworld.early_items.items():
for item, count in item_count.items():
if self.multiworld.worlds[player].create_item(item).advancement:
early_items.append((player, item, count))
for item, count in self.multiworld.local_early_items[self.player].items():
if self.create_item(item).advancement:
early_items.append((self.player, item, count))
def first_month_require_all_early_items(state: CollectionState) -> bool:
for player, item, count in early_items:
if not state.has(item, player, count):
return False
return True
first_month_end = self.multiworld.get_location("Month End 1", self.player)
set_rule(first_month_end, first_month_require_all_early_items)
def generate_basic(self):
pass
@ -283,13 +323,12 @@ class StardewValleyWorld(World):
def get_filler_item_rules(self):
if self.player in self.multiworld.groups:
link_group: ItemLinkGroup = self.multiworld.groups[self.player]
link_group = self.multiworld.groups[self.player]
include_traps = True
exclude_island = False
for player in link_group["players"]:
player_options = self.multiworld.worlds[player].options
if self.multiworld.game[player] != self.game:
continue
if player_options.trap_items == TrapItems.option_no_traps:
include_traps = False
@ -299,24 +338,57 @@ class StardewValleyWorld(World):
else:
return self.options.trap_items != TrapItems.option_no_traps, self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
"""Write to the spoiler header. If individual it's right at the end of that player's options,
if as stage it's right under the common header before per-player options."""
self.add_entrances_to_spoiler_log()
def write_spoiler(self, spoiler_handle: TextIO) -> None:
"""Write to the spoiler "middle", this is after the per-player options and before locations,
meant for useful or interesting info."""
self.add_bundles_to_spoiler_log(spoiler_handle)
def add_bundles_to_spoiler_log(self, spoiler_handle: TextIO):
if self.options.bundle_randomization == BundleRandomization.option_vanilla:
return
player_name = self.multiworld.get_player_name(self.player)
spoiler_handle.write(f"\n\nCommunity Center ({player_name}):\n")
for room in self.modified_bundles:
for bundle in room.bundles:
spoiler_handle.write(f"\t[{room.name}] {bundle.name} ({bundle.number_required} required):\n")
for i, item in enumerate(bundle.items):
if "Basic" in item.quality:
quality = ""
else:
quality = f" ({item.quality.split(' ')[0]})"
spoiler_handle.write(f"\t\t{item.amount}x {item.item_name}{quality}\n")
def add_entrances_to_spoiler_log(self):
if self.options.entrance_randomization == EntranceRandomization.option_disabled:
return
for original_entrance, replaced_entrance in self.randomized_entrances.items():
self.multiworld.spoiler.set_entrance(original_entrance, replaced_entrance, "entrance", self.player)
def fill_slot_data(self) -> Dict[str, Any]:
bundles = dict()
for room in self.modified_bundles:
bundles[room.name] = dict()
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}"
modified_bundles = {}
for bundle_key in self.modified_bundles:
key, value = self.modified_bundles[bundle_key].to_pair()
modified_bundles[key] = value
excluded_options = [BundleRandomization, BundlePrice, NumberOfMovementBuffs, NumberOfLuckBuffs]
excluded_options = [BundleRandomization, NumberOfMovementBuffs, NumberOfLuckBuffs]
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)
included_option_names: List[str] = [option_name for option_name in self.options_dataclass.type_hints if option_name not in excluded_option_names]
slot_data = self.options.as_dict(*included_option_names)
slot_data.update({
"seed": self.multiworld.per_slot_randoms[self.player].randrange(1000000000), # Seed should be max 9 digits
"seed": self.random.randrange(1000000000), # Seed should be max 9 digits
"randomized_entrances": self.randomized_entrances,
"modified_bundles": modified_bundles,
"client_version": "4.0.0",
"modified_bundles": bundles,
"client_version": "5.0.0",
})
return slot_data

View File

@ -1,254 +0,0 @@
from random import Random
from typing import List, Dict, Union
from .data.bundle_data import *
from .logic import StardewLogic
from .options import BundleRandomization, BundlePrice
vanilla_bundles = {
"Pantry/0": "Spring Crops/O 465 20/24 1 0 188 1 0 190 1 0 192 1 0/0",
"Pantry/1": "Summer Crops/O 621 1/256 1 0 260 1 0 258 1 0 254 1 0/3",
"Pantry/2": "Fall Crops/BO 10 1/270 1 0 272 1 0 276 1 0 280 1 0/2",
"Pantry/3": "Quality Crops/BO 15 1/24 5 2 254 5 2 276 5 2 270 5 2/6/3",
"Pantry/4": "Animal/BO 16 1/186 1 0 182 1 0 174 1 0 438 1 0 440 1 0 442 1 0/4/5",
# 639 1 0 640 1 0 641 1 0 642 1 0 643 1 0
"Pantry/5": "Artisan/BO 12 1/432 1 0 428 1 0 426 1 0 424 1 0 340 1 0 344 1 0 613 1 0 634 1 0 635 1 0 636 1 0 637 1 0 638 1 0/1/6",
"Crafts Room/13": "Spring Foraging/O 495 30/16 1 0 18 1 0 20 1 0 22 1 0/0",
"Crafts Room/14": "Summer Foraging/O 496 30/396 1 0 398 1 0 402 1 0/3",
"Crafts Room/15": "Fall Foraging/O 497 30/404 1 0 406 1 0 408 1 0 410 1 0/2",
"Crafts Room/16": "Winter Foraging/O 498 30/412 1 0 414 1 0 416 1 0 418 1 0/6",
"Crafts Room/17": "Construction/BO 114 1/388 99 0 388 99 0 390 99 0 709 10 0/4",
"Crafts Room/19": "Exotic Foraging/O 235 5/88 1 0 90 1 0 78 1 0 420 1 0 422 1 0 724 1 0 725 1 0 726 1 0 257 1 0/1/5",
"Fish Tank/6": "River Fish/O 685 30/145 1 0 143 1 0 706 1 0 699 1 0/6",
"Fish Tank/7": "Lake Fish/O 687 1/136 1 0 142 1 0 700 1 0 698 1 0/0",
"Fish Tank/8": "Ocean Fish/O 690 5/131 1 0 130 1 0 150 1 0 701 1 0/5",
"Fish Tank/9": "Night Fishing/R 516 1/140 1 0 132 1 0 148 1 0/1",
"Fish Tank/10": "Specialty Fish/O 242 5/128 1 0 156 1 0 164 1 0 734 1 0/4",
"Fish Tank/11": "Crab Pot/O 710 3/715 1 0 716 1 0 717 1 0 718 1 0 719 1 0 720 1 0 721 1 0 722 1 0 723 1 0 372 1 0/1/5",
"Boiler Room/20": "Blacksmith's/BO 13 1/334 1 0 335 1 0 336 1 0/2",
"Boiler Room/21": "Geologist's/O 749 5/80 1 0 86 1 0 84 1 0 82 1 0/1",
"Boiler Room/22": "Adventurer's/R 518 1/766 99 0 767 10 0 768 1 0 769 1 0/1/2",
"Vault/23": "2,500g/O 220 3/-1 2500 2500/4",
"Vault/24": "5,000g/O 369 30/-1 5000 5000/2",
"Vault/25": "10,000g/BO 9 1/-1 10000 10000/3",
"Vault/26": "25,000g/BO 21 1/-1 25000 25000/1",
"Bulletin Board/31": "Chef's/O 221 3/724 1 0 259 1 0 430 1 0 376 1 0 228 1 0 194 1 0/4",
"Bulletin Board/32": "Field Research/BO 20 1/422 1 0 392 1 0 702 1 0 536 1 0/5",
"Bulletin Board/33": "Enchanter's/O 336 5/725 1 0 348 1 0 446 1 0 637 1 0/1",
"Bulletin Board/34": "Dye/BO 25 1/420 1 0 397 1 0 421 1 0 444 1 0 62 1 0 266 1 0/6",
"Bulletin Board/35": "Fodder/BO 104 1/262 10 0 178 10 0 613 3 0/3",
# "Abandoned Joja Mart/36": "The Missing//348 1 1 807 1 0 74 1 0 454 5 2 795 1 2 445 1 0/1/5"
}
class Bundle:
room: str
sprite: str
original_name: str
name: str
rewards: List[str]
requirements: List[BundleItem]
color: str
number_required: int
def __init__(self, key: str, value: str):
key_parts = key.split("/")
self.room = key_parts[0]
self.sprite = key_parts[1]
value_parts = value.split("/")
self.original_name = value_parts[0]
self.name = value_parts[0]
self.rewards = self.parse_stardew_objects(value_parts[1])
self.requirements = self.parse_stardew_bundle_items(value_parts[2])
self.color = value_parts[3]
if len(value_parts) > 4:
self.number_required = int(value_parts[4])
else:
self.number_required = len(self.requirements)
def __repr__(self):
return f"{self.original_name} -> {repr(self.requirements)}"
def get_name_with_bundle(self) -> str:
return f"{self.original_name} Bundle"
def to_pair(self) -> (str, str):
key = f"{self.room}/{self.sprite}"
str_rewards = ""
for reward in self.rewards:
str_rewards += f" {reward}"
str_rewards = str_rewards.strip()
str_requirements = ""
for requirement in self.requirements:
str_requirements += f" {requirement.item.item_id} {requirement.amount} {requirement.quality}"
str_requirements = str_requirements.strip()
value = f"{self.name}/{str_rewards}/{str_requirements}/{self.color}/{self.number_required}"
return key, value
def remove_rewards(self):
self.rewards = []
def change_number_required(self, difference: int):
self.number_required = min(len(self.requirements), max(1, self.number_required + difference))
if len(self.requirements) == 1 and self.requirements[0].item.item_id == -1:
one_fifth = self.requirements[0].amount / 5
new_amount = int(self.requirements[0].amount + (difference * one_fifth))
self.requirements[0] = BundleItem.money_bundle(new_amount)
thousand_amount = int(new_amount / 1000)
dollar_amount = str(new_amount % 1000)
while len(dollar_amount) < 3:
dollar_amount = f"0{dollar_amount}"
self.name = f"{thousand_amount},{dollar_amount}g"
def randomize_requirements(self, random: Random,
potential_requirements: Union[List[BundleItem], List[List[BundleItem]]]):
if not potential_requirements:
return
number_to_generate = len(self.requirements)
self.requirements.clear()
if number_to_generate > len(potential_requirements):
choices: Union[BundleItem, List[BundleItem]] = random.choices(potential_requirements, k=number_to_generate)
else:
choices: Union[BundleItem, List[BundleItem]] = random.sample(potential_requirements, number_to_generate)
for choice in choices:
if isinstance(choice, BundleItem):
self.requirements.append(choice)
else:
self.requirements.append(random.choice(choice))
def assign_requirements(self, new_requirements: List[BundleItem]) -> List[BundleItem]:
number_to_generate = len(self.requirements)
self.requirements.clear()
for requirement in new_requirements:
self.requirements.append(requirement)
if len(self.requirements) >= number_to_generate:
return new_requirements[number_to_generate:]
@staticmethod
def parse_stardew_objects(string_objects: str) -> List[str]:
objects = []
if len(string_objects) < 5:
return objects
rewards_parts = string_objects.split(" ")
for index in range(0, len(rewards_parts), 3):
objects.append(f"{rewards_parts[index]} {rewards_parts[index + 1]} {rewards_parts[index + 2]}")
return objects
@staticmethod
def parse_stardew_bundle_items(string_objects: str) -> List[BundleItem]:
bundle_items = []
parts = string_objects.split(" ")
for index in range(0, len(parts), 3):
item_id = int(parts[index])
bundle_item = BundleItem(all_bundle_items_by_id[item_id].item,
int(parts[index + 1]),
int(parts[index + 2]))
bundle_items.append(bundle_item)
return bundle_items
# Shuffling the Vault doesn't really work with the stardew system in place
# shuffle_vault_amongst_themselves(random, bundles)
def get_all_bundles(random: Random, logic: StardewLogic, randomization: BundleRandomization, price: BundlePrice) -> Dict[str, Bundle]:
bundles = {}
for bundle_key in vanilla_bundles:
bundle_value = vanilla_bundles[bundle_key]
bundle = Bundle(bundle_key, bundle_value)
bundles[bundle.get_name_with_bundle()] = bundle
if randomization == BundleRandomization.option_thematic:
shuffle_bundles_thematically(random, bundles)
elif randomization == BundleRandomization.option_shuffled:
shuffle_bundles_completely(random, logic, bundles)
price_difference = 0
if price == BundlePrice.option_very_cheap:
price_difference = -2
elif price == BundlePrice.option_cheap:
price_difference = -1
elif price == BundlePrice.option_expensive:
price_difference = 1
for bundle_key in bundles:
bundles[bundle_key].remove_rewards()
bundles[bundle_key].change_number_required(price_difference)
return bundles
def shuffle_bundles_completely(random: Random, logic: StardewLogic, bundles: Dict[str, Bundle]):
total_required_item_number = sum(len(bundle.requirements) for bundle in bundles.values())
quality_crops_items_set = set(quality_crops_items)
all_bundle_items_without_quality_and_money = [item
for item in all_bundle_items_except_money
if item not in quality_crops_items_set] + \
random.sample(quality_crops_items, 10)
choices = random.sample(all_bundle_items_without_quality_and_money, total_required_item_number - 4)
items_sorted = sorted(choices, key=lambda x: logic.item_rules[x.item.name].get_difficulty())
keys = sorted(bundles.keys())
random.shuffle(keys)
for key in keys:
if not bundles[key].original_name.endswith("00g"):
items_sorted = bundles[key].assign_requirements(items_sorted)
def shuffle_bundles_thematically(random: Random, bundles: Dict[str, Bundle]):
shuffle_crafts_room_bundle_thematically(random, bundles)
shuffle_pantry_bundle_thematically(random, bundles)
shuffle_fish_tank_thematically(random, bundles)
shuffle_boiler_room_thematically(random, bundles)
shuffle_bulletin_board_thematically(random, bundles)
def shuffle_crafts_room_bundle_thematically(random: Random, bundles: Dict[str, Bundle]):
bundles["Spring Foraging Bundle"].randomize_requirements(random, spring_foraging_items)
bundles["Summer Foraging Bundle"].randomize_requirements(random, summer_foraging_items)
bundles["Fall Foraging Bundle"].randomize_requirements(random, fall_foraging_items)
bundles["Winter Foraging Bundle"].randomize_requirements(random, winter_foraging_items)
bundles["Exotic Foraging Bundle"].randomize_requirements(random, exotic_foraging_items)
bundles["Construction Bundle"].randomize_requirements(random, construction_items)
def shuffle_pantry_bundle_thematically(random: Random, bundles: Dict[str, Bundle]):
bundles["Spring Crops Bundle"].randomize_requirements(random, spring_crop_items)
bundles["Summer Crops Bundle"].randomize_requirements(random, summer_crops_items)
bundles["Fall Crops Bundle"].randomize_requirements(random, fall_crops_items)
bundles["Quality Crops Bundle"].randomize_requirements(random, quality_crops_items)
bundles["Animal Bundle"].randomize_requirements(random, animal_product_items)
bundles["Artisan Bundle"].randomize_requirements(random, artisan_goods_items)
def shuffle_fish_tank_thematically(random: Random, bundles: Dict[str, Bundle]):
bundles["River Fish Bundle"].randomize_requirements(random, river_fish_items)
bundles["Lake Fish Bundle"].randomize_requirements(random, lake_fish_items)
bundles["Ocean Fish Bundle"].randomize_requirements(random, ocean_fish_items)
bundles["Night Fishing Bundle"].randomize_requirements(random, night_fish_items)
bundles["Crab Pot Bundle"].randomize_requirements(random, crab_pot_items)
bundles["Specialty Fish Bundle"].randomize_requirements(random, specialty_fish_items)
def shuffle_boiler_room_thematically(random: Random, bundles: Dict[str, Bundle]):
bundles["Blacksmith's Bundle"].randomize_requirements(random, blacksmith_items)
bundles["Geologist's Bundle"].randomize_requirements(random, geologist_items)
bundles["Adventurer's Bundle"].randomize_requirements(random, adventurer_items)
def shuffle_bulletin_board_thematically(random: Random, bundles: Dict[str, Bundle]):
bundles["Chef's Bundle"].randomize_requirements(random, chef_items)
bundles["Dye Bundle"].randomize_requirements(random, dye_items)
bundles["Field Research Bundle"].randomize_requirements(random, field_research_items)
bundles["Fodder Bundle"].randomize_requirements(random, fodder_items)
bundles["Enchanter's Bundle"].randomize_requirements(random, enchanter_items)
def shuffle_vault_amongst_themselves(random: Random, bundles: Dict[str, Bundle]):
bundles["2,500g Bundle"].randomize_requirements(random, vault_bundle_items)
bundles["5,000g Bundle"].randomize_requirements(random, vault_bundle_items)
bundles["10,000g Bundle"].randomize_requirements(random, vault_bundle_items)
bundles["25,000g Bundle"].randomize_requirements(random, vault_bundle_items)

View File

@ -0,0 +1,163 @@
from dataclasses import dataclass
from random import Random
from typing import List
from .bundle_item import BundleItem
from ..options import BundlePrice, StardewValleyOptions, ExcludeGingerIsland, FestivalLocations
from ..strings.currency_names import Currency
@dataclass
class Bundle:
room: str
name: str
items: List[BundleItem]
number_required: int
def __repr__(self):
return f"{self.name} -> {self.number_required} from {repr(self.items)}"
@dataclass
class BundleTemplate:
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
self.number_possible_items = number_possible_items
self.number_required_items = number_required_items
@staticmethod
def extend_from(template, items: List[BundleItem]):
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)]
number_items = len(filtered_items)
number_chosen_items = self.number_possible_items
if number_chosen_items < number_required:
number_chosen_items = number_required
if number_chosen_items > number_items:
chosen_items = filtered_items + random.choices(filtered_items, k=number_chosen_items - number_items)
else:
chosen_items = random.sample(filtered_items, number_chosen_items)
return Bundle(self.room, self.name, chosen_items, number_required)
def can_appear(self, options: StardewValleyOptions) -> bool:
return True
class CurrencyBundleTemplate(BundleTemplate):
item: BundleItem
def __init__(self, room: str, name: str, item: BundleItem):
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)
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)
return currency_amount
def can_appear(self, options: StardewValleyOptions) -> bool:
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
if self.item.item_name == Currency.qi_gem or self.item.item_name == Currency.golden_walnut or self.item.item_name == Currency.cinder_shard:
return False
if options.festival_locations == FestivalLocations.option_disabled:
if self.item.item_name == Currency.star_token:
return False
return True
class MoneyBundleTemplate(CurrencyBundleTemplate):
def __init__(self, room: str, item: BundleItem):
super().__init__(room, "", item)
def create_bundle(self, bundle_price_option: BundlePrice, random: Random, options: StardewValleyOptions) -> Bundle:
currency_amount = self.get_currency_amount(bundle_price_option)
currency_name = "g"
if currency_amount >= 1000:
unit_amount = currency_amount % 1000
unit_amount = "000" if unit_amount == 0 else unit_amount
currency_display = f"{currency_amount // 1000},{unit_amount}"
else:
currency_display = f"{currency_amount}"
name = f"{currency_display}{currency_name} Bundle"
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)
return currency_amount
class IslandBundleTemplate(BundleTemplate):
def can_appear(self, options: StardewValleyOptions) -> bool:
return options.exclude_ginger_island == ExcludeGingerIsland.option_false
class FestivalBundleTemplate(BundleTemplate):
def can_appear(self, options: StardewValleyOptions) -> bool:
return options.festival_locations != FestivalLocations.option_disabled
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):
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
number_categories = len(self.categories)
number_chosen_categories = self.number_possible_items
if number_chosen_categories < number_required:
number_chosen_categories = number_required
if number_chosen_categories > number_categories:
chosen_categories = self.categories + random.choices(self.categories, k=number_chosen_categories - number_categories)
else:
chosen_categories = random.sample(self.categories, number_chosen_categories)
chosen_items = []
for category in chosen_categories:
filtered_items = [item for item in category if item.can_appear(options)]
chosen_items.append(random.choice(filtered_items))
return Bundle(self.room, self.name, chosen_items, number_required)

View File

@ -0,0 +1,73 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from ..options import StardewValleyOptions, ExcludeGingerIsland, FestivalLocations
from ..strings.crop_names import Fruit
from ..strings.currency_names import Currency
from ..strings.quality_names import CropQuality, FishQuality, ForageQuality
class BundleItemSource(ABC):
@abstractmethod
def can_appear(self, options: StardewValleyOptions) -> bool:
...
class VanillaItemSource(BundleItemSource):
def can_appear(self, options: StardewValleyOptions) -> bool:
return True
class IslandItemSource(BundleItemSource):
def can_appear(self, options: StardewValleyOptions) -> bool:
return options.exclude_ginger_island == ExcludeGingerIsland.option_false
class FestivalItemSource(BundleItemSource):
def can_appear(self, options: StardewValleyOptions) -> bool:
return options.festival_locations != FestivalLocations.option_disabled
@dataclass(frozen=True, order=True)
class BundleItem:
class Sources:
vanilla = VanillaItemSource()
island = IslandItemSource()
festival = FestivalItemSource()
item_name: str
amount: int = 1
quality: str = CropQuality.basic
source: BundleItemSource = Sources.vanilla
@staticmethod
def money_bundle(amount: int) -> BundleItem:
return BundleItem(Currency.money, amount)
def as_amount(self, amount: int) -> BundleItem:
return BundleItem(self.item_name, amount, self.quality, self.source)
def as_quality(self, quality: str) -> BundleItem:
return BundleItem(self.item_name, self.amount, quality, self.source)
def as_quality_crop(self) -> BundleItem:
amount = 5
difficult_crops = [Fruit.sweet_gem_berry, Fruit.ancient_fruit]
if self.item_name in difficult_crops:
amount = 1
return self.as_quality(CropQuality.gold).as_amount(amount)
def as_quality_fish(self) -> BundleItem:
return self.as_quality(FishQuality.gold)
def as_quality_forage(self) -> BundleItem:
return self.as_quality(ForageQuality.gold)
def __repr__(self):
quality = "" if self.quality == CropQuality.basic else self.quality
return f"{self.amount} {quality} {self.item_name}"
def can_appear(self, options: StardewValleyOptions) -> bool:
return self.source.can_appear(options)

View File

@ -0,0 +1,24 @@
from dataclasses import dataclass
from random import Random
from typing import List
from .bundle import Bundle, BundleTemplate
from ..options import BundlePrice, StardewValleyOptions
@dataclass
class BundleRoom:
name: str
bundles: List[Bundle]
@dataclass
class BundleRoomTemplate:
name: str
bundles: List[BundleTemplate]
number_bundles: int
def create_bundle_room(self, bundle_price_option: BundlePrice, random: Random, 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])

View File

@ -0,0 +1,80 @@
from random import Random
from typing import List
from .bundle_room import BundleRoom
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
from ..logic.logic import StardewLogic
from ..options import BundleRandomization, StardewValleyOptions, ExcludeGingerIsland
def get_all_bundles(random: Random, logic: StardewLogic, options: StardewValleyOptions) -> List[BundleRoom]:
if options.bundle_randomization == BundleRandomization.option_vanilla:
return get_vanilla_bundles(random, options)
elif options.bundle_randomization == BundleRandomization.option_thematic:
return get_thematic_bundles(random, options)
elif options.bundle_randomization == BundleRandomization.option_remixed:
return get_remixed_bundles(random, options)
elif options.bundle_randomization == BundleRandomization.option_shuffled:
return get_shuffled_bundles(random, logic, 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_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_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_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)]
rooms = [room for room in get_remixed_bundles(random, options) if room.name != "Vault"]
required_items = 0
for room in rooms:
for bundle in room.bundles:
required_items += len(bundle.items)
random.shuffle(room.bundles)
random.shuffle(rooms)
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:]
vault = vault_remixed.create_bundle_room(options.bundle_price, random, options)
return [*rooms, vault]

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
fishing_chest = "Fishing Chest"
secret_note = "Secret Note"
quality_dict = {
0: "",
1: "Silver",
2: "Gold",
3: "Iridium"
}

View File

@ -0,0 +1,313 @@
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
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.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.flower_names import Flower
from ..strings.food_names import Meal
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable
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.quest_names import Quest
from ..strings.region_names import Region, SVERegion
from ..strings.seed_names import Seed, TreeSeed
from ..strings.skill_names import Skill, ModSkill
from ..strings.special_order_names import SpecialOrder
from ..strings.villager_names import NPC, ModNPC
class CraftingRecipe:
item: str
ingredients: Dict[str, int]
source: RecipeSource
mod_name: Optional[str]
def __init__(self, item: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None):
self.item = item
self.ingredients = ingredients
self.source = source
self.mod_name = mod_name
def __repr__(self):
return f"{self.item} (Source: {self.source} |" \
f" Ingredients: {self.ingredients})"
all_crafting_recipes: List[CraftingRecipe] = []
def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe:
source = FriendshipSource(friend, hearts)
return create_recipe(name, ingredients, source, mod_name)
def cutscene_recipe(name: str, region: str, friend: str, hearts: int, ingredients: Dict[str, int]) -> CraftingRecipe:
source = CutsceneSource(region, friend, hearts)
return create_recipe(name, ingredients, source)
def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe:
source = SkillSource(skill, level)
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)
def festival_shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int]) -> CraftingRecipe:
source = FestivalShopSource(region, price)
return create_recipe(name, ingredients, source)
def shop_trade_recipe(name: str, region: str, currency: str, price: int, ingredients: Dict[str, int]) -> CraftingRecipe:
source = ShopTradeSource(region, currency, price)
return create_recipe(name, ingredients, source)
def queen_of_sauce_recipe(name: str, year: int, season: str, day: int, ingredients: Dict[str, int]) -> CraftingRecipe:
source = QueenOfSauceSource(year, season, day)
return create_recipe(name, ingredients, source)
def quest_recipe(name: str, quest: str, ingredients: Dict[str, int]) -> CraftingRecipe:
source = QuestSource(quest)
return create_recipe(name, ingredients, source)
def special_order_recipe(name: str, special_order: str, ingredients: Dict[str, int]) -> CraftingRecipe:
source = SpecialOrderSource(special_order)
return create_recipe(name, ingredients, source)
def starter_recipe(name: str, ingredients: Dict[str, int]) -> CraftingRecipe:
source = StarterSource()
return create_recipe(name, ingredients, source)
def ap_recipe(name: str, ingredients: Dict[str, int], ap_item: str = None) -> CraftingRecipe:
if ap_item is None:
ap_item = f"{name} Recipe"
source = ArchipelagoSource(ap_item)
return create_recipe(name, ingredients, source)
def cellar_recipe(name: str, ingredients: Dict[str, int]) -> CraftingRecipe:
source = LogicSource("Cellar")
return create_recipe(name, ingredients, source)
def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None) -> CraftingRecipe:
recipe = CraftingRecipe(name, ingredients, source, mod_name)
all_crafting_recipes.append(recipe)
return recipe
cherry_bomb = skill_recipe(Bomb.cherry_bomb, Skill.mining, 1, {Ore.copper: 4, Material.coal: 1})
bomb = skill_recipe(Bomb.bomb, Skill.mining, 6, {Ore.iron: 4, Material.coal: 1})
mega_bomb = skill_recipe(Bomb.mega_bomb, Skill.mining, 8, {Ore.gold: 4, Loot.solar_essence: 1, Loot.void_essence: 1})
gate = starter_recipe(Fence.gate, {Material.wood: 10})
wood_fence = starter_recipe(Fence.wood, {Material.wood: 2})
stone_fence = skill_recipe(Fence.stone, Skill.farming, 2, {Material.stone: 2})
iron_fence = skill_recipe(Fence.iron, Skill.farming, 4, {MetalBar.iron: 2})
hardwood_fence = skill_recipe(Fence.hardwood, Skill.farming, 6, {Material.hardwood: 2})
sprinkler = skill_recipe(Sprinkler.basic, Skill.farming, 2, {MetalBar.copper: 1, MetalBar.iron: 1})
quality_sprinkler = skill_recipe(Sprinkler.quality, Skill.farming, 6, {MetalBar.iron: 1, MetalBar.gold: 1, MetalBar.quartz: 1})
iridium_sprinkler = skill_recipe(Sprinkler.iridium, Skill.farming, 9, {MetalBar.gold: 1, MetalBar.iridium: 1, ArtisanGood.battery_pack: 1})
bee_house = skill_recipe(Machine.bee_house, Skill.farming, 3, {Material.wood: 40, Material.coal: 8, MetalBar.iron: 1, ArtisanGood.maple_syrup: 1})
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})
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})
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})
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})
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})
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})
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})
ancient_seeds = ap_recipe(WildSeeds.ancient, {Artifact.ancient_seed: 1})
grass_starter = shop_recipe(WildSeeds.grass_starter, Region.pierre_store, 1000, {Material.fiber: 10})
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})
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})
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})
brick_floor = shop_recipe(Floor.brick, Region.carpenter, 500, {Material.clay: 2, Material.stone: 5})
wood_path = starter_recipe(Floor.wood_path, {Material.wood: 1})
gravel_path = starter_recipe(Floor.gravel_path, {Material.stone: 1})
cobblestone_path = starter_recipe(Floor.cobblestone_path, {Material.stone: 1})
stepping_stone_path = shop_recipe(Floor.stepping_stone_path, Region.carpenter, 100, {Material.stone: 1})
crystal_path = shop_recipe(Floor.crystal_path, Region.carpenter, 200, {MetalBar.quartz: 1})
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})
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})
dressed_spinner = skill_recipe(Fishing.dressed_spinner, Skill.fishing, 8, {MetalBar.iron: 2, ArtisanGood.cloth: 1})
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})
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})
sturdy_ring = skill_recipe(Ring.sturdy_ring, Skill.combat, 1, {MetalBar.copper: 2, Loot.bug_meat: 25, Loot.slime: 25})
warrior_ring = skill_recipe(Ring.warrior_ring, Skill.combat, 4, {MetalBar.iron: 10, Material.coal: 25, Mineral.frozen_tear: 10})
ring_of_yoba = skill_recipe(Ring.ring_of_yoba, Skill.combat, 7, {MetalBar.gold: 5, MetalBar.iron: 5, Mineral.diamond: 1})
thorns_ring = skill_recipe(Ring.thorns_ring, Skill.combat, 7, {Fossil.bone_fragment: 50, Material.stone: 50, MetalBar.gold: 1})
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})
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})
oil_of_garlic = skill_recipe(Edible.oil_of_garlic, Skill.combat, 6, {Vegetable.garlic: 10, Ingredient.oil: 1})
monster_musk = special_order_recipe(Consumable.monster_musk, SpecialOrder.prismatic_jelly, {Loot.bat_wing: 30, Loot.slime: 30})
fairy_dust = quest_recipe(Consumable.fairy_dust, Quest.the_pirates_wife, {Mineral.diamond: 1, Flower.fairy_rose: 1})
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})
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})
campfire = starter_recipe(Lighting.campfire, {Material.stone: 10, Material.wood: 10, Material.fiber: 10})
wooden_brazier = shop_recipe(Lighting.wooden_brazier, Region.carpenter, 250, {Material.wood: 10, Material.coal: 1, Material.fiber: 5})
stone_brazier = shop_recipe(Lighting.stone_brazier, Region.carpenter, 400, {Material.stone: 10, Material.coal: 1, Material.fiber: 5})
gold_brazier = shop_recipe(Lighting.gold_brazier, Region.carpenter, 1000, {MetalBar.gold: 1, Material.coal: 1, Material.fiber: 5})
carved_brazier = shop_recipe(Lighting.carved_brazier, Region.carpenter, 2000, {Material.hardwood: 10, Material.coal: 1})
stump_brazier = shop_recipe(Lighting.stump_brazier, Region.carpenter, 800, {Material.hardwood: 5, Material.coal: 1})
barrel_brazier = shop_recipe(Lighting.barrel_brazier, Region.carpenter, 800, {Material.wood: 50, Loot.solar_essence: 1, Material.coal: 1})
skull_brazier = shop_recipe(Lighting.skull_brazier, Region.carpenter, 3000, {Fossil.bone_fragment: 10})
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})
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})
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})
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})
recycling_machine = skill_recipe(Machine.recycling_machine, Skill.fishing, 4, {Material.wood: 25, Material.stone: 25, MetalBar.iron: 1})
seed_maker = skill_recipe(Machine.seed_maker, Skill.farming, 9, {Material.wood: 25, Material.coal: 10, MetalBar.gold: 1})
slime_egg_press = skill_recipe(Machine.slime_egg_press, Skill.combat, 6, {Material.coal: 25, Mineral.fire_quartz: 1, ArtisanGood.battery_pack: 1})
slime_incubator = skill_recipe(Machine.slime_incubator, Skill.combat, 8, {MetalBar.iridium: 2, Loot.slime: 100})
solar_panel = special_order_recipe(Machine.solar_panel, SpecialOrder.island_ingredients, {MetalBar.quartz: 10, MetalBar.iron: 5, MetalBar.gold: 5})
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})
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})
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})
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})
deluxe_scarecrow = ap_recipe(Craftable.deluxe_scarecrow, {Material.wood: 50, Material.fiber: 40, Ore.iridium: 1})
staircase = skill_recipe(Craftable.staircase, Skill.mining, 2, {Material.stone: 99})
explosive_ammo = skill_recipe(Craftable.explosive_ammo, Skill.combat, 8, {MetalBar.iron: 1, Material.coal: 2})
transmute_fe = skill_recipe(Craftable.transmute_fe, Skill.mining, 4, {MetalBar.copper: 3})
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})
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})
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},
ModNames.archaeology)
preservation_chamber_h = skill_recipe(ModMachine.hardwood_preservation_chamber, ModSkill.archaeology, 7, {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},
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)
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)
hardwood_display = skill_recipe(ModCraftable.hardwood_display, ModSkill.archaeology, 7, {Material.hardwood: 10}, 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,
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,
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,
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)
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,
MetalBar.iron: 5, Material.hardwood: 10}, ModNames.boarding_house)
pterodactyl_skeleton_l = shop_recipe(ModCraftable.pterodactyl_skeleton_l, Region.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,
{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,
{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,
{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,
{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,
{ModFossil.dinosaur_vertebra: 1, ModFossil.dinosaur_femur: 1, ModFossil.dinosaur_pelvis: 1,
MetalBar.iron: 10, Material.hardwood: 15}, ModNames.boarding_house)
all_crafting_recipes_by_name = {recipe.item: recipe for recipe in all_crafting_recipes}

View File

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

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List
from typing import Tuple
from .. import data
@ -7,14 +7,15 @@ from .. import data
@dataclass(frozen=True)
class SeedItem:
name: str
seasons: List[str]
regions: List[str]
seasons: Tuple[str]
regions: Tuple[str]
requires_island: bool
@dataclass(frozen=True)
class CropItem:
name: str
farm_growth_seasons: List[str]
farm_growth_seasons: Tuple[str]
seed: SeedItem
@ -32,13 +33,14 @@ def load_crop_csv():
for item in reader:
seeds.append(SeedItem(item["seed"],
[season for season in item["seed_seasons"].split(",")]
if item["seed_seasons"] else [],
[region for region in item["seed_regions"].split(",")]
if item["seed_regions"] else []))
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"],
[season for season in item["farm_growth_seasons"].split(",")]
if item["farm_growth_seasons"] else [],
tuple(season for season in item["farm_growth_seasons"].split(","))
if item["farm_growth_seasons"] else tuple(),
seeds[-1]))
return crops, seeds

View File

@ -1,20 +1,24 @@
from dataclasses import dataclass
from typing import List, Tuple, Union, Optional
from typing import List, Tuple, Union, Optional, Set
from . import season_data as season
from .game_item import GameItem
from ..strings.region_names import Region
from ..strings.fish_names import Fish, SVEFish, DistantLandsFish
from ..strings.region_names import Region, SVERegion
from ..mods.mod_data import ModNames
@dataclass(frozen=True)
class FishItem(GameItem):
class FishItem:
name: str
locations: Tuple[str]
seasons: Tuple[str]
difficulty: int
mod_name: Optional[str]
legendary: bool
extended_family: bool
mod_name: Optional[str] = None
def __repr__(self):
return f"{self.name} [{self.item_id}] (Locations: {self.locations} |" \
return f"{self.name} (Locations: {self.locations} |" \
f" Seasons: {self.seasons} |" \
f" Difficulty: {self.difficulty}) |" \
f"Mod: {self.mod_name}"
@ -39,92 +43,149 @@ ginger_island_ocean = (Region.island_south, Region.island_west)
ginger_island_river = (Region.island_west,)
pirate_cove = (Region.pirate_cove,)
crimson_badlands = (SVERegion.crimson_badlands,)
shearwater = (SVERegion.shearwater,)
highlands = (SVERegion.highlands_outside,)
sprite_spring = (SVERegion.sprite_spring,)
fable_reef = (SVERegion.fable_reef,)
vineyard = (SVERegion.blue_moon_vineyard,)
all_fish: List[FishItem] = []
def create_fish(name: str, item_id: int, locations: Tuple[str, ...], seasons: Union[str, Tuple[str, ...]],
difficulty: int, mod_name: Optional[str] = None) -> 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:
if isinstance(seasons, str):
seasons = (seasons,)
fish_item = FishItem(name, item_id, locations, seasons, difficulty, mod_name)
fish_item = FishItem(name, locations, seasons, difficulty, legendary, extended_family, mod_name)
all_fish.append(fish_item)
return fish_item
albacore = create_fish("Albacore", 705, ocean, (season.fall, season.winter), 60)
anchovy = create_fish("Anchovy", 129, ocean, (season.spring, season.fall), 30)
blue_discus = create_fish("Blue Discus", 838, ginger_island_river, season.all_seasons, 60)
bream = create_fish("Bream", 132, town_river + forest_river, season.all_seasons, 35)
bullhead = create_fish("Bullhead", 700, mountain_lake, season.all_seasons, 46)
carp = create_fish("Carp", 142, mountain_lake + secret_woods + sewers + mutant_bug_lair, season.not_winter, 15)
catfish = create_fish("Catfish", 143, town_river + forest_river + secret_woods, (season.spring, season.fall), 75)
chub = create_fish("Chub", 702, forest_river + mountain_lake, season.all_seasons, 35)
dorado = create_fish("Dorado", 704, forest_river, season.summer, 78)
eel = create_fish("Eel", 148, ocean, (season.spring, season.fall), 70)
flounder = create_fish("Flounder", 267, ocean, (season.spring, season.summer), 50)
ghostfish = create_fish("Ghostfish", 156, mines_floor_20 + mines_floor_60, season.all_seasons, 50)
halibut = create_fish("Halibut", 708, ocean, season.not_fall, 50)
herring = create_fish("Herring", 147, ocean, (season.spring, season.winter), 25)
ice_pip = create_fish("Ice Pip", 161, mines_floor_60, season.all_seasons, 85)
largemouth_bass = create_fish("Largemouth Bass", 136, mountain_lake, season.all_seasons, 50)
lava_eel = create_fish("Lava Eel", 162, mines_floor_100, season.all_seasons, 90)
lingcod = create_fish("Lingcod", 707, town_river + forest_river + mountain_lake, season.winter, 85)
lionfish = create_fish("Lionfish", 837, ginger_island_ocean, season.all_seasons, 50)
midnight_carp = create_fish("Midnight Carp", 269, mountain_lake + forest_pond + ginger_island_river,
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)
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,
(season.fall, season.winter), 55)
octopus = create_fish("Octopus", 149, ocean, season.summer, 95)
perch = create_fish("Perch", 141, town_river + forest_river + forest_pond + mountain_lake, season.winter, 35)
pike = create_fish("Pike", 144, town_river + forest_river + forest_pond, (season.summer, season.winter), 60)
pufferfish = create_fish("Pufferfish", 128, ocean + ginger_island_ocean, season.summer, 80)
rainbow_trout = create_fish("Rainbow Trout", 138, town_river + forest_river + mountain_lake, season.summer, 45)
red_mullet = create_fish("Red Mullet", 146, ocean, (season.summer, season.winter), 55)
red_snapper = create_fish("Red Snapper", 150, ocean, (season.summer, season.fall), 40)
salmon = create_fish("Salmon", 139, town_river + forest_river, season.fall, 50)
sandfish = create_fish("Sandfish", 164, desert, season.all_seasons, 65)
sardine = create_fish("Sardine", 131, ocean, (season.spring, season.fall, season.winter), 30)
scorpion_carp = create_fish("Scorpion Carp", 165, desert, season.all_seasons, 90)
sea_cucumber = create_fish("Sea Cucumber", 154, ocean, (season.fall, season.winter), 40)
shad = create_fish("Shad", 706, town_river + forest_river, season.not_winter, 45)
slimejack = create_fish("Slimejack", 796, mutant_bug_lair, season.all_seasons, 55)
smallmouth_bass = create_fish("Smallmouth Bass", 137, town_river + forest_river, (season.spring, season.fall), 28)
squid = create_fish("Squid", 151, ocean, season.winter, 75)
stingray = create_fish("Stingray", 836, pirate_cove, season.all_seasons, 80)
stonefish = create_fish("Stonefish", 158, mines_floor_20, season.all_seasons, 65)
sturgeon = create_fish("Sturgeon", 698, mountain_lake, (season.summer, season.winter), 78)
sunfish = create_fish("Sunfish", 145, town_river + forest_river, (season.spring, season.summer), 30)
super_cucumber = create_fish("Super Cucumber", 155, ocean + ginger_island_ocean, (season.summer, season.fall), 80)
tiger_trout = create_fish("Tiger Trout", 699, town_river + forest_river, (season.fall, season.winter), 60)
tilapia = create_fish("Tilapia", 701, ocean + ginger_island_ocean, (season.summer, season.fall), 50)
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)
# Tuna has different seasons on ginger island. Should be changed when the whole fish thing is refactored
tuna = create_fish("Tuna", 130, ocean + ginger_island_ocean, (season.summer, season.winter), 70)
void_salmon = create_fish("Void Salmon", 795, witch_swamp, season.all_seasons, 80)
walleye = create_fish("Walleye", 140, town_river + forest_river + forest_pond + mountain_lake, season.fall, 45)
woodskip = create_fish("Woodskip", 734, secret_woods, season.all_seasons, 50)
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)
blob_fish = create_fish("Blobfish", 800, night_market, season.winter, 75)
midnight_squid = create_fish("Midnight Squid", 798, night_market, season.winter, 55)
spook_fish = create_fish("Spook Fish", 799, night_market, season.winter, 60)
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)
angler = create_fish("Angler", 160, town_river, season.fall, 85)
crimsonfish = create_fish("Crimsonfish", 159, ocean, season.summer, 95)
glacierfish = create_fish("Glacierfish", 775, forest_river, season.winter, 100)
legend = create_fish("Legend", 163, mountain_lake, season.spring, 110)
mutant_carp = create_fish("Mutant Carp", 682, sewers, season.all_seasons, 80)
angler = create_fish(Fish.angler, town_river, season.fall, 85, True, False)
crimsonfish = create_fish(Fish.crimsonfish, ocean, season.summer, 95, True, False)
glacierfish = create_fish(Fish.glacierfish, forest_river, season.winter, 100, True, False)
legend = create_fish(Fish.legend, mountain_lake, season.spring, 110, True, False)
mutant_carp = create_fish(Fish.mutant_carp, sewers, season.all_seasons, 80, True, False)
clam = create_fish("Clam", 372, ocean, season.all_seasons, -1)
cockle = create_fish("Cockle", 718, ocean, season.all_seasons, -1)
crab = create_fish("Crab", 717, ocean, season.all_seasons, -1)
crayfish = create_fish("Crayfish", 716, fresh_water, season.all_seasons, -1)
lobster = create_fish("Lobster", 715, ocean, season.all_seasons, -1)
mussel = create_fish("Mussel", 719, ocean, season.all_seasons, -1)
oyster = create_fish("Oyster", 723, ocean, season.all_seasons, -1)
periwinkle = create_fish("Periwinkle", 722, fresh_water, season.all_seasons, -1)
shrimp = create_fish("Shrimp", 720, ocean, season.all_seasons, -1)
snail = create_fish("Snail", 721, fresh_water, season.all_seasons, -1)
ms_angler = create_fish(Fish.ms_angler, town_river, season.fall, 85, True, True)
son_of_crimsonfish = create_fish(Fish.son_of_crimsonfish, ocean, season.summer, 95, True, True)
glacierfish_jr = create_fish(Fish.glacierfish_jr, forest_river, season.winter, 100, True, True)
legend_ii = create_fish(Fish.legend_ii, mountain_lake, season.spring, 110, True, True)
radioactive_carp = create_fish(Fish.radioactive_carp, sewers, season.all_seasons, 80, True, True)
legendary_fish = [crimsonfish, angler, legend, glacierfish, mutant_carp]
baby_lunaloo = create_fish(SVEFish.baby_lunaloo, ginger_island_ocean, season.all_seasons, 15, mod_name=ModNames.sve)
bonefish = create_fish(SVEFish.bonefish, crimson_badlands, season.all_seasons, 70, mod_name=ModNames.sve)
bull_trout = create_fish(SVEFish.bull_trout, forest_river, season.not_spring, 45, mod_name=ModNames.sve)
butterfish = create_fish(SVEFish.butterfish, shearwater, season.not_winter, 75, mod_name=ModNames.sve)
clownfish = create_fish(SVEFish.clownfish, ginger_island_ocean, season.all_seasons, 45, mod_name=ModNames.sve)
daggerfish = create_fish(SVEFish.daggerfish, highlands, season.all_seasons, 50, mod_name=ModNames.sve)
frog = create_fish(SVEFish.frog, mountain_lake, (season.spring, season.summer), 70, mod_name=ModNames.sve)
gemfish = create_fish(SVEFish.gemfish, highlands, season.all_seasons, 100, mod_name=ModNames.sve)
goldenfish = create_fish(SVEFish.goldenfish, sprite_spring, season.all_seasons, 60, mod_name=ModNames.sve)
grass_carp = create_fish(SVEFish.grass_carp, secret_woods, (season.spring, season.summer), 85, mod_name=ModNames.sve)
king_salmon = create_fish(SVEFish.king_salmon, forest_river, (season.spring, season.summer), 80, mod_name=ModNames.sve)
kittyfish = create_fish(SVEFish.kittyfish, shearwater, (season.fall, season.winter), 85, mod_name=ModNames.sve)
lunaloo = create_fish(SVEFish.lunaloo, ginger_island_ocean, season.all_seasons, 70, mod_name=ModNames.sve)
meteor_carp = create_fish(SVEFish.meteor_carp, sprite_spring, season.all_seasons, 80, mod_name=ModNames.sve)
minnow = create_fish(SVEFish.minnow, town_river, season.all_seasons, 1, mod_name=ModNames.sve)
puppyfish = create_fish(SVEFish.puppyfish, shearwater, season.not_winter, 85, mod_name=ModNames.sve)
radioactive_bass = create_fish(SVEFish.radioactive_bass, sewers, season.all_seasons, 90, mod_name=ModNames.sve)
seahorse = create_fish(SVEFish.seahorse, ginger_island_ocean, season.all_seasons, 25, mod_name=ModNames.sve)
shiny_lunaloo = create_fish(SVEFish.shiny_lunaloo, ginger_island_ocean, season.all_seasons, 110, mod_name=ModNames.sve)
snatcher_worm = create_fish(SVEFish.snatcher_worm, mutant_bug_lair, season.all_seasons, 75, mod_name=ModNames.sve)
starfish = create_fish(SVEFish.starfish, ginger_island_ocean, season.all_seasons, 75, mod_name=ModNames.sve)
torpedo_trout = create_fish(SVEFish.torpedo_trout, fable_reef, season.all_seasons, 70, mod_name=ModNames.sve)
undeadfish = create_fish(SVEFish.undeadfish, crimson_badlands, season.all_seasons, 80, mod_name=ModNames.sve)
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("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]
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

View File

@ -1,13 +0,0 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class GameItem:
name: str
item_id: int
def __repr__(self):
return f"{self.name} [{self.item_id}]"
def __lt__(self, other):
return self.name < other.name

View File

@ -7,48 +7,46 @@ id,name,classification,groups,mod_name
19,Glittering Boulder Removed,progression,COMMUNITY_REWARD,
20,Minecarts Repair,useful,COMMUNITY_REWARD,
21,Bus Repair,progression,COMMUNITY_REWARD,
22,Movie Theater,useful,,
22,Progressive Movie Theater,progression,COMMUNITY_REWARD,
23,Stardrop,progression,,
24,Progressive Backpack,progression,,
25,Rusty Sword,progression,WEAPON,
26,Leather Boots,progression,"FOOTWEAR,MINES_FLOOR_10",
27,Work Boots,useful,"FOOTWEAR,MINES_FLOOR_10",
28,Wooden Blade,progression,"MINES_FLOOR_10,WEAPON",
29,Iron Dirk,progression,"MINES_FLOOR_10,WEAPON",
30,Wind Spire,progression,"MINES_FLOOR_10,WEAPON",
31,Femur,progression,"MINES_FLOOR_10,WEAPON",
32,Steel Smallsword,progression,"MINES_FLOOR_20,WEAPON",
33,Wood Club,progression,"MINES_FLOOR_20,WEAPON",
34,Elf Blade,progression,"MINES_FLOOR_20,WEAPON",
35,Glow Ring,useful,"MINES_FLOOR_20,RING",
36,Magnet Ring,useful,"MINES_FLOOR_20,RING",
37,Slingshot,progression,WEAPON,
38,Tundra Boots,useful,"FOOTWEAR,MINES_FLOOR_50",
39,Thermal Boots,useful,"FOOTWEAR,MINES_FLOOR_50",
40,Combat Boots,useful,"FOOTWEAR,MINES_FLOOR_50",
41,Silver Saber,progression,"MINES_FLOOR_50,WEAPON",
42,Pirate's Sword,progression,"MINES_FLOOR_50,WEAPON",
43,Crystal Dagger,progression,"MINES_FLOOR_60,WEAPON",
44,Cutlass,progression,"MINES_FLOOR_60,WEAPON",
45,Iron Edge,progression,"MINES_FLOOR_60,WEAPON",
46,Burglar's Shank,progression,"MINES_FLOOR_60,WEAPON",
47,Wood Mallet,progression,"MINES_FLOOR_60,WEAPON",
48,Master Slingshot,progression,WEAPON,
49,Firewalker Boots,useful,"FOOTWEAR,MINES_FLOOR_80",
50,Dark Boots,useful,"FOOTWEAR,MINES_FLOOR_80",
51,Claymore,progression,"MINES_FLOOR_80,WEAPON",
52,Templar's Blade,progression,"MINES_FLOOR_80,WEAPON",
53,Kudgel,progression,"MINES_FLOOR_80,WEAPON",
54,Shadow Dagger,progression,"MINES_FLOOR_80,WEAPON",
55,Obsidian Edge,progression,"MINES_FLOOR_90,WEAPON",
56,Tempered Broadsword,progression,"MINES_FLOOR_90,WEAPON",
57,Wicked Kris,progression,"MINES_FLOOR_90,WEAPON",
58,Bone Sword,progression,"MINES_FLOOR_90,WEAPON",
59,Ossified Blade,progression,"MINES_FLOOR_90,WEAPON",
60,Space Boots,useful,"FOOTWEAR,MINES_FLOOR_110",
61,Crystal Shoes,useful,"FOOTWEAR,MINES_FLOOR_110",
62,Steel Falchion,progression,"MINES_FLOOR_110,WEAPON",
63,The Slammer,progression,"MINES_FLOOR_110,WEAPON",
25,Rusty Sword,filler,"WEAPON,DEPRECATED",
26,Leather Boots,filler,"FOOTWEAR,DEPRECATED",
27,Work Boots,filler,"FOOTWEAR,DEPRECATED",
28,Wooden Blade,filler,"WEAPON,DEPRECATED",
29,Iron Dirk,filler,"WEAPON,DEPRECATED",
30,Wind Spire,filler,"WEAPON,DEPRECATED",
31,Femur,filler,"WEAPON,DEPRECATED",
32,Steel Smallsword,filler,"WEAPON,DEPRECATED",
33,Wood Club,filler,"WEAPON,DEPRECATED",
34,Elf Blade,filler,"WEAPON,DEPRECATED",
37,Slingshot,filler,"WEAPON,DEPRECATED",
38,Tundra Boots,filler,"FOOTWEAR,DEPRECATED",
39,Thermal Boots,filler,"FOOTWEAR,DEPRECATED",
40,Combat Boots,filler,"FOOTWEAR,DEPRECATED",
41,Silver Saber,filler,"WEAPON,DEPRECATED",
42,Pirate's Sword,filler,"WEAPON,DEPRECATED",
43,Crystal Dagger,filler,"WEAPON,DEPRECATED",
44,Cutlass,filler,"WEAPON,DEPRECATED",
45,Iron Edge,filler,"WEAPON,DEPRECATED",
46,Burglar's Shank,filler,"WEAPON,DEPRECATED",
47,Wood Mallet,filler,"WEAPON,DEPRECATED",
48,Master Slingshot,filler,"WEAPON,DEPRECATED",
49,Firewalker Boots,filler,"FOOTWEAR,DEPRECATED",
50,Dark Boots,filler,"FOOTWEAR,DEPRECATED",
51,Claymore,filler,"WEAPON,DEPRECATED",
52,Templar's Blade,filler,"WEAPON,DEPRECATED",
53,Kudgel,filler,"WEAPON,DEPRECATED",
54,Shadow Dagger,filler,"WEAPON,DEPRECATED",
55,Obsidian Edge,filler,"WEAPON,DEPRECATED",
56,Tempered Broadsword,filler,"WEAPON,DEPRECATED",
57,Wicked Kris,filler,"WEAPON,DEPRECATED",
58,Bone Sword,filler,"WEAPON,DEPRECATED",
59,Ossified Blade,filler,"WEAPON,DEPRECATED",
60,Space Boots,filler,"FOOTWEAR,DEPRECATED",
61,Crystal Shoes,filler,"FOOTWEAR,DEPRECATED",
62,Steel Falchion,filler,"WEAPON,DEPRECATED",
63,The Slammer,filler,"WEAPON,DEPRECATED",
64,Skull Key,progression,,
65,Progressive Hoe,progression,PROGRESSIVE_TOOLS,
66,Progressive Pickaxe,progression,PROGRESSIVE_TOOLS,
@ -63,40 +61,40 @@ id,name,classification,groups,mod_name
75,Foraging Level,progression,SKILL_LEVEL_UP,
76,Mining Level,progression,SKILL_LEVEL_UP,
77,Combat Level,progression,SKILL_LEVEL_UP,
78,Earth Obelisk,progression,,
79,Water Obelisk,progression,,
80,Desert Obelisk,progression,,
81,Island Obelisk,progression,GINGER_ISLAND,
82,Junimo Hut,useful,,
83,Gold Clock,progression,,
84,Progressive Coop,progression,,
85,Progressive Barn,progression,,
86,Well,useful,,
87,Silo,progression,,
88,Mill,progression,,
89,Progressive Shed,progression,,
90,Fish Pond,progression,,
91,Stable,useful,,
92,Slime Hutch,useful,,
93,Shipping Bin,progression,,
78,Earth Obelisk,progression,WIZARD_BUILDING,
79,Water Obelisk,progression,WIZARD_BUILDING,
80,Desert Obelisk,progression,WIZARD_BUILDING,
81,Island Obelisk,progression,"WIZARD_BUILDING,GINGER_ISLAND",
82,Junimo Hut,useful,WIZARD_BUILDING,
83,Gold Clock,progression,WIZARD_BUILDING,
84,Progressive Coop,progression,BUILDING,
85,Progressive Barn,progression,BUILDING,
86,Well,useful,BUILDING,
87,Silo,progression,BUILDING,
88,Mill,progression,BUILDING,
89,Progressive Shed,progression,BUILDING,
90,Fish Pond,progression,BUILDING,
91,Stable,useful,BUILDING,
92,Slime Hutch,progression,BUILDING,
93,Shipping Bin,progression,BUILDING,
94,Beach Bridge,progression,,
95,Adventurer's Guild,progression,,
95,Adventurer's Guild,progression,DEPRECATED,
96,Club Card,progression,,
97,Magnifying Glass,progression,,
98,Bear's Knowledge,progression,,
99,Iridium Snake Milk,progression,,
99,Iridium Snake Milk,useful,,
100,JotPK: Progressive Boots,progression,ARCADE_MACHINE_BUFFS,
101,JotPK: Progressive Gun,progression,ARCADE_MACHINE_BUFFS,
102,JotPK: Progressive Ammo,progression,ARCADE_MACHINE_BUFFS,
103,JotPK: Extra Life,progression,ARCADE_MACHINE_BUFFS,
104,JotPK: Increased Drop Rate,progression,ARCADE_MACHINE_BUFFS,
105,Junimo Kart: Extra Life,progression,ARCADE_MACHINE_BUFFS,
106,Galaxy Sword,progression,"GALAXY_WEAPONS,WEAPON",
107,Galaxy Dagger,progression,"GALAXY_WEAPONS,WEAPON",
108,Galaxy Hammer,progression,"GALAXY_WEAPONS,WEAPON",
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,,
111,Lava Katana,progression,"MINES_FLOOR_110,WEAPON",
111,Lava Katana,filler,"WEAPON,DEPRECATED",
112,Progressive House,progression,,
113,Traveling Merchant: Sunday,progression,TRAVELING_MERCHANT_DAY,
114,Traveling Merchant: Monday,progression,TRAVELING_MERCHANT_DAY,
@ -105,8 +103,8 @@ id,name,classification,groups,mod_name
117,Traveling Merchant: Thursday,progression,TRAVELING_MERCHANT_DAY,
118,Traveling Merchant: Friday,progression,TRAVELING_MERCHANT_DAY,
119,Traveling Merchant: Saturday,progression,TRAVELING_MERCHANT_DAY,
120,Traveling Merchant Stock Size,progression,,
121,Traveling Merchant Discount,progression,,
120,Traveling Merchant Stock Size,useful,,
121,Traveling Merchant Discount,useful,,
122,Return Scepter,useful,,
123,Progressive Season,progression,,
124,Spring,progression,SEASON,
@ -223,7 +221,7 @@ id,name,classification,groups,mod_name
235,Quality Bobber Recipe,progression,SPECIAL_ORDER_BOARD,
236,Mini-Obelisk Recipe,progression,SPECIAL_ORDER_BOARD,
237,Monster Musk Recipe,progression,SPECIAL_ORDER_BOARD,
239,Sewing Machine,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD",
239,Sewing Machine,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD",
240,Coffee Maker,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD",
241,Mini-Fridge,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD",
242,Mini-Shipping Bin,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD",
@ -245,7 +243,7 @@ id,name,classification,groups,mod_name
258,Banana Sapling,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY",
259,Mango Sapling,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY",
260,Boat Repair,progression,GINGER_ISLAND,
261,Open Professor Snail Cave,progression,"GINGER_ISLAND",
261,Open Professor Snail Cave,progression,GINGER_ISLAND,
262,Island North Turtle,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
263,Island West Turtle,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
264,Island Farmhouse,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
@ -254,42 +252,218 @@ id,name,classification,groups,mod_name
267,Dig Site Bridge,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
268,Island Trader,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
269,Volcano Bridge,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
270,Volcano Exit Shortcut,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
270,Volcano Exit Shortcut,useful,"GINGER_ISLAND,WALNUT_PURCHASE",
271,Island Resort,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
272,Parrot Express,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
273,Qi Walnut Room,progression,"GINGER_ISLAND,WALNUT_PURCHASE",
274,Pineapple Seeds,progression,"GINGER_ISLAND,CROPSANITY",
275,Taro Tuber,progression,"GINGER_ISLAND,CROPSANITY",
276,Weather Report,useful,"TV_CHANNEL",
277,Fortune Teller,useful,"TV_CHANNEL",
278,Livin' Off The Land,useful,"TV_CHANNEL",
279,The Queen of Sauce,progression,"TV_CHANNEL",
280,Fishing Information Broadcasting Service,useful,"TV_CHANNEL",
281,Sinister Signal,useful,"TV_CHANNEL",
276,Weather Report,useful,TV_CHANNEL,
277,Fortune Teller,useful,TV_CHANNEL,
278,Livin' Off The Land,useful,TV_CHANNEL,
279,The Queen of Sauce,progression,TV_CHANNEL,
280,Fishing Information Broadcasting Service,useful,TV_CHANNEL,
281,Sinister Signal,filler,TV_CHANNEL,
282,Dark Talisman,progression,,
283,Ostrich Incubator Recipe,progression,"GINGER_ISLAND",
284,Cute Baby,progression,"BABY",
285,Ugly Baby,progression,"BABY",
286,Deluxe Scarecrow Recipe,progression,"FESTIVAL,RARECROW",
287,Treehouse,progression,"GINGER_ISLAND",
283,Ostrich Incubator Recipe,progression,GINGER_ISLAND,
284,Cute Baby,progression,BABY,
285,Ugly Baby,progression,BABY,
286,Deluxe Scarecrow Recipe,progression,RARECROW,
287,Treehouse,progression,GINGER_ISLAND,
288,Coffee Bean,progression,CROPSANITY,
4001,Burnt,trap,TRAP,
4002,Darkness,trap,TRAP,
4003,Frozen,trap,TRAP,
4004,Jinxed,trap,TRAP,
4005,Nauseated,trap,TRAP,
4006,Slimed,trap,TRAP,
4007,Weakness,trap,TRAP,
4008,Taxes,trap,TRAP,
4009,Random Teleport,trap,TRAP,
4010,The Crows,trap,TRAP,
4011,Monsters,trap,TRAP,
4012,Entrance Reshuffle,trap,"TRAP,DEPRECATED",
4013,Debris,trap,TRAP,
4014,Shuffle,trap,TRAP,
4015,Temporary Winter,trap,"TRAP,DEPRECATED",
4016,Pariah,trap,TRAP,
4017,Drought,trap,TRAP,
289,Progressive Weapon,progression,"WEAPON,WEAPON_GENERIC",
290,Progressive Sword,progression,"WEAPON,WEAPON_SWORD",
291,Progressive Club,progression,"WEAPON,WEAPON_CLUB",
292,Progressive Dagger,progression,"WEAPON,WEAPON_DAGGER",
293,Progressive Slingshot,progression,"WEAPON,WEAPON_SLINGSHOT",
294,Progressive Footwear,useful,FOOTWEAR,
295,Small Glow Ring,filler,"RING,RESOURCE_PACK,MAXIMUM_ONE",
296,Glow Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
297,Small Magnet Ring,filler,"RING,RESOURCE_PACK,MAXIMUM_ONE",
298,Magnet Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
299,Slime Charmer Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
300,Warrior Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
301,Vampire Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
302,Savage Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
303,Ring of Yoba,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
304,Sturdy Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
305,Burglar's Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
306,Iridium Band,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
307,Jukebox Ring,filler,"RING,RESOURCE_PACK,MAXIMUM_ONE",
308,Amethyst Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
309,Topaz Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
310,Aquamarine Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
311,Jade Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
312,Emerald Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
313,Ruby Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
314,Wedding Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
315,Crabshell Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
316,Napalm Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
317,Thorns Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
318,Lucky Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
319,Hot Java Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
320,Protection Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
321,Soul Sapper Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
322,Phoenix Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
323,Immunity Band,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
324,Glowstone Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE",
325,Fairy Dust Recipe,progression,,
326,Heavy Tapper Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND",
327,Hyper Speed-Gro Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND",
328,Deluxe Fertilizer Recipe,progression,QI_CRAFTING_RECIPE,
329,Hopper Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND",
330,Magic Bait Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND",
331,Jack-O-Lantern Recipe,progression,FESTIVAL,
333,Tub o' Flowers Recipe,progression,FESTIVAL,
335,Moonlight Jellies Banner,filler,FESTIVAL,
336,Starport Decal,filler,FESTIVAL,
337,Golden Egg,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
340,Algae Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
341,Artichoke Dip Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
342,Autumn's Bounty Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
343,Baked Fish Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
344,Banana Pudding Recipe,progression,"CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE",
345,Bean Hotpot Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
346,Blackberry Cobbler Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
347,Blueberry Tart Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
348,Bread Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_FRIENDSHIP,CHEFSANITY_PURCHASE",
349,Bruschetta Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
350,Carp Surprise Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
351,Cheese Cauliflower Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
352,Chocolate Cake Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
353,Chowder Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
354,Coleslaw Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
355,Complete Breakfast Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
356,Cookies Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
357,Crab Cakes Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
358,Cranberry Candy Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
359,Cranberry Sauce Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
360,Crispy Bass Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
361,Dish O' The Sea Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
362,Eggplant Parmesan Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
363,Escargot Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
364,Farmer's Lunch Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
365,Fiddlehead Risotto Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
366,Fish Stew Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
367,Fish Taco Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
368,Fried Calamari Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
369,Fried Eel Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
370,Fried Egg Recipe,progression,CHEFSANITY_STARTER,
371,Fried Mushroom Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
372,Fruit Salad Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
373,Ginger Ale Recipe,progression,"CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE",
374,Glazed Yams Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
375,Hashbrowns Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE",
376,Ice Cream Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
377,Lobster Bisque Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_FRIENDSHIP",
378,Lucky Lunch Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
379,Maki Roll Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE",
380,Mango Sticky Rice Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP,GINGER_ISLAND",
381,Maple Bar Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
382,Miner's Treat Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
383,Omelet Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE",
384,Pale Broth Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
385,Pancakes Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE",
386,Parsnip Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
387,Pepper Poppers Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
388,Pink Cake Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
389,Pizza Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE",
390,Plum Pudding Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
391,Poi Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP,GINGER_ISLAND",
392,Poppyseed Muffin Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
393,Pumpkin Pie Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
394,Pumpkin Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
395,Radish Salad Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
396,Red Plate Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
397,Rhubarb Pie Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
398,Rice Pudding Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
399,Roasted Hazelnuts Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
400,Roots Platter Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
401,Salad Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
402,Salmon Dinner Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
403,Sashimi Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
404,Seafoam Pudding Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
405,Shrimp Cocktail Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
406,Spaghetti Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
407,Spicy Eel Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
408,Squid Ink Ravioli Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
409,Stir Fry Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS",
410,Strange Bun Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
411,Stuffing Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
412,Super Meal Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
413,Survival Burger Recipe,progression,"CHEFSANITY,CHEFSANITY_SKILL",
414,Tom Kha Soup Recipe,progression,"CHEFSANITY,CHEFSANITY_FRIENDSHIP",
415,Tortilla Recipe,progression,"CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE",
416,Triple Shot Espresso Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",
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",
425,Gate Recipe,progression,CRAFTSANITY,
426,Wood Fence Recipe,progression,CRAFTSANITY,
427,Deluxe Retaining Soil Recipe,progression,"CRAFTSANITY,GINGER_ISLAND",
428,Grass Starter Recipe,progression,CRAFTSANITY,
429,Wood Floor Recipe,progression,CRAFTSANITY,
430,Rustic Plank Floor Recipe,progression,CRAFTSANITY,
431,Straw Floor Recipe,progression,CRAFTSANITY,
432,Weathered Floor Recipe,progression,CRAFTSANITY,
433,Crystal Floor Recipe,progression,CRAFTSANITY,
434,Stone Floor Recipe,progression,CRAFTSANITY,
435,Stone Walkway Floor Recipe,progression,CRAFTSANITY,
436,Brick Floor Recipe,progression,CRAFTSANITY,
437,Wood Path Recipe,progression,CRAFTSANITY,
438,Gravel Path Recipe,progression,CRAFTSANITY,
439,Cobblestone Path Recipe,progression,CRAFTSANITY,
440,Stepping Stone Path Recipe,progression,CRAFTSANITY,
441,Crystal Path Recipe,progression,CRAFTSANITY,
442,Wedding Ring Recipe,progression,CRAFTSANITY,
443,Warp Totem: Desert Recipe,progression,CRAFTSANITY,
444,Warp Totem: Island Recipe,progression,"CRAFTSANITY,GINGER_ISLAND",
445,Torch Recipe,progression,CRAFTSANITY,
446,Campfire Recipe,progression,CRAFTSANITY,
447,Wooden Brazier Recipe,progression,CRAFTSANITY,
448,Stone Brazier Recipe,progression,CRAFTSANITY,
449,Gold Brazier Recipe,progression,CRAFTSANITY,
450,Carved Brazier Recipe,progression,CRAFTSANITY,
451,Stump Brazier Recipe,progression,CRAFTSANITY,
452,Barrel Brazier Recipe,progression,CRAFTSANITY,
453,Skull Brazier Recipe,progression,CRAFTSANITY,
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,
458,Wicked Statue Recipe,progression,CRAFTSANITY,
459,Chest Recipe,progression,CRAFTSANITY,
460,Wood Sign Recipe,progression,CRAFTSANITY,
461,Stone Sign Recipe,progression,CRAFTSANITY,
469,Railroad Boulder Removed,progression,,
470,Fruit Bats,progression,,
471,Mushroom Boxes,progression,,
475,The Gateway Gazette,progression,TV_CHANNEL,
4001,Burnt Trap,trap,TRAP,
4002,Darkness Trap,trap,TRAP,
4003,Frozen Trap,trap,TRAP,
4004,Jinxed Trap,trap,TRAP,
4005,Nauseated Trap,trap,TRAP,
4006,Slimed Trap,trap,TRAP,
4007,Weakness Trap,trap,TRAP,
4008,Taxes Trap,trap,TRAP,
4009,Random Teleport Trap,trap,TRAP,
4010,The Crows Trap,trap,TRAP,
4011,Monsters Trap,trap,TRAP,
4012,Entrance Reshuffle Trap,trap,"TRAP,DEPRECATED",
4013,Debris Trap,trap,TRAP,
4014,Shuffle Trap,trap,TRAP,
4015,Temporary Winter Trap,trap,"TRAP,DEPRECATED",
4016,Pariah Trap,trap,TRAP,
4017,Drought Trap,trap,TRAP,
4018,Time Flies Trap,trap,TRAP,
4019,Babies Trap,trap,TRAP,
4020,Meow Trap,trap,TRAP,
4021,Bark Trap,trap,TRAP,
4022,Depression Trap,trap,"TRAP,DEPRECATED",
4023,Benjamin Budton Trap,trap,TRAP,
4024,Inflation Trap,trap,TRAP,
4025,Bomb Trap,trap,TRAP,
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",
@ -336,12 +510,12 @@ id,name,classification,groups,mod_name
5043,Resource Pack: 7 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5044,Resource Pack: 9 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5045,Resource Pack: 10 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5046,Resource Pack: 1 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5047,Resource Pack: 3 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5048,Resource Pack: 5 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM",
5049,Resource Pack: 7 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5050,Resource Pack: 9 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5051,Resource Pack: 10 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5046,Resource Pack: 1 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND",
5047,Resource Pack: 3 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND",
5048,Resource Pack: 5 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND",
5049,Resource Pack: 7 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND",
5050,Resource Pack: 9 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND",
5051,Resource Pack: 10 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND",
5052,Resource Pack: 1 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5053,Resource Pack: 3 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM",
5054,Resource Pack: 5 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM",
@ -492,7 +666,7 @@ id,name,classification,groups,mod_name
5199,Friendship Bonus (2 <3),useful,"FRIENDSHIP_PACK,COMMUNITY_REWARD",
5200,Friendship Bonus (3 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK",
5201,Friendship Bonus (4 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK",
5202,30 Qi Gems,useful,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5202,15 Qi Gems,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5203,Solar Panel,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5204,Geode Crusher,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5205,Farm Computer,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
@ -507,36 +681,29 @@ id,name,classification,groups,mod_name
5214,Quality Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5215,Iridium Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5216,Scarecrow,filler,RESOURCE_PACK,
5217,Deluxe Scarecrow,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5217,Deluxe Scarecrow,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5218,Furnace,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5219,Charcoal Kiln,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5220,Lightning Rod,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5221,Resource Pack: 5000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5222,Resource Pack: 10000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5223,Junimo Chest,filler,"EXACTLY_TWO,RESOURCE_PACK",
5224,Horse Flute,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5225,Pierre's Missing Stocklist,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5224,Horse Flute,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5225,Pierre's Missing Stocklist,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5226,Hopper,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5227,Enricher,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5228,Pressure Nozzle,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5229,Deconstructor,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5230,Key To The Town,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5230,Key To The Town,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5231,Galaxy Soul,filler,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5232,Mushroom Tree Seed,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5233,Resource Pack: 20 Magic Bait,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5234,Resource Pack: 10 Qi Seasoning,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5235,Mr. Qi's Hat,filler,"MAXIMUM_ONE,RESOURCE_PACK",
5236,Aquatic Sanctuary,filler,RESOURCE_PACK,
5237,Heavy Tapper Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5238,Hyper Speed-Gro Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5239,Deluxe Fertilizer Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5240,Hopper Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5241,Magic Bait Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5242,Exotic Double Bed,filler,RESOURCE_PACK,
5243,Resource Pack: 2 Qi Gem,filler,"GINGER_ISLAND,RESOURCE_PACK",
5244,Golden Egg,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5243,Resource Pack: 2 Qi Gem,filler,"GINGER_ISLAND,RESOURCE_PACK,DEPRECATED",
5245,Golden Walnut,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,GINGER_ISLAND",
5246,Fairy Dust Recipe,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
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",
@ -552,10 +719,13 @@ id,name,classification,groups,mod_name
5259,Worm Bin,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5260,Tapper,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5261,Heavy Tapper,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5262,Slime Incubator,useful,"RESOURCE_PACK",
5263,Slime Egg-Press,useful,"RESOURCE_PACK",
5262,Slime Incubator,useful,RESOURCE_PACK,
5263,Slime Egg-Press,useful,RESOURCE_PACK,
5264,Crystalarium,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5265,Ostrich Incubator,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5266,Resource Pack: 5 Staircase,filler,"RESOURCE_PACK",
5267,Auto-Petter,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
5268,Auto-Grabber,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",
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
@ -569,14 +739,14 @@ id,name,classification,groups,mod_name
10011,Spell: Water,progression,MAGIC_SPELL,Magic
10012,Spell: Blink,progression,MAGIC_SPELL,Magic
10013,Spell: Evac,useful,MAGIC_SPELL,Magic
10014,Spell: Haste,filler,MAGIC_SPELL,Magic
10014,Spell: Haste,useful,MAGIC_SPELL,Magic
10015,Spell: Heal,progression,MAGIC_SPELL,Magic
10016,Spell: Buff,useful,MAGIC_SPELL,Magic
10017,Spell: Shockwave,progression,MAGIC_SPELL,Magic
10018,Spell: Fireball,progression,MAGIC_SPELL,Magic
10019,Spell: Frostbite,progression,MAGIC_SPELL,Magic
10019,Spell: Frostbolt,progression,MAGIC_SPELL,Magic
10020,Spell: Teleport,progression,MAGIC_SPELL,Magic
10021,Spell: Lantern,filler,MAGIC_SPELL,Magic
10021,Spell: Lantern,useful,MAGIC_SPELL,Magic
10022,Spell: Tendrils,progression,MAGIC_SPELL,Magic
10023,Spell: Photosynthesis,useful,MAGIC_SPELL,Magic
10024,Spell: Descend,progression,MAGIC_SPELL,Magic
@ -585,6 +755,9 @@ id,name,classification,groups,mod_name
10027,Spell: Lucksteal,useful,MAGIC_SPELL,Magic
10028,Spell: Spirit,progression,MAGIC_SPELL,Magic
10029,Spell: Rewind,useful,MAGIC_SPELL,Magic
10030,Pendant of Community,progression,,DeepWoods
10031,Pendant of Elders,progression,,DeepWoods
10032,Pendant of Depths,progression,,DeepWoods
10101,Juna <3,progression,FRIENDSANITY,Juna - Roommate NPC
10102,Jasper <3,progression,FRIENDSANITY,Professor Jasper Thomas
10103,Alec <3,progression,FRIENDSANITY,Alec Revisited
@ -596,5 +769,91 @@ id,name,classification,groups,mod_name
10109,Delores <3,progression,FRIENDSANITY,Delores - Custom NPC
10110,Ayeisha <3,progression,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC)
10111,Riley <3,progression,FRIENDSANITY,Custom NPC - Riley
10112,Claire <3,progression,FRIENDSANITY,Stardew Valley Expanded
10113,Lance <3,progression,"FRIENDSANITY,GINGER_ISLAND",Stardew Valley Expanded
10114,Olivia <3,progression,FRIENDSANITY,Stardew Valley Expanded
10115,Sophia <3,progression,FRIENDSANITY,Stardew Valley Expanded
10116,Victor <3,progression,FRIENDSANITY,Stardew Valley Expanded
10117,Andy <3,progression,FRIENDSANITY,Stardew Valley Expanded
10118,Apples <3,progression,FRIENDSANITY,Stardew Valley Expanded
10119,Gunther <3,progression,FRIENDSANITY,Stardew Valley Expanded
10120,Martin <3,progression,FRIENDSANITY,Stardew Valley Expanded
10121,Marlon <3,progression,FRIENDSANITY,Stardew Valley Expanded
10122,Morgan <3,progression,FRIENDSANITY,Stardew Valley Expanded
10123,Scarlett <3,progression,FRIENDSANITY,Stardew Valley Expanded
10124,Susan <3,progression,FRIENDSANITY,Stardew Valley Expanded
10125,Morris <3,progression,FRIENDSANITY,Stardew Valley Expanded
10126,Alecto <3,progression,FRIENDSANITY,Alecto the Witch
10127,Zic <3,progression,FRIENDSANITY,Distant Lands - Witch Swamp Overhaul
10128,Lacey <3,progression,FRIENDSANITY,Hat Mouse Lacey
10129,Gregory <3,progression,FRIENDSANITY,Boarding House and Bus Stop Extension
10130,Sheila <3,progression,FRIENDSANITY,Boarding House and Bus Stop Extension
10131,Joel <3,progression,FRIENDSANITY,Boarding House and Bus Stop Extension
10301,Progressive Woods Obelisk Sigils,progression,,DeepWoods
10302,Progressive Skull Cavern Elevator,progression,,Skull Cavern Elevator
10401,Baked Berry Oatmeal Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10402,Big Bark Burger Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10403,Flower Cookie Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10404,Frog Legs Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10405,Glazed Butterfish Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10406,Mixed Berry Pie Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10407,Mushroom Berry Rice Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
10408,Seaweed Salad Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Stardew Valley Expanded
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
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
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
10502,Diamond Wand,filler,"WEAPON,DEPRECATED",Stardew Valley Expanded
10503,Iridium Bomb,progression,,Stardew Valley Expanded
10504,Void Spirit Peace Agreement,useful,GINGER_ISLAND,Stardew Valley Expanded
10505,Kittyfish Spell,progression,,Stardew Valley Expanded
10506,Nexus: Adventurer's Guild Runes,progression,MOD_WARP,Stardew Valley Expanded
10507,Nexus: Junimo Woods Runes,progression,MOD_WARP,Stardew Valley Expanded
10508,Nexus: Aurora Vineyard Runes,progression,MOD_WARP,Stardew Valley Expanded
10509,Nexus: Sprite Spring Runes,progression,MOD_WARP,Stardew Valley Expanded
10510,Nexus: Outpost Runes,progression,MOD_WARP,Stardew Valley Expanded
10511,Nexus: Farm Runes,progression,MOD_WARP,Stardew Valley Expanded
10512,Nexus: Wizard Runes,progression,MOD_WARP,Stardew Valley Expanded
10513,Fable Reef Portal,progression,GINGER_ISLAND,Stardew Valley Expanded
10514,Tempered Galaxy Sword,filler,"WEAPON,DEPRECATED",Stardew Valley Expanded
10515,Tempered Galaxy Dagger,filler,"WEAPON,DEPRECATED",Stardew Valley Expanded
10516,Tempered Galaxy Hammer,filler,"WEAPON,DEPRECATED",Stardew Valley Expanded
10517,Grandpa's Shed,progression,,Stardew Valley Expanded
10518,Aurora Vineyard Tablet,progression,,Stardew Valley Expanded
10519,Scarlett's Job Offer,progression,,Stardew Valley Expanded
10520,Morgan's Schooling,progression,,Stardew Valley Expanded
10601,Magic Elixir Recipe,progression,"CHEFSANITY,CHEFSANITY_PURCHASE",Magic
10602,Travel Core Recipe,progression,CRAFTSANITY,Magic
10603,Haste Elixir Recipe,progression,CRAFTSANITY,Stardew Valley Expanded
10604,Hero Elixir Recipe,progression,CRAFTSANITY,Stardew Valley Expanded
10605,Armor Elixir Recipe,progression,CRAFTSANITY,Stardew Valley Expanded
10606,Neanderthal Skeleton Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10607,Pterodactyl Skeleton L Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10608,Pterodactyl Skeleton M Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10609,Pterodactyl Skeleton R Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10610,T-Rex Skeleton L Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10611,T-Rex Skeleton M Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10612,T-Rex Skeleton R Recipe,progression,CRAFTSANITY,Boarding House and Bus Stop Extension
10701,Resource Pack: 3 Magic Elixir,filler,RESOURCE_PACK,Magic
10702,Resource Pack: 3 Travel Core,filler,RESOURCE_PACK,Magic
10703,Preservation Chamber,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology
10704,Hardwood Preservation Chamber,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology
10705,Resource Pack: 3 Water Shifter,filler,RESOURCE_PACK,Archaeology
10706,Resource Pack: 5 Hardwood Display,filler,RESOURCE_PACK,Archaeology
10707,Resource Pack: 5 Wooden Display,filler,RESOURCE_PACK,Archaeology
10708,Grinder,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology
10709,Ancient Battery Production Station,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL",Archaeology
10710,Hero Elixir,filler,RESOURCE_PACK,Starde 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

1 id name classification groups mod_name
7 19 Glittering Boulder Removed progression COMMUNITY_REWARD
8 20 Minecarts Repair useful COMMUNITY_REWARD
9 21 Bus Repair progression COMMUNITY_REWARD
10 22 Movie Theater Progressive Movie Theater useful progression COMMUNITY_REWARD
11 23 Stardrop progression
12 24 Progressive Backpack progression
13 25 Rusty Sword progression filler WEAPON WEAPON,DEPRECATED
14 26 Leather Boots progression filler FOOTWEAR,MINES_FLOOR_10 FOOTWEAR,DEPRECATED
15 27 Work Boots useful filler FOOTWEAR,MINES_FLOOR_10 FOOTWEAR,DEPRECATED
16 28 Wooden Blade progression filler MINES_FLOOR_10,WEAPON WEAPON,DEPRECATED
17 29 Iron Dirk progression filler MINES_FLOOR_10,WEAPON WEAPON,DEPRECATED
18 30 Wind Spire progression filler MINES_FLOOR_10,WEAPON WEAPON,DEPRECATED
19 31 Femur progression filler MINES_FLOOR_10,WEAPON WEAPON,DEPRECATED
20 32 Steel Smallsword progression filler MINES_FLOOR_20,WEAPON WEAPON,DEPRECATED
21 33 Wood Club progression filler MINES_FLOOR_20,WEAPON WEAPON,DEPRECATED
22 34 Elf Blade progression filler MINES_FLOOR_20,WEAPON WEAPON,DEPRECATED
23 35 37 Glow Ring Slingshot useful filler MINES_FLOOR_20,RING WEAPON,DEPRECATED
24 36 38 Magnet Ring Tundra Boots useful filler MINES_FLOOR_20,RING FOOTWEAR,DEPRECATED
25 37 39 Slingshot Thermal Boots progression filler WEAPON FOOTWEAR,DEPRECATED
26 38 40 Tundra Boots Combat Boots useful filler FOOTWEAR,MINES_FLOOR_50 FOOTWEAR,DEPRECATED
27 39 41 Thermal Boots Silver Saber useful filler FOOTWEAR,MINES_FLOOR_50 WEAPON,DEPRECATED
28 40 42 Combat Boots Pirate's Sword useful filler FOOTWEAR,MINES_FLOOR_50 WEAPON,DEPRECATED
29 41 43 Silver Saber Crystal Dagger progression filler MINES_FLOOR_50,WEAPON WEAPON,DEPRECATED
30 42 44 Pirate's Sword Cutlass progression filler MINES_FLOOR_50,WEAPON WEAPON,DEPRECATED
31 43 45 Crystal Dagger Iron Edge progression filler MINES_FLOOR_60,WEAPON WEAPON,DEPRECATED
32 44 46 Cutlass Burglar's Shank progression filler MINES_FLOOR_60,WEAPON WEAPON,DEPRECATED
33 45 47 Iron Edge Wood Mallet progression filler MINES_FLOOR_60,WEAPON WEAPON,DEPRECATED
34 46 48 Burglar's Shank Master Slingshot progression filler MINES_FLOOR_60,WEAPON WEAPON,DEPRECATED
35 47 49 Wood Mallet Firewalker Boots progression filler MINES_FLOOR_60,WEAPON FOOTWEAR,DEPRECATED
36 48 50 Master Slingshot Dark Boots progression filler WEAPON FOOTWEAR,DEPRECATED
37 49 51 Firewalker Boots Claymore useful filler FOOTWEAR,MINES_FLOOR_80 WEAPON,DEPRECATED
38 50 52 Dark Boots Templar's Blade useful filler FOOTWEAR,MINES_FLOOR_80 WEAPON,DEPRECATED
39 51 53 Claymore Kudgel progression filler MINES_FLOOR_80,WEAPON WEAPON,DEPRECATED
40 52 54 Templar's Blade Shadow Dagger progression filler MINES_FLOOR_80,WEAPON WEAPON,DEPRECATED
41 53 55 Kudgel Obsidian Edge progression filler MINES_FLOOR_80,WEAPON WEAPON,DEPRECATED
42 54 56 Shadow Dagger Tempered Broadsword progression filler MINES_FLOOR_80,WEAPON WEAPON,DEPRECATED
43 55 57 Obsidian Edge Wicked Kris progression filler MINES_FLOOR_90,WEAPON WEAPON,DEPRECATED
44 56 58 Tempered Broadsword Bone Sword progression filler MINES_FLOOR_90,WEAPON WEAPON,DEPRECATED
45 57 59 Wicked Kris Ossified Blade progression filler MINES_FLOOR_90,WEAPON WEAPON,DEPRECATED
46 58 60 Bone Sword Space Boots progression filler MINES_FLOOR_90,WEAPON FOOTWEAR,DEPRECATED
47 59 61 Ossified Blade Crystal Shoes progression filler MINES_FLOOR_90,WEAPON FOOTWEAR,DEPRECATED
48 60 62 Space Boots Steel Falchion useful filler FOOTWEAR,MINES_FLOOR_110 WEAPON,DEPRECATED
49 61 63 Crystal Shoes The Slammer useful filler FOOTWEAR,MINES_FLOOR_110 WEAPON,DEPRECATED
62 Steel Falchion progression MINES_FLOOR_110,WEAPON
63 The Slammer progression MINES_FLOOR_110,WEAPON
50 64 Skull Key progression
51 65 Progressive Hoe progression PROGRESSIVE_TOOLS
52 66 Progressive Pickaxe progression PROGRESSIVE_TOOLS
61 75 Foraging Level progression SKILL_LEVEL_UP
62 76 Mining Level progression SKILL_LEVEL_UP
63 77 Combat Level progression SKILL_LEVEL_UP
64 78 Earth Obelisk progression WIZARD_BUILDING
65 79 Water Obelisk progression WIZARD_BUILDING
66 80 Desert Obelisk progression WIZARD_BUILDING
67 81 Island Obelisk progression GINGER_ISLAND WIZARD_BUILDING,GINGER_ISLAND
68 82 Junimo Hut useful WIZARD_BUILDING
69 83 Gold Clock progression WIZARD_BUILDING
70 84 Progressive Coop progression BUILDING
71 85 Progressive Barn progression BUILDING
72 86 Well useful BUILDING
73 87 Silo progression BUILDING
74 88 Mill progression BUILDING
75 89 Progressive Shed progression BUILDING
76 90 Fish Pond progression BUILDING
77 91 Stable useful BUILDING
78 92 Slime Hutch useful progression BUILDING
79 93 Shipping Bin progression BUILDING
80 94 Beach Bridge progression
81 95 Adventurer's Guild progression DEPRECATED
82 96 Club Card progression
83 97 Magnifying Glass progression
84 98 Bear's Knowledge progression
85 99 Iridium Snake Milk progression useful
86 100 JotPK: Progressive Boots progression ARCADE_MACHINE_BUFFS
87 101 JotPK: Progressive Gun progression ARCADE_MACHINE_BUFFS
88 102 JotPK: Progressive Ammo progression ARCADE_MACHINE_BUFFS
89 103 JotPK: Extra Life progression ARCADE_MACHINE_BUFFS
90 104 JotPK: Increased Drop Rate progression ARCADE_MACHINE_BUFFS
91 105 Junimo Kart: Extra Life progression ARCADE_MACHINE_BUFFS
92 106 Galaxy Sword progression filler GALAXY_WEAPONS,WEAPON WEAPON,DEPRECATED
93 107 Galaxy Dagger progression filler GALAXY_WEAPONS,WEAPON WEAPON,DEPRECATED
94 108 Galaxy Hammer progression filler GALAXY_WEAPONS,WEAPON WEAPON,DEPRECATED
95 109 Movement Speed Bonus progression
96 110 Luck Bonus progression
97 111 Lava Katana progression filler MINES_FLOOR_110,WEAPON WEAPON,DEPRECATED
98 112 Progressive House progression
99 113 Traveling Merchant: Sunday progression TRAVELING_MERCHANT_DAY
100 114 Traveling Merchant: Monday progression TRAVELING_MERCHANT_DAY
103 117 Traveling Merchant: Thursday 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 progression useful
107 121 Traveling Merchant Discount progression useful
108 122 Return Scepter useful
109 123 Progressive Season progression
110 124 Spring progression SEASON
221 235 Quality Bobber Recipe progression SPECIAL_ORDER_BOARD
222 236 Mini-Obelisk Recipe progression SPECIAL_ORDER_BOARD
223 237 Monster Musk Recipe progression SPECIAL_ORDER_BOARD
224 239 Sewing Machine progression useful RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD
225 240 Coffee Maker progression RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD
226 241 Mini-Fridge useful RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD
227 242 Mini-Shipping Bin progression RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD
243 258 Banana Sapling progression GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY
244 259 Mango Sapling progression GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY
245 260 Boat Repair progression GINGER_ISLAND
246 261 Open Professor Snail Cave progression GINGER_ISLAND
247 262 Island North Turtle progression GINGER_ISLAND,WALNUT_PURCHASE
248 263 Island West Turtle progression GINGER_ISLAND,WALNUT_PURCHASE
249 264 Island Farmhouse progression GINGER_ISLAND,WALNUT_PURCHASE
252 267 Dig Site Bridge progression GINGER_ISLAND,WALNUT_PURCHASE
253 268 Island Trader progression GINGER_ISLAND,WALNUT_PURCHASE
254 269 Volcano Bridge progression GINGER_ISLAND,WALNUT_PURCHASE
255 270 Volcano Exit Shortcut progression useful GINGER_ISLAND,WALNUT_PURCHASE
256 271 Island Resort progression GINGER_ISLAND,WALNUT_PURCHASE
257 272 Parrot Express progression GINGER_ISLAND,WALNUT_PURCHASE
258 273 Qi Walnut Room progression GINGER_ISLAND,WALNUT_PURCHASE
259 274 Pineapple Seeds progression GINGER_ISLAND,CROPSANITY
260 275 Taro Tuber progression GINGER_ISLAND,CROPSANITY
261 276 Weather Report useful TV_CHANNEL
262 277 Fortune Teller useful TV_CHANNEL
263 278 Livin' Off The Land useful TV_CHANNEL
264 279 The Queen of Sauce progression TV_CHANNEL
265 280 Fishing Information Broadcasting Service useful TV_CHANNEL
266 281 Sinister Signal useful filler TV_CHANNEL
267 282 Dark Talisman progression
268 283 Ostrich Incubator Recipe progression GINGER_ISLAND
269 284 Cute Baby progression BABY
270 285 Ugly Baby progression BABY
271 286 Deluxe Scarecrow Recipe progression FESTIVAL,RARECROW RARECROW
272 287 Treehouse progression GINGER_ISLAND
273 288 Coffee Bean progression CROPSANITY
274 4001 289 Burnt Progressive Weapon trap progression TRAP WEAPON,WEAPON_GENERIC
275 4002 290 Darkness Progressive Sword trap progression TRAP WEAPON,WEAPON_SWORD
276 4003 291 Frozen Progressive Club trap progression TRAP WEAPON,WEAPON_CLUB
277 4004 292 Jinxed Progressive Dagger trap progression TRAP WEAPON,WEAPON_DAGGER
278 4005 293 Nauseated Progressive Slingshot trap progression TRAP WEAPON,WEAPON_SLINGSHOT
279 4006 294 Slimed Progressive Footwear trap useful TRAP FOOTWEAR
280 4007 295 Weakness Small Glow Ring trap filler TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
281 4008 296 Taxes Glow Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
282 4009 297 Random Teleport Small Magnet Ring trap filler TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
283 4010 298 The Crows Magnet Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
284 4011 299 Monsters Slime Charmer Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
285 4012 300 Entrance Reshuffle Warrior Ring trap useful TRAP,DEPRECATED RING,RESOURCE_PACK,MAXIMUM_ONE
286 4013 301 Debris Vampire Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
287 4014 302 Shuffle Savage Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
288 4015 303 Temporary Winter Ring of Yoba trap useful TRAP,DEPRECATED RING,RESOURCE_PACK,MAXIMUM_ONE
289 4016 304 Pariah Sturdy Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
290 4017 305 Drought Burglar's Ring trap useful TRAP RING,RESOURCE_PACK,MAXIMUM_ONE
291 306 Iridium Band useful RING,RESOURCE_PACK,MAXIMUM_ONE
292 307 Jukebox Ring filler RING,RESOURCE_PACK,MAXIMUM_ONE
293 308 Amethyst Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
294 309 Topaz Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
295 310 Aquamarine Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
296 311 Jade Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
297 312 Emerald Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
298 313 Ruby Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
299 314 Wedding Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
300 315 Crabshell Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
301 316 Napalm Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
302 317 Thorns Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
303 318 Lucky Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
304 319 Hot Java Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
305 320 Protection Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
306 321 Soul Sapper Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
307 322 Phoenix Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
308 323 Immunity Band useful RING,RESOURCE_PACK,MAXIMUM_ONE
309 324 Glowstone Ring useful RING,RESOURCE_PACK,MAXIMUM_ONE
310 325 Fairy Dust Recipe progression
311 326 Heavy Tapper Recipe progression QI_CRAFTING_RECIPE,GINGER_ISLAND
312 327 Hyper Speed-Gro Recipe progression QI_CRAFTING_RECIPE,GINGER_ISLAND
313 328 Deluxe Fertilizer Recipe progression QI_CRAFTING_RECIPE
314 329 Hopper Recipe progression QI_CRAFTING_RECIPE,GINGER_ISLAND
315 330 Magic Bait Recipe progression QI_CRAFTING_RECIPE,GINGER_ISLAND
316 331 Jack-O-Lantern Recipe progression FESTIVAL
317 333 Tub o' Flowers Recipe progression FESTIVAL
318 335 Moonlight Jellies Banner filler FESTIVAL
319 336 Starport Decal filler FESTIVAL
320 337 Golden Egg progression RESOURCE_PACK,RESOURCE_PACK_USEFUL
321 340 Algae Soup Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
322 341 Artichoke Dip Recipe progression CHEFSANITY,CHEFSANITY_QOS
323 342 Autumn's Bounty Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
324 343 Baked Fish Recipe progression CHEFSANITY,CHEFSANITY_QOS
325 344 Banana Pudding Recipe progression CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE
326 345 Bean Hotpot Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
327 346 Blackberry Cobbler Recipe progression CHEFSANITY,CHEFSANITY_QOS
328 347 Blueberry Tart Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
329 348 Bread Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_FRIENDSHIP,CHEFSANITY_PURCHASE
330 349 Bruschetta Recipe progression CHEFSANITY,CHEFSANITY_QOS
331 350 Carp Surprise Recipe progression CHEFSANITY,CHEFSANITY_QOS
332 351 Cheese Cauliflower Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
333 352 Chocolate Cake Recipe progression CHEFSANITY,CHEFSANITY_QOS
334 353 Chowder Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
335 354 Coleslaw Recipe progression CHEFSANITY,CHEFSANITY_QOS
336 355 Complete Breakfast Recipe progression CHEFSANITY,CHEFSANITY_QOS
337 356 Cookies Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
338 357 Crab Cakes Recipe progression CHEFSANITY,CHEFSANITY_QOS
339 358 Cranberry Candy Recipe progression CHEFSANITY,CHEFSANITY_QOS
340 359 Cranberry Sauce Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
341 360 Crispy Bass Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
342 361 Dish O' The Sea Recipe progression CHEFSANITY,CHEFSANITY_SKILL
343 362 Eggplant Parmesan Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
344 363 Escargot Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
345 364 Farmer's Lunch Recipe progression CHEFSANITY,CHEFSANITY_SKILL
346 365 Fiddlehead Risotto Recipe progression CHEFSANITY,CHEFSANITY_QOS
347 366 Fish Stew Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
348 367 Fish Taco Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
349 368 Fried Calamari Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
350 369 Fried Eel Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
351 370 Fried Egg Recipe progression CHEFSANITY_STARTER
352 371 Fried Mushroom Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
353 372 Fruit Salad Recipe progression CHEFSANITY,CHEFSANITY_QOS
354 373 Ginger Ale Recipe progression CHEFSANITY,GINGER_ISLAND,CHEFSANITY_PURCHASE
355 374 Glazed Yams Recipe progression CHEFSANITY,CHEFSANITY_QOS
356 375 Hashbrowns Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE
357 376 Ice Cream Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
358 377 Lobster Bisque Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_FRIENDSHIP
359 378 Lucky Lunch Recipe progression CHEFSANITY,CHEFSANITY_QOS
360 379 Maki Roll Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE
361 380 Mango Sticky Rice Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP,GINGER_ISLAND
362 381 Maple Bar Recipe progression CHEFSANITY,CHEFSANITY_QOS
363 382 Miner's Treat Recipe progression CHEFSANITY,CHEFSANITY_SKILL
364 383 Omelet Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE
365 384 Pale Broth Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
366 385 Pancakes Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE
367 386 Parsnip Soup Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
368 387 Pepper Poppers Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
369 388 Pink Cake Recipe progression CHEFSANITY,CHEFSANITY_QOS
370 389 Pizza Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE
371 390 Plum Pudding Recipe progression CHEFSANITY,CHEFSANITY_QOS
372 391 Poi Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP,GINGER_ISLAND
373 392 Poppyseed Muffin Recipe progression CHEFSANITY,CHEFSANITY_QOS
374 393 Pumpkin Pie Recipe progression CHEFSANITY,CHEFSANITY_QOS
375 394 Pumpkin Soup Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
376 395 Radish Salad Recipe progression CHEFSANITY,CHEFSANITY_QOS
377 396 Red Plate Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
378 397 Rhubarb Pie Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
379 398 Rice Pudding Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
380 399 Roasted Hazelnuts Recipe progression CHEFSANITY,CHEFSANITY_QOS
381 400 Roots Platter Recipe progression CHEFSANITY,CHEFSANITY_SKILL
382 401 Salad Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
383 402 Salmon Dinner Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
384 403 Sashimi Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
385 404 Seafoam Pudding Recipe progression CHEFSANITY,CHEFSANITY_SKILL
386 405 Shrimp Cocktail Recipe progression CHEFSANITY,CHEFSANITY_QOS
387 406 Spaghetti Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
388 407 Spicy Eel Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
389 408 Squid Ink Ravioli Recipe progression CHEFSANITY,CHEFSANITY_SKILL
390 409 Stir Fry Recipe progression CHEFSANITY,CHEFSANITY_QOS
391 410 Strange Bun Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
392 411 Stuffing Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
393 412 Super Meal Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
394 413 Survival Burger Recipe progression CHEFSANITY,CHEFSANITY_SKILL
395 414 Tom Kha Soup Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP
396 415 Tortilla Recipe progression CHEFSANITY,CHEFSANITY_QOS,CHEFSANITY_PURCHASE
397 416 Triple Shot Espresso Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE
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 425 Gate Recipe progression CRAFTSANITY
402 426 Wood Fence Recipe progression CRAFTSANITY
403 427 Deluxe Retaining Soil Recipe progression CRAFTSANITY,GINGER_ISLAND
404 428 Grass Starter Recipe progression CRAFTSANITY
405 429 Wood Floor Recipe progression CRAFTSANITY
406 430 Rustic Plank Floor Recipe progression CRAFTSANITY
407 431 Straw Floor Recipe progression CRAFTSANITY
408 432 Weathered Floor Recipe progression CRAFTSANITY
409 433 Crystal Floor Recipe progression CRAFTSANITY
410 434 Stone Floor Recipe progression CRAFTSANITY
411 435 Stone Walkway Floor Recipe progression CRAFTSANITY
412 436 Brick Floor Recipe progression CRAFTSANITY
413 437 Wood Path Recipe progression CRAFTSANITY
414 438 Gravel Path Recipe progression CRAFTSANITY
415 439 Cobblestone Path Recipe progression CRAFTSANITY
416 440 Stepping Stone Path Recipe progression CRAFTSANITY
417 441 Crystal Path Recipe progression CRAFTSANITY
418 442 Wedding Ring Recipe progression CRAFTSANITY
419 443 Warp Totem: Desert Recipe progression CRAFTSANITY
420 444 Warp Totem: Island Recipe progression CRAFTSANITY,GINGER_ISLAND
421 445 Torch Recipe progression CRAFTSANITY
422 446 Campfire Recipe progression CRAFTSANITY
423 447 Wooden Brazier Recipe progression CRAFTSANITY
424 448 Stone Brazier Recipe progression CRAFTSANITY
425 449 Gold Brazier Recipe progression CRAFTSANITY
426 450 Carved Brazier Recipe progression CRAFTSANITY
427 451 Stump Brazier Recipe progression CRAFTSANITY
428 452 Barrel Brazier Recipe progression CRAFTSANITY
429 453 Skull Brazier Recipe progression CRAFTSANITY
430 454 Marble Brazier Recipe progression CRAFTSANITY
431 455 Wood Lamp-post Recipe progression CRAFTSANITY
432 456 Iron Lamp-post Recipe progression CRAFTSANITY
433 457 Furnace Recipe progression CRAFTSANITY
434 458 Wicked Statue Recipe progression CRAFTSANITY
435 459 Chest Recipe progression CRAFTSANITY
436 460 Wood Sign Recipe progression CRAFTSANITY
437 461 Stone Sign Recipe progression CRAFTSANITY
438 469 Railroad Boulder Removed progression
439 470 Fruit Bats progression
440 471 Mushroom Boxes progression
441 475 The Gateway Gazette progression TV_CHANNEL
442 4001 Burnt Trap trap TRAP
443 4002 Darkness Trap trap TRAP
444 4003 Frozen Trap trap TRAP
445 4004 Jinxed Trap trap TRAP
446 4005 Nauseated Trap trap TRAP
447 4006 Slimed Trap trap TRAP
448 4007 Weakness Trap trap TRAP
449 4008 Taxes Trap trap TRAP
450 4009 Random Teleport Trap trap TRAP
451 4010 The Crows Trap trap TRAP
452 4011 Monsters Trap trap TRAP
453 4012 Entrance Reshuffle Trap trap TRAP,DEPRECATED
454 4013 Debris Trap trap TRAP
455 4014 Shuffle Trap trap TRAP
456 4015 Temporary Winter Trap trap TRAP,DEPRECATED
457 4016 Pariah Trap trap TRAP
458 4017 Drought Trap trap TRAP
459 4018 Time Flies Trap trap TRAP
460 4019 Babies Trap trap TRAP
461 4020 Meow Trap trap TRAP
462 4021 Bark Trap trap TRAP
463 4022 Depression Trap trap TRAP,DEPRECATED
464 4023 Benjamin Budton Trap trap TRAP
465 4024 Inflation Trap trap TRAP
466 4025 Bomb Trap trap TRAP
467 5000 Resource Pack: 500 Money useful BASE_RESOURCE,RESOURCE_PACK
468 5001 Resource Pack: 1000 Money useful BASE_RESOURCE,RESOURCE_PACK
469 5002 Resource Pack: 1500 Money useful BASE_RESOURCE,RESOURCE_PACK
510 5043 Resource Pack: 7 Warp Totem: Farm filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM
511 5044 Resource Pack: 9 Warp Totem: Farm filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM
512 5045 Resource Pack: 10 Warp Totem: Farm filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM
513 5046 Resource Pack: 1 Warp Totem: Island filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND
514 5047 Resource Pack: 3 Warp Totem: Island filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND
515 5048 Resource Pack: 5 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND
516 5049 Resource Pack: 7 Warp Totem: Island filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND
517 5050 Resource Pack: 9 Warp Totem: Island filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND
518 5051 Resource Pack: 10 Warp Totem: Island filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM DEPRECATED,RESOURCE_PACK,WARP_TOTEM,GINGER_ISLAND
519 5052 Resource Pack: 1 Warp Totem: Mountains filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM
520 5053 Resource Pack: 3 Warp Totem: Mountains filler DEPRECATED,RESOURCE_PACK,WARP_TOTEM
521 5054 Resource Pack: 5 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
666 5199 Friendship Bonus (2 <3) useful FRIENDSHIP_PACK,COMMUNITY_REWARD
667 5200 Friendship Bonus (3 <3) useful DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK
668 5201 Friendship Bonus (4 <3) useful DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK
669 5202 30 Qi Gems 15 Qi Gems useful progression GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL
670 5203 Solar Panel filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
671 5204 Geode Crusher filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
672 5205 Farm Computer filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
681 5214 Quality Sprinkler filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
682 5215 Iridium Sprinkler filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
683 5216 Scarecrow filler RESOURCE_PACK
684 5217 Deluxe Scarecrow filler useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
685 5218 Furnace filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
686 5219 Charcoal Kiln filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
687 5220 Lightning Rod filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
688 5221 Resource Pack: 5000 Money useful BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
689 5222 Resource Pack: 10000 Money useful BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
690 5223 Junimo Chest filler EXACTLY_TWO,RESOURCE_PACK
691 5224 Horse Flute filler useful MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
692 5225 Pierre's Missing Stocklist filler useful MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
693 5226 Hopper filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
694 5227 Enricher filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
695 5228 Pressure Nozzle filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
696 5229 Deconstructor filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
697 5230 Key To The Town filler useful MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
698 5231 Galaxy Soul filler GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL
699 5232 Mushroom Tree Seed filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
700 5233 Resource Pack: 20 Magic Bait filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
701 5234 Resource Pack: 10 Qi Seasoning filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
702 5235 Mr. Qi's Hat filler MAXIMUM_ONE,RESOURCE_PACK
703 5236 Aquatic Sanctuary filler RESOURCE_PACK
5237 Heavy Tapper Recipe filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
5238 Hyper Speed-Gro Recipe filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
5239 Deluxe Fertilizer Recipe filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
5240 Hopper Recipe filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
5241 Magic Bait Recipe filler MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
704 5242 Exotic Double Bed filler RESOURCE_PACK
705 5243 Resource Pack: 2 Qi Gem filler GINGER_ISLAND,RESOURCE_PACK GINGER_ISLAND,RESOURCE_PACK,DEPRECATED
5244 Golden Egg filler RESOURCE_PACK,RESOURCE_PACK_USEFUL
706 5245 Golden Walnut filler RESOURCE_PACK,RESOURCE_PACK_USEFUL,GINGER_ISLAND
5246 Fairy Dust Recipe useful MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
707 5247 Fairy Dust useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
708 5248 Seed Maker useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
709 5249 Keg useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
719 5259 Worm Bin useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
720 5260 Tapper useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
721 5261 Heavy Tapper useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
722 5262 Slime Incubator useful RESOURCE_PACK
723 5263 Slime Egg-Press useful RESOURCE_PACK
724 5264 Crystalarium useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
725 5265 Ostrich Incubator useful MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL
726 5266 Resource Pack: 5 Staircase filler RESOURCE_PACK
727 5267 Auto-Petter useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
728 5268 Auto-Grabber useful RESOURCE_PACK,RESOURCE_PACK_USEFUL
729 10001 Luck Level progression SKILL_LEVEL_UP Luck Skill
730 10002 Magic Level progression SKILL_LEVEL_UP Magic
731 10003 Socializing Level progression SKILL_LEVEL_UP Socializing Skill
739 10011 Spell: Water progression MAGIC_SPELL Magic
740 10012 Spell: Blink progression MAGIC_SPELL Magic
741 10013 Spell: Evac useful MAGIC_SPELL Magic
742 10014 Spell: Haste filler useful MAGIC_SPELL Magic
743 10015 Spell: Heal progression MAGIC_SPELL Magic
744 10016 Spell: Buff useful MAGIC_SPELL Magic
745 10017 Spell: Shockwave progression MAGIC_SPELL Magic
746 10018 Spell: Fireball progression MAGIC_SPELL Magic
747 10019 Spell: Frostbite Spell: Frostbolt progression MAGIC_SPELL Magic
748 10020 Spell: Teleport progression MAGIC_SPELL Magic
749 10021 Spell: Lantern filler useful MAGIC_SPELL Magic
750 10022 Spell: Tendrils progression MAGIC_SPELL Magic
751 10023 Spell: Photosynthesis useful MAGIC_SPELL Magic
752 10024 Spell: Descend progression MAGIC_SPELL Magic
755 10027 Spell: Lucksteal useful MAGIC_SPELL Magic
756 10028 Spell: Spirit progression MAGIC_SPELL Magic
757 10029 Spell: Rewind useful MAGIC_SPELL Magic
758 10030 Pendant of Community progression DeepWoods
759 10031 Pendant of Elders progression DeepWoods
760 10032 Pendant of Depths progression DeepWoods
761 10101 Juna <3 progression FRIENDSANITY Juna - Roommate NPC
762 10102 Jasper <3 progression FRIENDSANITY Professor Jasper Thomas
763 10103 Alec <3 progression FRIENDSANITY Alec Revisited
769 10109 Delores <3 progression FRIENDSANITY Delores - Custom NPC
770 10110 Ayeisha <3 progression FRIENDSANITY Ayeisha - The Postal Worker (Custom NPC)
771 10111 Riley <3 progression FRIENDSANITY Custom NPC - Riley
772 10112 Claire <3 progression FRIENDSANITY Stardew Valley Expanded
773 10113 Lance <3 progression FRIENDSANITY,GINGER_ISLAND Stardew Valley Expanded
774 10114 Olivia <3 progression FRIENDSANITY Stardew Valley Expanded
775 10115 Sophia <3 progression FRIENDSANITY Stardew Valley Expanded
776 10116 Victor <3 progression FRIENDSANITY Stardew Valley Expanded
777 10117 Andy <3 progression FRIENDSANITY Stardew Valley Expanded
778 10118 Apples <3 progression FRIENDSANITY Stardew Valley Expanded
779 10119 Gunther <3 progression FRIENDSANITY Stardew Valley Expanded
780 10120 Martin <3 progression FRIENDSANITY Stardew Valley Expanded
781 10121 Marlon <3 progression FRIENDSANITY Stardew Valley Expanded
782 10122 Morgan <3 progression FRIENDSANITY Stardew Valley Expanded
783 10123 Scarlett <3 progression FRIENDSANITY Stardew Valley Expanded
784 10124 Susan <3 progression FRIENDSANITY Stardew Valley Expanded
785 10125 Morris <3 progression FRIENDSANITY Stardew Valley Expanded
786 10126 Alecto <3 progression FRIENDSANITY Alecto the Witch
787 10127 Zic <3 progression FRIENDSANITY Distant Lands - Witch Swamp Overhaul
788 10128 Lacey <3 progression FRIENDSANITY Hat Mouse Lacey
789 10129 Gregory <3 progression FRIENDSANITY Boarding House and Bus Stop Extension
790 10130 Sheila <3 progression FRIENDSANITY Boarding House and Bus Stop Extension
791 10131 Joel <3 progression FRIENDSANITY Boarding House and Bus Stop Extension
792 10301 Progressive Woods Obelisk Sigils progression DeepWoods
793 10302 Progressive Skull Cavern Elevator progression Skull Cavern Elevator
794 10401 Baked Berry Oatmeal Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
795 10402 Big Bark Burger Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
796 10403 Flower Cookie Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
797 10404 Frog Legs Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
798 10405 Glazed Butterfish Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
799 10406 Mixed Berry Pie Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
800 10407 Mushroom Berry Rice Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
801 10408 Seaweed Salad Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
802 10409 Void Delight Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
803 10410 Void Salmon Sushi Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Stardew Valley Expanded
804 10411 Mushroom Kebab Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
805 10412 Crayfish Soup Recipe progression Distant Lands - Witch Swamp Overhaul
806 10413 Pemmican Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
807 10414 Void Mint Tea Recipe progression CHEFSANITY,CHEFSANITY_FRIENDSHIP Distant Lands - Witch Swamp Overhaul
808 10415 Ginger Tincture Recipe progression GINGER_ISLAND Distant Lands - Witch Swamp Overhaul
809 10416 Special Pumpkin Soup Recipe progression Boarding House and Bus Stop Extension
810 10450 Void Mint Seeds progression DEPRECATED Distant Lands - Witch Swamp Overhaul
811 10451 Vile Ancient Fruit Seeds progression DEPRECATED Distant Lands - Witch Swamp Overhaul
812 10501 Marlon's Boat Paddle progression GINGER_ISLAND Stardew Valley Expanded
813 10502 Diamond Wand filler WEAPON,DEPRECATED Stardew Valley Expanded
814 10503 Iridium Bomb progression Stardew Valley Expanded
815 10504 Void Spirit Peace Agreement useful GINGER_ISLAND Stardew Valley Expanded
816 10505 Kittyfish Spell progression Stardew Valley Expanded
817 10506 Nexus: Adventurer's Guild Runes progression MOD_WARP Stardew Valley Expanded
818 10507 Nexus: Junimo Woods Runes progression MOD_WARP Stardew Valley Expanded
819 10508 Nexus: Aurora Vineyard Runes progression MOD_WARP Stardew Valley Expanded
820 10509 Nexus: Sprite Spring Runes progression MOD_WARP Stardew Valley Expanded
821 10510 Nexus: Outpost Runes progression MOD_WARP Stardew Valley Expanded
822 10511 Nexus: Farm Runes progression MOD_WARP Stardew Valley Expanded
823 10512 Nexus: Wizard Runes progression MOD_WARP Stardew Valley Expanded
824 10513 Fable Reef Portal progression GINGER_ISLAND Stardew Valley Expanded
825 10514 Tempered Galaxy Sword filler WEAPON,DEPRECATED Stardew Valley Expanded
826 10515 Tempered Galaxy Dagger filler WEAPON,DEPRECATED Stardew Valley Expanded
827 10516 Tempered Galaxy Hammer filler WEAPON,DEPRECATED Stardew Valley Expanded
828 10517 Grandpa's Shed progression Stardew Valley Expanded
829 10518 Aurora Vineyard Tablet progression Stardew Valley Expanded
830 10519 Scarlett's Job Offer progression Stardew Valley Expanded
831 10520 Morgan's Schooling progression Stardew Valley Expanded
832 10601 Magic Elixir Recipe progression CHEFSANITY,CHEFSANITY_PURCHASE Magic
833 10602 Travel Core Recipe progression CRAFTSANITY Magic
834 10603 Haste Elixir Recipe progression CRAFTSANITY Stardew Valley Expanded
835 10604 Hero Elixir Recipe progression CRAFTSANITY Stardew Valley Expanded
836 10605 Armor Elixir Recipe progression CRAFTSANITY Stardew Valley Expanded
837 10606 Neanderthal Skeleton Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
838 10607 Pterodactyl Skeleton L Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
839 10608 Pterodactyl Skeleton M Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
840 10609 Pterodactyl Skeleton R Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
841 10610 T-Rex Skeleton L Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
842 10611 T-Rex Skeleton M Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
843 10612 T-Rex Skeleton R Recipe progression CRAFTSANITY Boarding House and Bus Stop Extension
844 10701 Resource Pack: 3 Magic Elixir filler RESOURCE_PACK Magic
845 10702 Resource Pack: 3 Travel Core filler RESOURCE_PACK Magic
846 10703 Preservation Chamber filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
847 10704 Hardwood Preservation Chamber filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
848 10705 Resource Pack: 3 Water Shifter filler RESOURCE_PACK Archaeology
849 10706 Resource Pack: 5 Hardwood Display filler RESOURCE_PACK Archaeology
850 10707 Resource Pack: 5 Wooden Display filler RESOURCE_PACK Archaeology
851 10708 Grinder filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
852 10709 Ancient Battery Production Station filler RESOURCE_PACK,RESOURCE_PACK_USEFUL Archaeology
853 10710 Hero Elixir filler RESOURCE_PACK Starde Valley Expanded
854 10711 Aegis Elixir filler RESOURCE_PACK Stardew Valley Expanded
855 10712 Haste Elixir filler RESOURCE_PACK Stardew Valley Expanded
856 10713 Lightning Elixir filler RESOURCE_PACK Stardew Valley Expanded
857 10714 Armor Elixir filler RESOURCE_PACK Stardew Valley Expanded
858 10715 Gravity Elixir filler RESOURCE_PACK Stardew Valley Expanded
859 10716 Barbarian Elixir filler RESOURCE_PACK Stardew Valley Expanded

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,173 @@
class Monster:
duggy = "Duggy"
blue_slime = "Blue Slime"
pepper_rex = "Pepper Rex"
stone_golem = "Stone Golem"
from dataclasses import dataclass
from typing import List, Tuple, Dict, Set, Callable
from ..mods.mod_data import ModNames
from ..mods.mod_monster_locations import modded_monsters_locations
from ..strings.monster_names import Monster, MonsterCategory
from ..strings.performance_names import Performance
from ..strings.region_names import Region
frozen_monsters = (Monster.blue_slime,)
@dataclass(frozen=True)
class StardewMonster:
name: str
category: str
locations: Tuple[str]
difficulty: str
def __repr__(self):
return f"{self.name} [{self.category}] (Locations: {self.locations} |" \
f" Difficulty: {self.difficulty}) |"
slime_hutch = (Region.slime_hutch,)
mines_floor_20 = (Region.mines_floor_20,)
mines_floor_60 = (Region.mines_floor_60,)
mines_floor_100 = (Region.mines_floor_100,)
dangerous_mines_20 = (Region.dangerous_mines_20,)
dangerous_mines_60 = (Region.dangerous_mines_60,)
dangerous_mines_100 = (Region.dangerous_mines_100,)
quarry_mine = (Region.quarry_mine,)
mutant_bug_lair = (Region.mutant_bug_lair,)
skull_cavern = (Region.skull_cavern_25,)
skull_cavern_high = (Region.skull_cavern_75,)
skull_cavern_dangerous = (Region.dangerous_skull_cavern,)
tiger_slime_grove = (Region.island_west,)
volcano = (Region.volcano_floor_5,)
volcano_high = (Region.volcano_floor_10,)
all_monsters: List[StardewMonster] = []
monster_modifications_by_mod: Dict[str, Dict[str, Callable[[str, StardewMonster], StardewMonster]]] = {}
def create_monster(name: str, category: str, locations: Tuple[str, ...], difficulty: str) -> StardewMonster:
monster = StardewMonster(name, category, locations, difficulty)
all_monsters.append(monster)
return monster
def update_monster_locations(mod_name: str, monster: StardewMonster):
new_locations = modded_monsters_locations[mod_name][monster.name]
total_locations = tuple(sorted(set(monster.locations + new_locations)))
return StardewMonster(monster.name, monster.category, total_locations, monster.difficulty)
def register_monster_modification(mod_name: str, monster: StardewMonster, modification_function):
if mod_name not in monster_modifications_by_mod:
monster_modifications_by_mod[mod_name] = {}
monster_modifications_by_mod[mod_name][monster.name] = modification_function
green_slime = create_monster(Monster.green_slime, MonsterCategory.slime, mines_floor_20, Performance.basic)
blue_slime = create_monster(Monster.blue_slime, MonsterCategory.slime, mines_floor_60, Performance.decent)
red_slime = create_monster(Monster.red_slime, MonsterCategory.slime, mines_floor_100, Performance.good)
purple_slime = create_monster(Monster.purple_slime, MonsterCategory.slime, skull_cavern, Performance.great)
yellow_slime = create_monster(Monster.yellow_slime, MonsterCategory.slime, skull_cavern_high, Performance.galaxy)
black_slime = create_monster(Monster.black_slime, MonsterCategory.slime, slime_hutch, Performance.decent)
copper_slime = create_monster(Monster.copper_slime, MonsterCategory.slime, quarry_mine, Performance.decent)
iron_slime = create_monster(Monster.iron_slime, MonsterCategory.slime, quarry_mine, Performance.good)
tiger_slime = create_monster(Monster.tiger_slime, MonsterCategory.slime, tiger_slime_grove, Performance.galaxy)
shadow_shaman = create_monster(Monster.shadow_shaman, MonsterCategory.void_spirits, mines_floor_100, Performance.good)
shadow_shaman_dangerous = create_monster(Monster.shadow_shaman_dangerous, MonsterCategory.void_spirits, dangerous_mines_100, Performance.galaxy)
shadow_brute = create_monster(Monster.shadow_brute, MonsterCategory.void_spirits, mines_floor_100, Performance.good)
shadow_brute_dangerous = create_monster(Monster.shadow_brute_dangerous, MonsterCategory.void_spirits, dangerous_mines_100, Performance.galaxy)
shadow_sniper = create_monster(Monster.shadow_sniper, MonsterCategory.void_spirits, dangerous_mines_100, Performance.galaxy)
bat = create_monster(Monster.bat, MonsterCategory.bats, mines_floor_20, Performance.basic)
bat_dangerous = create_monster(Monster.bat_dangerous, MonsterCategory.bats, dangerous_mines_20, Performance.galaxy)
frost_bat = create_monster(Monster.frost_bat, MonsterCategory.bats, mines_floor_60, Performance.decent)
frost_bat_dangerous = create_monster(Monster.frost_bat_dangerous, MonsterCategory.bats, dangerous_mines_60, Performance.galaxy)
lava_bat = create_monster(Monster.lava_bat, MonsterCategory.bats, mines_floor_100, Performance.good)
iridium_bat = create_monster(Monster.iridium_bat, MonsterCategory.bats, skull_cavern_high, Performance.great)
skeleton = create_monster(Monster.skeleton, MonsterCategory.skeletons, mines_floor_100, Performance.good)
skeleton_dangerous = create_monster(Monster.skeleton_dangerous, MonsterCategory.skeletons, dangerous_mines_100, Performance.galaxy)
skeleton_mage = create_monster(Monster.skeleton_mage, MonsterCategory.skeletons, dangerous_mines_100, Performance.galaxy)
bug = create_monster(Monster.bug, MonsterCategory.cave_insects, mines_floor_20, Performance.basic)
bug_dangerous = create_monster(Monster.bug_dangerous, MonsterCategory.cave_insects, dangerous_mines_20, Performance.galaxy)
cave_fly = create_monster(Monster.cave_fly, MonsterCategory.cave_insects, mines_floor_20, Performance.basic)
cave_fly_dangerous = create_monster(Monster.cave_fly_dangerous, MonsterCategory.cave_insects, dangerous_mines_60, Performance.galaxy)
grub = create_monster(Monster.grub, MonsterCategory.cave_insects, mines_floor_20, Performance.basic)
grub_dangerous = create_monster(Monster.grub_dangerous, MonsterCategory.cave_insects, dangerous_mines_60, Performance.galaxy)
mutant_fly = create_monster(Monster.mutant_fly, MonsterCategory.cave_insects, mutant_bug_lair, Performance.good)
mutant_grub = create_monster(Monster.mutant_grub, MonsterCategory.cave_insects, mutant_bug_lair, Performance.good)
armored_bug = create_monster(Monster.armored_bug, MonsterCategory.cave_insects, skull_cavern, Performance.basic) # Requires 'Bug Killer' enchantment
armored_bug_dangerous = create_monster(Monster.armored_bug_dangerous, MonsterCategory.cave_insects, skull_cavern,
Performance.good) # Requires 'Bug Killer' enchantment
duggy = create_monster(Monster.duggy, MonsterCategory.duggies, mines_floor_20, Performance.basic)
duggy_dangerous = create_monster(Monster.duggy_dangerous, MonsterCategory.duggies, dangerous_mines_20, Performance.great)
magma_duggy = create_monster(Monster.magma_duggy, MonsterCategory.duggies, volcano, Performance.galaxy)
dust_sprite = create_monster(Monster.dust_sprite, MonsterCategory.dust_sprites, mines_floor_60, Performance.basic)
dust_sprite_dangerous = create_monster(Monster.dust_sprite_dangerous, MonsterCategory.dust_sprites, dangerous_mines_60, Performance.great)
rock_crab = create_monster(Monster.rock_crab, MonsterCategory.rock_crabs, mines_floor_20, Performance.basic)
rock_crab_dangerous = create_monster(Monster.rock_crab_dangerous, MonsterCategory.rock_crabs, dangerous_mines_20, Performance.great)
lava_crab = create_monster(Monster.lava_crab, MonsterCategory.rock_crabs, mines_floor_100, Performance.good)
lava_crab_dangerous = create_monster(Monster.lava_crab_dangerous, MonsterCategory.rock_crabs, dangerous_mines_100, Performance.galaxy)
iridium_crab = create_monster(Monster.iridium_crab, MonsterCategory.rock_crabs, skull_cavern, Performance.great)
mummy = create_monster(Monster.mummy, MonsterCategory.mummies, skull_cavern, Performance.great) # Requires bombs or "Crusader" enchantment
mummy_dangerous = create_monster(Monster.mummy_dangerous, MonsterCategory.mummies, skull_cavern_dangerous,
Performance.maximum) # Requires bombs or "Crusader" enchantment
pepper_rex = create_monster(Monster.pepper_rex, MonsterCategory.pepper_rex, skull_cavern, Performance.great)
serpent = create_monster(Monster.serpent, MonsterCategory.serpents, skull_cavern, Performance.galaxy)
royal_serpent = create_monster(Monster.royal_serpent, MonsterCategory.serpents, skull_cavern_dangerous, Performance.maximum)
magma_sprite = create_monster(Monster.magma_sprite, MonsterCategory.magma_sprites, volcano, Performance.galaxy)
magma_sparker = create_monster(Monster.magma_sparker, MonsterCategory.magma_sprites, volcano_high, Performance.galaxy)
register_monster_modification(ModNames.sve, shadow_brute_dangerous, update_monster_locations)
register_monster_modification(ModNames.sve, shadow_sniper, update_monster_locations)
register_monster_modification(ModNames.sve, shadow_shaman_dangerous, update_monster_locations)
register_monster_modification(ModNames.sve, mummy_dangerous, update_monster_locations)
register_monster_modification(ModNames.sve, royal_serpent, update_monster_locations)
register_monster_modification(ModNames.sve, skeleton_dangerous, update_monster_locations)
register_monster_modification(ModNames.sve, skeleton_mage, update_monster_locations)
register_monster_modification(ModNames.sve, dust_sprite_dangerous, update_monster_locations)
register_monster_modification(ModNames.deepwoods, shadow_brute, update_monster_locations)
register_monster_modification(ModNames.deepwoods, cave_fly, update_monster_locations)
register_monster_modification(ModNames.deepwoods, green_slime, update_monster_locations)
register_monster_modification(ModNames.boarding_house, pepper_rex, update_monster_locations)
register_monster_modification(ModNames.boarding_house, shadow_brute, update_monster_locations)
register_monster_modification(ModNames.boarding_house, iridium_bat, update_monster_locations)
register_monster_modification(ModNames.boarding_house, frost_bat, update_monster_locations)
register_monster_modification(ModNames.boarding_house, cave_fly, update_monster_locations)
register_monster_modification(ModNames.boarding_house, bat, update_monster_locations)
register_monster_modification(ModNames.boarding_house, grub, update_monster_locations)
register_monster_modification(ModNames.boarding_house, bug, update_monster_locations)
def all_monsters_by_name_given_mods(mods: Set[str]) -> Dict[str, StardewMonster]:
monsters_by_name = {}
for monster in all_monsters:
current_monster = monster
for mod in monster_modifications_by_mod:
if mod not in mods or monster.name not in monster_modifications_by_mod[mod]:
continue
modification_function = monster_modifications_by_mod[mod][monster.name]
current_monster = modification_function(mod, current_monster)
monsters_by_name[monster.name] = current_monster
return monsters_by_name
def all_monsters_by_category_given_mods(mods: Set[str]) -> Dict[str, Tuple[StardewMonster, ...]]:
monsters_by_category = {}
for monster in all_monsters:
current_monster = monster
for mod in monster_modifications_by_mod:
if mod not in mods or monster.name not in monster_modifications_by_mod[mod]:
continue
modification_function = monster_modifications_by_mod[mod][monster.name]
current_monster = modification_function(mod, current_monster)
if current_monster.category not in monsters_by_category:
monsters_by_category[monster.category] = ()
monsters_by_category[current_monster.category] = monsters_by_category[current_monster.category] + (current_monster,)
return monsters_by_category

View File

@ -3,23 +3,24 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import List, Tuple, Union, Optional
from . import common_data as common
from .game_item import GameItem
from .monster_data import Monster
from ..strings.monster_names import Monster
from ..strings.fish_names import WaterChest
from ..strings.forageable_names import Forageable
from ..strings.metal_names import Mineral, Artifact, Fossil
from ..strings.region_names import Region
from ..strings.geode_names import Geode
@dataclass(frozen=True)
class MuseumItem(GameItem):
class MuseumItem:
item_name: str
locations: Tuple[str, ...]
geodes: Tuple[str, ...]
monsters: Tuple[str, ...]
difficulty: float
@staticmethod
def of(name: str,
item_id: int,
def of(item_name: str,
difficulty: float,
locations: Union[str, Tuple[str, ...]],
geodes: Union[str, Tuple[str, ...]],
@ -33,10 +34,10 @@ class MuseumItem(GameItem):
if isinstance(monsters, str):
monsters = (monsters,)
return MuseumItem(name, item_id, locations, geodes, monsters, difficulty)
return MuseumItem(item_name, locations, geodes, monsters, difficulty)
def __repr__(self):
return f"{self.name} [{self.item_id}] (Locations: {self.locations} |" \
return f"{self.item_name} (Locations: {self.locations} |" \
f" Geodes: {self.geodes} |" \
f" Monsters: {self.monsters}) "
@ -50,20 +51,18 @@ all_museum_items: List[MuseumItem] = []
def create_artifact(name: str,
item_id: int,
difficulty: float,
locations: Union[str, Tuple[str, ...]] = (),
geodes: Union[str, Tuple[str, ...]] = (),
monsters: Union[str, Tuple[str, ...]] = ()) -> MuseumItem:
artifact_item = MuseumItem.of(name, item_id, difficulty, locations, geodes, monsters)
artifact_item = MuseumItem.of(name, difficulty, locations, geodes, monsters)
all_museum_artifacts.append(artifact_item)
all_museum_items.append(artifact_item)
return artifact_item
def create_mineral(name: str,
item_id: int,
locations: Union[str, Tuple[str, ...]],
locations: Union[str, Tuple[str, ...]] = (),
geodes: Union[str, Tuple[str, ...]] = (),
monsters: Union[str, Tuple[str, ...]] = (),
difficulty: Optional[float] = None) -> MuseumItem:
@ -78,212 +77,207 @@ def create_mineral(name: str,
if "Omni Geode" in geodes:
difficulty += 31.0 / 2750.0 * 100
mineral_item = MuseumItem.of(name, item_id, difficulty, locations, geodes, monsters)
mineral_item = MuseumItem.of(name, difficulty, locations, geodes, monsters)
all_museum_minerals.append(mineral_item)
all_museum_items.append(mineral_item)
return mineral_item
class Artifact:
dwarf_scroll_i = create_artifact("Dwarf Scroll I", 96, 5.6, Region.mines_floor_20,
dwarf_scroll_i = create_artifact("Dwarf Scroll I", 5.6, Region.mines_floor_20,
monsters=unlikely)
dwarf_scroll_ii = create_artifact("Dwarf Scroll II", 97, 3, Region.mines_floor_20,
dwarf_scroll_ii = create_artifact("Dwarf Scroll II", 3, Region.mines_floor_20,
monsters=unlikely)
dwarf_scroll_iii = create_artifact("Dwarf Scroll III", 98, 7.5, Region.mines_floor_60,
dwarf_scroll_iii = create_artifact("Dwarf Scroll III", 7.5, Region.mines_floor_60,
monsters=Monster.blue_slime)
dwarf_scroll_iv = create_artifact("Dwarf Scroll IV", 99, 4, Region.mines_floor_100)
chipped_amphora = create_artifact("Chipped Amphora", 100, 6.7, Region.town,
dwarf_scroll_iv = create_artifact("Dwarf Scroll IV", 4, Region.mines_floor_100)
chipped_amphora = create_artifact("Chipped Amphora", 6.7, Region.town,
geodes=Geode.artifact_trove)
arrowhead = create_artifact("Arrowhead", 101, 8.5, (Region.mountain, Region.forest, Region.bus_stop),
arrowhead = create_artifact("Arrowhead", 8.5, (Region.mountain, Region.forest, Region.bus_stop),
geodes=Geode.artifact_trove)
ancient_doll = create_artifact("Ancient Doll", 103, 13.1, (Region.mountain, Region.forest, Region.bus_stop),
geodes=(Geode.artifact_trove, common.fishing_chest))
elvish_jewelry = create_artifact("Elvish Jewelry", 104, 5.3, Region.forest,
geodes=(Geode.artifact_trove, common.fishing_chest))
chewing_stick = create_artifact("Chewing Stick", 105, 10.3, (Region.mountain, Region.forest, Region.town),
geodes=(Geode.artifact_trove, common.fishing_chest))
ornamental_fan = create_artifact("Ornamental Fan", 106, 7.4, (Region.beach, Region.forest, Region.town),
geodes=(Geode.artifact_trove, common.fishing_chest))
dinosaur_egg = create_artifact("Dinosaur Egg", 107, 11.4, (Region.mountain, Region.skull_cavern),
geodes=common.fishing_chest,
ancient_doll = create_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))
chewing_stick = create_artifact("Chewing Stick", 10.3, (Region.mountain, Region.forest, Region.town),
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,
monsters=Monster.pepper_rex)
rare_disc = create_artifact("Rare Disc", 108, 5.6, Region.stardew_valley,
geodes=(Geode.artifact_trove, common.fishing_chest),
rare_disc = create_artifact("Rare Disc", 5.6, Region.stardew_valley,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest),
monsters=unlikely)
ancient_sword = create_artifact("Ancient Sword", 109, 5.8, (Region.forest, Region.mountain),
geodes=(Geode.artifact_trove, common.fishing_chest))
rusty_spoon = create_artifact("Rusty Spoon", 110, 9.6, Region.town,
geodes=(Geode.artifact_trove, common.fishing_chest))
rusty_spur = create_artifact("Rusty Spur", 111, 15.6, Region.farm,
geodes=(Geode.artifact_trove, common.fishing_chest))
rusty_cog = create_artifact("Rusty Cog", 112, 9.6, Region.mountain,
geodes=(Geode.artifact_trove, common.fishing_chest))
chicken_statue = create_artifact("Chicken Statue", 113, 13.5, Region.farm,
geodes=(Geode.artifact_trove, common.fishing_chest))
ancient_seed = create_artifact("Ancient Seed", 114, 8.4, (Region.forest, Region.mountain),
geodes=(Geode.artifact_trove, common.fishing_chest),
ancient_sword = create_artifact("Ancient Sword", 5.8, (Region.forest, Region.mountain),
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
rusty_spoon = create_artifact("Rusty Spoon", 9.6, Region.town,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
rusty_spur = create_artifact("Rusty Spur", 15.6, Region.farm,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
rusty_cog = create_artifact("Rusty Cog", 9.6, Region.mountain,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
chicken_statue = create_artifact("Chicken Statue", 13.5, Region.farm,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
ancient_seed = create_artifact("Ancient Seed", 8.4, (Region.forest, Region.mountain),
geodes=(Geode.artifact_trove, WaterChest.fishing_chest),
monsters=unlikely)
prehistoric_tool = create_artifact("Prehistoric Tool", 115, 11.1, (Region.mountain, Region.forest, Region.bus_stop),
geodes=(Geode.artifact_trove, common.fishing_chest))
dried_starfish = create_artifact("Dried Starfish", 116, 12.5, Region.beach,
geodes=(Geode.artifact_trove, common.fishing_chest))
anchor = create_artifact("Anchor", 117, 8.5, Region.beach, geodes=(Geode.artifact_trove, common.fishing_chest))
glass_shards = create_artifact("Glass Shards", 118, 11.5, Region.beach,
geodes=(Geode.artifact_trove, common.fishing_chest))
bone_flute = create_artifact("Bone Flute", 119, 6.3, (Region.mountain, Region.forest, Region.town),
geodes=(Geode.artifact_trove, common.fishing_chest))
prehistoric_handaxe = create_artifact("Prehistoric Handaxe", 120, 13.7,
prehistoric_tool = create_artifact("Prehistoric Tool", 11.1, (Region.mountain, Region.forest, Region.bus_stop),
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
dried_starfish = create_artifact("Dried Starfish", 12.5, Region.beach,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
anchor = create_artifact("Anchor", 8.5, Region.beach, geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
glass_shards = create_artifact("Glass Shards", 11.5, Region.beach,
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
bone_flute = create_artifact("Bone Flute", 6.3, (Region.mountain, Region.forest, Region.town),
geodes=(Geode.artifact_trove, WaterChest.fishing_chest))
prehistoric_handaxe = create_artifact(Artifact.prehistoric_handaxe, 13.7,
(Region.mountain, Region.forest, Region.bus_stop),
geodes=Geode.artifact_trove)
dwarvish_helm = create_artifact("Dwarvish Helm", 121, 8.7, Region.mines_floor_20,
dwarvish_helm = create_artifact("Dwarvish Helm", 8.7, Region.mines_floor_20,
geodes=(Geode.geode, Geode.omni, Geode.artifact_trove))
dwarf_gadget = create_artifact("Dwarf Gadget", 122, 9.7, Region.mines_floor_60,
dwarf_gadget = create_artifact("Dwarf Gadget", 9.7, Region.mines_floor_60,
geodes=(Geode.magma, Geode.omni, Geode.artifact_trove))
ancient_drum = create_artifact("Ancient Drum", 123, 9.5, (Region.bus_stop, Region.forest, Region.town),
ancient_drum = create_artifact("Ancient Drum", 9.5, (Region.bus_stop, Region.forest, Region.town),
geodes=(Geode.frozen, Geode.omni, Geode.artifact_trove))
golden_mask = create_artifact("Golden Mask", 124, 6.7, Region.desert,
golden_mask = create_artifact("Golden Mask", 6.7, Region.desert,
geodes=Geode.artifact_trove)
golden_relic = create_artifact("Golden Relic", 125, 9.7, Region.desert,
golden_relic = create_artifact("Golden Relic", 9.7, Region.desert,
geodes=Geode.artifact_trove)
strange_doll_green = create_artifact("Strange Doll (Green)", 126, 10, Region.town,
geodes=common.secret_note)
strange_doll = create_artifact("Strange Doll", 127, 10, Region.desert,
geodes=common.secret_note)
prehistoric_scapula = create_artifact("Prehistoric Scapula", 579, 6.2,
strange_doll_green = create_artifact("Strange Doll (Green)", 10, Region.town,
geodes=Forageable.secret_note)
strange_doll = create_artifact("Strange Doll", 10, Region.desert,
geodes=Forageable.secret_note)
prehistoric_scapula = create_artifact("Prehistoric Scapula", 6.2,
(Region.dig_site, Region.forest, Region.town))
prehistoric_tibia = create_artifact("Prehistoric Tibia", 580, 16.6,
prehistoric_tibia = create_artifact("Prehistoric Tibia", 16.6,
(Region.dig_site, Region.forest, Region.railroad))
prehistoric_skull = create_artifact("Prehistoric Skull", 581, 3.9, (Region.dig_site, Region.mountain))
skeletal_hand = create_artifact("Skeletal Hand", 582, 7.9, (Region.dig_site, Region.backwoods, Region.beach))
prehistoric_rib = create_artifact("Prehistoric Rib", 583, 15, (Region.dig_site, Region.farm, Region.town),
prehistoric_skull = create_artifact("Prehistoric Skull", 3.9, (Region.dig_site, Region.mountain))
skeletal_hand = create_artifact(Fossil.skeletal_hand, 7.9, (Region.dig_site, Region.backwoods, Region.beach))
prehistoric_rib = create_artifact("Prehistoric Rib", 15, (Region.dig_site, Region.farm, Region.town),
monsters=Monster.pepper_rex)
prehistoric_vertebra = create_artifact("Prehistoric Vertebra", 584, 12.7, (Region.dig_site, Region.bus_stop),
prehistoric_vertebra = create_artifact("Prehistoric Vertebra", 12.7, (Region.dig_site, Region.bus_stop),
monsters=Monster.pepper_rex)
skeletal_tail = create_artifact("Skeletal Tail", 585, 5.1, (Region.dig_site, Region.mines_floor_20),
geodes=common.fishing_chest)
nautilus_fossil = create_artifact("Nautilus Fossil", 586, 6.9, (Region.dig_site, Region.beach),
geodes=common.fishing_chest)
amphibian_fossil = create_artifact("Amphibian Fossil", 587, 6.3, (Region.dig_site, Region.forest, Region.mountain),
geodes=common.fishing_chest)
palm_fossil = create_artifact("Palm Fossil", 588, 10.2,
skeletal_tail = create_artifact("Skeletal Tail", 5.1, (Region.dig_site, Region.mines_floor_20),
geodes=WaterChest.fishing_chest)
nautilus_fossil = create_artifact("Nautilus Fossil", 6.9, (Region.dig_site, Region.beach),
geodes=WaterChest.fishing_chest)
amphibian_fossil = create_artifact("Amphibian Fossil", 6.3, (Region.dig_site, Region.forest, Region.mountain),
geodes=WaterChest.fishing_chest)
palm_fossil = create_artifact("Palm Fossil", 10.2,
(Region.dig_site, Region.desert, Region.forest, Region.beach))
trilobite = create_artifact("Trilobite", 589, 7.4, (Region.dig_site, Region.desert, Region.forest, Region.beach))
trilobite = create_artifact("Trilobite", 7.4, (Region.dig_site, Region.desert, Region.forest, Region.beach))
class Mineral:
quartz = create_mineral("Quartz", 80, Region.mines_floor_20,
monsters=Monster.stone_golem)
fire_quartz = create_mineral("Fire Quartz", 82, Region.mines_floor_100,
geodes=(Geode.magma, Geode.omni, common.fishing_chest),
quartz = create_mineral(Mineral.quartz, Region.mines_floor_20)
fire_quartz = create_mineral("Fire Quartz", Region.mines_floor_100,
geodes=(Geode.magma, Geode.omni, WaterChest.fishing_chest),
difficulty=1.0 / 12.0)
frozen_tear = create_mineral("Frozen Tear", 84, Region.mines_floor_60,
geodes=(Geode.frozen, Geode.omni, common.fishing_chest),
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)
earth_crystal = create_mineral("Earth Crystal", 86, Region.mines_floor_20,
geodes=(Geode.geode, Geode.omni, common.fishing_chest),
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)
emerald = create_mineral("Emerald", 60, Region.mines_floor_100,
geodes=common.fishing_chest)
aquamarine = create_mineral("Aquamarine", 62, Region.mines_floor_60,
geodes=common.fishing_chest)
ruby = create_mineral("Ruby", 64, Region.mines_floor_100,
geodes=common.fishing_chest)
amethyst = create_mineral("Amethyst", 66, Region.mines_floor_20,
geodes=common.fishing_chest)
topaz = create_mineral("Topaz", 68, Region.mines_floor_20,
geodes=common.fishing_chest)
jade = create_mineral("Jade", 70, Region.mines_floor_60,
geodes=common.fishing_chest)
diamond = create_mineral("Diamond", 72, Region.mines_floor_60,
geodes=common.fishing_chest)
prismatic_shard = create_mineral("Prismatic Shard", 74, Region.skull_cavern_100,
emerald = create_mineral("Emerald", Region.mines_floor_100,
geodes=WaterChest.fishing_chest)
aquamarine = create_mineral("Aquamarine", Region.mines_floor_60,
geodes=WaterChest.fishing_chest)
ruby = create_mineral("Ruby", Region.mines_floor_100,
geodes=WaterChest.fishing_chest)
amethyst = create_mineral("Amethyst", Region.mines_floor_20,
geodes=WaterChest.fishing_chest)
topaz = create_mineral("Topaz", Region.mines_floor_20,
geodes=WaterChest.fishing_chest)
jade = create_mineral("Jade", Region.mines_floor_60,
geodes=WaterChest.fishing_chest)
diamond = create_mineral("Diamond", Region.mines_floor_60,
geodes=WaterChest.fishing_chest)
prismatic_shard = create_mineral("Prismatic Shard", Region.skull_cavern_100,
geodes=unlikely,
monsters=unlikely)
alamite = create_mineral("Alamite", 538, Region.town,
alamite = create_mineral("Alamite",
geodes=(Geode.geode, Geode.omni))
bixite = create_mineral("Bixite", 539, Region.town,
bixite = create_mineral("Bixite",
geodes=(Geode.magma, Geode.omni),
monsters=unlikely)
baryte = create_mineral("Baryte", 540, Region.town,
baryte = create_mineral("Baryte",
geodes=(Geode.magma, Geode.omni))
aerinite = create_mineral("Aerinite", 541, Region.town,
aerinite = create_mineral("Aerinite",
geodes=(Geode.frozen, Geode.omni))
calcite = create_mineral("Calcite", 542, Region.town,
calcite = create_mineral("Calcite",
geodes=(Geode.geode, Geode.omni))
dolomite = create_mineral("Dolomite", 543, Region.town,
dolomite = create_mineral("Dolomite",
geodes=(Geode.magma, Geode.omni))
esperite = create_mineral("Esperite", 544, Region.town,
esperite = create_mineral("Esperite",
geodes=(Geode.frozen, Geode.omni))
fluorapatite = create_mineral("Fluorapatite", 545, Region.town,
fluorapatite = create_mineral("Fluorapatite",
geodes=(Geode.frozen, Geode.omni))
geminite = create_mineral("Geminite", 546, Region.town,
geminite = create_mineral("Geminite",
geodes=(Geode.frozen, Geode.omni))
helvite = create_mineral("Helvite", 547, Region.town,
helvite = create_mineral("Helvite",
geodes=(Geode.magma, Geode.omni))
jamborite = create_mineral("Jamborite", 548, Region.town,
jamborite = create_mineral("Jamborite",
geodes=(Geode.geode, Geode.omni))
jagoite = create_mineral("Jagoite", 549, Region.town,
jagoite = create_mineral("Jagoite",
geodes=(Geode.geode, Geode.omni))
kyanite = create_mineral("Kyanite", 550, Region.town,
kyanite = create_mineral("Kyanite",
geodes=(Geode.frozen, Geode.omni))
lunarite = create_mineral("Lunarite", 551, Region.town,
lunarite = create_mineral("Lunarite",
geodes=(Geode.frozen, Geode.omni))
malachite = create_mineral("Malachite", 552, Region.town,
malachite = create_mineral("Malachite",
geodes=(Geode.geode, Geode.omni))
neptunite = create_mineral("Neptunite", 553, Region.town,
neptunite = create_mineral("Neptunite",
geodes=(Geode.magma, Geode.omni))
lemon_stone = create_mineral("Lemon Stone", 554, Region.town,
lemon_stone = create_mineral("Lemon Stone",
geodes=(Geode.magma, Geode.omni))
nekoite = create_mineral("Nekoite", 555, Region.town,
nekoite = create_mineral("Nekoite",
geodes=(Geode.geode, Geode.omni))
orpiment = create_mineral("Orpiment", 556, Region.town,
orpiment = create_mineral("Orpiment",
geodes=(Geode.geode, Geode.omni))
petrified_slime = create_mineral("Petrified Slime", 557, Region.town,
geodes=(Geode.geode, Geode.omni))
thunder_egg = create_mineral("Thunder Egg", 558, Region.town,
petrified_slime = create_mineral(Mineral.petrified_slime, Region.slime_hutch)
thunder_egg = create_mineral("Thunder Egg",
geodes=(Geode.geode, Geode.omni))
pyrite = create_mineral("Pyrite", 559, Region.town,
pyrite = create_mineral("Pyrite",
geodes=(Geode.frozen, Geode.omni))
ocean_stone = create_mineral("Ocean Stone", 560, Region.town,
ocean_stone = create_mineral("Ocean Stone",
geodes=(Geode.frozen, Geode.omni))
ghost_crystal = create_mineral("Ghost Crystal", 561, Region.town,
ghost_crystal = create_mineral("Ghost Crystal",
geodes=(Geode.frozen, Geode.omni))
tigerseye = create_mineral("Tigerseye", 562, Region.town,
tigerseye = create_mineral("Tigerseye",
geodes=(Geode.magma, Geode.omni))
jasper = create_mineral("Jasper", 563, Region.town,
jasper = create_mineral("Jasper",
geodes=(Geode.magma, Geode.omni))
opal = create_mineral("Opal", 564, Region.town,
opal = create_mineral("Opal",
geodes=(Geode.frozen, Geode.omni))
fire_opal = create_mineral("Fire Opal", 565, Region.town,
fire_opal = create_mineral("Fire Opal",
geodes=(Geode.magma, Geode.omni))
celestine = create_mineral("Celestine", 566, Region.town,
celestine = create_mineral("Celestine",
geodes=(Geode.geode, Geode.omni))
marble = create_mineral("Marble", 567, Region.town,
marble = create_mineral("Marble",
geodes=(Geode.frozen, Geode.omni))
sandstone = create_mineral("Sandstone", 568, Region.town,
sandstone = create_mineral("Sandstone",
geodes=(Geode.geode, Geode.omni))
granite = create_mineral("Granite", 569, Region.town,
granite = create_mineral("Granite",
geodes=(Geode.geode, Geode.omni))
basalt = create_mineral("Basalt", 570, Region.town,
basalt = create_mineral("Basalt",
geodes=(Geode.magma, Geode.omni))
limestone = create_mineral("Limestone", 571, Region.town,
limestone = create_mineral("Limestone",
geodes=(Geode.geode, Geode.omni))
soapstone = create_mineral("Soapstone", 572, Region.town,
soapstone = create_mineral("Soapstone",
geodes=(Geode.frozen, Geode.omni))
hematite = create_mineral("Hematite", 573, Region.town,
hematite = create_mineral("Hematite",
geodes=(Geode.frozen, Geode.omni))
mudstone = create_mineral("Mudstone", 574, Region.town,
mudstone = create_mineral("Mudstone",
geodes=(Geode.geode, Geode.omni))
obsidian = create_mineral("Obsidian", 575, Region.town,
obsidian = create_mineral("Obsidian",
geodes=(Geode.magma, Geode.omni))
slate = create_mineral("Slate", 576, Region.town,
geodes=(Geode.geode, Geode.omni))
fairy_stone = create_mineral("Fairy Stone", 577, Region.town,
geodes=(Geode.frozen, Geode.omni))
star_shards = create_mineral("Star Shards", 578, Region.town,
geodes=(Geode.magma, Geode.omni))
slate = create_mineral("Slate", geodes=(Geode.geode, Geode.omni))
fairy_stone = create_mineral("Fairy Stone", geodes=(Geode.frozen, Geode.omni))
star_shards = create_mineral("Star Shards", geodes=(Geode.magma, Geode.omni))
dwarf_scrolls = (Artifact.dwarf_scroll_i, Artifact.dwarf_scroll_ii, Artifact.dwarf_scroll_iii, Artifact.dwarf_scroll_iv)
@ -291,4 +285,4 @@ skeleton_front = (Artifact.prehistoric_skull, Artifact.skeletal_hand, Artifact.p
skeleton_middle = (Artifact.prehistoric_rib, Artifact.prehistoric_vertebra)
skeleton_back = (Artifact.prehistoric_tibia, Artifact.skeletal_tail)
all_museum_items_by_name = {item.name: item for item in all_museum_items}
all_museum_items_by_name = {item.item_name: item for item in all_museum_items}

View File

@ -1,78 +1,35 @@
from typing import Dict, List
from typing import Dict, List, Optional
from ..mods.mod_data import ModNames
from .recipe_source import RecipeSource, FriendshipSource, SkillSource, QueenOfSauceSource, ShopSource, StarterSource, ShopTradeSource, ShopFriendshipSource
from ..strings.animal_product_names import AnimalProduct
from ..strings.artisan_good_names import ArtisanGood
from ..strings.crop_names import Fruit, Vegetable
from ..strings.fish_names import Fish, WaterItem
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
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable
from ..strings.ingredient_names import Ingredient
from ..strings.food_names import Meal, Beverage
from ..strings.region_names import Region
from ..strings.food_names import Meal, SVEMeal, Beverage, DistantLandsMeal, BoardingHouseMeal
from ..strings.material_names import Material
from ..strings.metal_names import Fossil
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.villager_names import NPC
class RecipeSource:
pass
class StarterSource(RecipeSource):
pass
class QueenOfSauceSource(RecipeSource):
year: int
season: str
day: int
def __init__(self, year: int, season: str, day: int):
self.year = year
self.season = season
self.day = day
class FriendshipSource(RecipeSource):
friend: str
hearts: int
def __init__(self, friend: str, hearts: int):
self.friend = friend
self.hearts = hearts
class SkillSource(RecipeSource):
skill: str
level: int
def __init__(self, skill: str, level: int):
self.skill = skill
self.level = level
class ShopSource(RecipeSource):
region: str
price: int
def __init__(self, region: str, price: int):
self.region = region
self.price = price
class ShopTradeSource(ShopSource):
currency: str
from ..strings.villager_names import NPC, ModNPC
class CookingRecipe:
meal: str
ingredients: Dict[str, int]
source: RecipeSource
mod_name: Optional[str] = None
def __init__(self, meal: str, ingredients: Dict[str, int], source: RecipeSource):
def __init__(self, meal: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None):
self.meal = meal
self.ingredients = ingredients
self.source = source
self.mod_name = mod_name
def __repr__(self):
return f"{self.meal} (Source: {self.source} |" \
@ -82,9 +39,14 @@ class CookingRecipe:
all_cooking_recipes: List[CookingRecipe] = []
def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int]) -> CookingRecipe:
def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe:
source = FriendshipSource(friend, hearts)
return create_recipe(name, ingredients, source)
return create_recipe(name, ingredients, source, mod_name)
def friendship_and_shop_recipe(name: str, friend: str, hearts: int, region: str, price: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe:
source = ShopFriendshipSource(friend, hearts, region, price)
return create_recipe(name, ingredients, source, mod_name)
def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int]) -> CookingRecipe:
@ -92,8 +54,13 @@ def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int])
return create_recipe(name, ingredients, source)
def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int]) -> CookingRecipe:
def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CookingRecipe:
source = ShopSource(region, price)
return create_recipe(name, ingredients, source, mod_name)
def shop_trade_recipe(name: str, region: str, currency: str, price: int, ingredients: Dict[str, int]) -> CookingRecipe:
source = ShopTradeSource(region, currency, price)
return create_recipe(name, ingredients, source)
@ -107,34 +74,44 @@ def starter_recipe(name: str, ingredients: Dict[str, int]) -> CookingRecipe:
return create_recipe(name, ingredients, source)
def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource) -> CookingRecipe:
recipe = CookingRecipe(name, ingredients, source)
def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource, mod_name: Optional[str] = None) -> CookingRecipe:
recipe = CookingRecipe(name, ingredients, source, mod_name)
all_cooking_recipes.append(recipe)
return recipe
algae_soup = friendship_recipe(Meal.algae_soup, NPC.clint, 3, {WaterItem.green_algae: 4})
artichoke_dip = queen_of_sauce_recipe(Meal.artichoke_dip, 1, Season.fall, 28, {Vegetable.artichoke: 1, AnimalProduct.cow_milk: 1})
autumn_bounty = friendship_recipe(Meal.autumn_bounty, NPC.demetrius, 7, {Vegetable.yam: 1, Vegetable.pumpkin: 1})
baked_fish = queen_of_sauce_recipe(Meal.baked_fish, 1, Season.summer, 7, {Fish.sunfish: 1, Fish.bream: 1, Ingredient.wheat_flour: 1})
banana_pudding = shop_trade_recipe(Meal.banana_pudding, Region.island_trader, Fossil.bone_fragment, 30, {Fruit.banana: 1, AnimalProduct.cow_milk: 1, Ingredient.sugar: 1})
bean_hotpot = friendship_recipe(Meal.bean_hotpot, NPC.clint, 7, {Vegetable.green_bean: 2})
blackberry_cobbler_ingredients = {Forageable.blackberry: 2, Ingredient.sugar: 1, Ingredient.wheat_flour: 1}
blackberry_cobbler_qos = queen_of_sauce_recipe(Meal.blackberry_cobbler, 2, Season.fall, 14, blackberry_cobbler_ingredients)
blueberry_tart = friendship_recipe(Meal.blueberry_tart, NPC.pierre, 3, {Fruit.blueberry: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.any_egg: 1})
blueberry_tart_ingredients = {Fruit.blueberry: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.any_egg: 1}
blueberry_tart = friendship_recipe(Meal.blueberry_tart, NPC.pierre, 3, blueberry_tart_ingredients)
bread = queen_of_sauce_recipe(Meal.bread, 1, Season.summer, 28, {Ingredient.wheat_flour: 1})
bruschetta = queen_of_sauce_recipe(Meal.bruschetta, 2, Season.winter, 21, {Meal.bread: 1, Ingredient.oil: 1, Vegetable.tomato: 1})
carp_surprise = queen_of_sauce_recipe(Meal.carp_surprise, 2, Season.summer, 7, {Fish.carp: 4})
cheese_cauliflower = friendship_recipe(Meal.cheese_cauliflower, NPC.pam, 3, {Vegetable.cauliflower: 1, ArtisanGood.cheese: 1})
chocolate_cake_ingredients = {Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.chicken_egg: 1}
chocolate_cake_qos = queen_of_sauce_recipe(Meal.chocolate_cake, 1, Season.winter, 14, chocolate_cake_ingredients)
chowder = friendship_recipe(Meal.chowder, NPC.willy, 3, {WaterItem.clam: 1, AnimalProduct.cow_milk: 1})
complete_breakfast = queen_of_sauce_recipe(Meal.complete_breakfast, 2, Season.spring, 21, {Meal.fried_egg: 1, AnimalProduct.milk: 1, Meal.hashbrowns: 1, Meal.pancakes: 1})
chowder = friendship_recipe(Meal.chowder, NPC.willy, 3, {Fish.clam: 1, AnimalProduct.cow_milk: 1})
coleslaw = queen_of_sauce_recipe(Meal.coleslaw, 14, Season.spring, 14, {Vegetable.red_cabbage: 1, Ingredient.vinegar: 1, ArtisanGood.mayonnaise: 1})
complete_breakfast_ingredients = {Meal.fried_egg: 1, AnimalProduct.milk: 1, Meal.hashbrowns: 1, Meal.pancakes: 1}
complete_breakfast = queen_of_sauce_recipe(Meal.complete_breakfast, 2, Season.spring, 21, complete_breakfast_ingredients)
cookie = friendship_recipe(Meal.cookie, NPC.evelyn, 4, {Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.chicken_egg: 1})
crab_cakes_ingredients = {Fish.crab: 1, Ingredient.wheat_flour: 1, AnimalProduct.chicken_egg: 1, Ingredient.oil: 1}
crab_cakes_qos = queen_of_sauce_recipe(Meal.crab_cakes, 2, Season.fall, 21, crab_cakes_ingredients)
cranberry_candy = queen_of_sauce_recipe(Meal.cranberry_candy, 1, Season.winter, 28, {Fruit.cranberries: 1, Fruit.apple: 1, Ingredient.sugar: 1})
cranberry_sauce = friendship_recipe(Meal.cranberry_sauce, NPC.gus, 7, {Fruit.cranberries: 1, Ingredient.sugar: 1})
crispy_bass = friendship_recipe(Meal.crispy_bass, NPC.kent, 3, {Fish.largemouth_bass: 1, Ingredient.wheat_flour: 1, Ingredient.oil: 1})
dish_o_the_sea = skill_recipe(Meal.dish_o_the_sea, Skill.fishing, 3, {Fish.sardine: 2, Meal.hashbrowns: 1})
eggplant_parmesan = friendship_recipe(Meal.eggplant_parmesan, NPC.lewis, 7, {Vegetable.eggplant: 1, Vegetable.tomato: 1})
escargot = friendship_recipe(Meal.escargot, NPC.willy, 5, {Fish.snail: 1, Vegetable.garlic: 1})
farmer_lunch = skill_recipe(Meal.farmer_lunch, Skill.farming, 3, {Meal.omelet: 2, Vegetable.parsnip: 1})
fiddlehead_risotto = queen_of_sauce_recipe(Meal.fiddlehead_risotto, 2, Season.fall, 28, {Ingredient.oil: 1, Forageable.fiddlehead_fern: 1, Vegetable.garlic: 1})
fish_stew = friendship_recipe(Meal.fish_stew, NPC.willy, 7, {Fish.crayfish: 1, Fish.mussel: 1, Fish.periwinkle: 1, Vegetable.tomato: 1})
fish_taco = friendship_recipe(Meal.fish_taco, NPC.linus, 7, {Fish.tuna: 1, Meal.tortilla: 1, Vegetable.red_cabbage: 1, ArtisanGood.mayonnaise: 1})
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})
@ -145,7 +122,12 @@ ginger_ale = shop_recipe(Beverage.ginger_ale, Region.volcano_dwarf_shop, 1000, {
glazed_yams = queen_of_sauce_recipe(Meal.glazed_yams, 1, Season.fall, 21, {Vegetable.yam: 1, Ingredient.sugar: 1})
hashbrowns = queen_of_sauce_recipe(Meal.hashbrowns, 2, Season.spring, 14, {Vegetable.potato: 1, Ingredient.oil: 1})
ice_cream = friendship_recipe(Meal.ice_cream, NPC.jodi, 7, {AnimalProduct.cow_milk: 1, Ingredient.sugar: 1})
lobster_bisque_ingredients = {Fish.lobster: 1, AnimalProduct.cow_milk: 1}
lobster_bisque_friend = friendship_recipe(Meal.lobster_bisque, NPC.willy, 9, lobster_bisque_ingredients)
lobster_bisque_qos = queen_of_sauce_recipe(Meal.lobster_bisque, 2, Season.winter, 14, lobster_bisque_ingredients)
lucky_lunch = queen_of_sauce_recipe(Meal.lucky_lunch, 2, Season.spring, 28, {Fish.sea_cucumber: 1, Meal.tortilla: 1, Flower.blue_jazz: 1})
maki_roll = queen_of_sauce_recipe(Meal.maki_roll, 1, Season.summer, 21, {Fish.any: 1, WaterItem.seaweed: 1, Ingredient.rice: 1})
mango_sticky_rice = friendship_recipe(Meal.mango_sticky_rice, NPC.leo, 7, {Fruit.mango: 1, Forageable.coconut: 1, Ingredient.rice: 1})
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})
omelet = queen_of_sauce_recipe(Meal.omelet, 1, Season.spring, 28, {AnimalProduct.chicken_egg: 1, AnimalProduct.cow_milk: 1})
@ -159,9 +141,12 @@ pizza_ingredients = {Ingredient.wheat_flour: 1, Vegetable.tomato: 1, ArtisanGood
pizza_qos = queen_of_sauce_recipe(Meal.pizza, 2, Season.spring, 7, pizza_ingredients)
pizza_saloon = shop_recipe(Meal.pizza, Region.saloon, 150, pizza_ingredients)
plum_pudding = queen_of_sauce_recipe(Meal.plum_pudding, 1, Season.winter, 7, {Forageable.wild_plum: 2, Ingredient.wheat_flour: 1, Ingredient.sugar: 1})
poi = friendship_recipe(Meal.poi, NPC.leo, 3, {Vegetable.taro_root: 4})
poppyseed_muffin = queen_of_sauce_recipe(Meal.poppyseed_muffin, 2, Season.winter, 7, {Flower.poppy: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1})
pumpkin_pie_ingredients = {Vegetable.pumpkin: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.cow_milk: 1}
pumpkin_pie_qos = queen_of_sauce_recipe(Meal.pumpkin_pie, 1, Season.winter, 21, pumpkin_pie_ingredients)
pumpkin_soup = friendship_recipe(Meal.pumpkin_soup, NPC.robin, 7, {Vegetable.pumpkin: 1, AnimalProduct.cow_milk: 1})
radish_salad = queen_of_sauce_recipe(Meal.radish_salad, 1, Season.spring, 21, {Ingredient.oil: 1, Ingredient.vinegar: 1, Vegetable.radish: 1})
red_plate = friendship_recipe(Meal.red_plate, NPC.emily, 7, {Vegetable.red_cabbage: 1, Vegetable.radish: 1})
rhubarb_pie = friendship_recipe(Meal.rhubarb_pie, NPC.marnie, 7, {Fruit.rhubarb: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1})
rice_pudding = friendship_recipe(Meal.rice_pudding, NPC.evelyn, 7, {AnimalProduct.milk: 1, Ingredient.sugar: 1, Ingredient.rice: 1})
@ -170,21 +155,59 @@ roots_platter = skill_recipe(Meal.roots_platter, Skill.combat, 3, {Forageable.ca
salad = friendship_recipe(Meal.salad, NPC.emily, 3, {Forageable.leek: 1, Forageable.dandelion: 1, Ingredient.vinegar: 1})
salmon_dinner = friendship_recipe(Meal.salmon_dinner, NPC.gus, 3, {Fish.salmon: 1, Vegetable.amaranth: 1, Vegetable.kale: 1})
sashimi = friendship_recipe(Meal.sashimi, NPC.linus, 3, {Fish.any: 1})
seafoam_pudding = skill_recipe(Meal.seafoam_pudding, Skill.fishing, 9, {Fish.flounder: 1, Fish.midnight_carp: 1, AnimalProduct.squid_ink: 1})
shrimp_cocktail = queen_of_sauce_recipe(Meal.shrimp_cocktail, 2, Season.winter, 28, {Vegetable.tomato: 1, Fish.shrimp: 1, Forageable.wild_horseradish: 1})
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_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})
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)
triple_shot_espresso = shop_recipe(Beverage.triple_shot_espresso, Region.saloon, 5000, {Beverage.coffee: 3})
tropical_curry = shop_recipe(Meal.tropical_curry, Region.island_resort, 2000, {Forageable.coconut: 1, Fruit.pineapple: 1, Fruit.hot_pepper: 1})
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)
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)
big_bark_burger = friendship_and_shop_recipe(SVEMeal.big_bark_burger, NPC.gus, 5, Region.saloon, 5500,
{SVEFish.puppyfish: 1, Meal.bread: 1, Ingredient.oil: 1}, ModNames.sve)
flower_cookie = shop_recipe(SVEMeal.flower_cookie, SVERegion.bear_shop, 0, {SVEForage.ferngill_primrose: 1, SVEForage.goldenrod: 1,
SVEForage.winter_star_rose: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1,
AnimalProduct.large_egg: 1}, ModNames.sve)
frog_legs = shop_recipe(SVEMeal.frog_legs, Region.adventurer_guild, 2000, {SVEFish.frog: 1, Ingredient.oil: 1, Ingredient.wheat_flour: 1}, ModNames.sve)
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},
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)
seaweed_salad = shop_recipe(SVEMeal.seaweed_salad, Region.fish_shop, 1250, {SVEFish.dulse_seaweed: 2, WaterItem.seaweed: 2, Ingredient.oil: 1}, ModNames.sve)
void_delight = friendship_and_shop_recipe(SVEMeal.void_delight, NPC.krobus, 10, Region.sewer, 5000,
{SVEFish.void_eel: 1, Loot.void_essence: 50, Loot.solar_essence: 20}, ModNames.sve)
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)
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)
pemmican = friendship_recipe(DistantLandsMeal.pemmican, ModNPC.goblin, 8, {Loot.bug_meat: 1, Fish.any: 1, Forageable.salmonberry: 3,
Material.stone: 2}, ModNames.distant_lands)
special_pumpkin_soup = friendship_recipe(BoardingHouseMeal.special_pumpkin_soup, ModNPC.joel, 6, {Vegetable.pumpkin: 2, AnimalProduct.large_goat_milk: 1,
Vegetable.garlic: 1}, ModNames.boarding_house)
all_cooking_recipes_by_name = {recipe.meal: recipe for recipe in all_cooking_recipes}

View File

@ -0,0 +1,149 @@
from typing import Union, List, Tuple
class RecipeSource:
def __repr__(self):
return f"RecipeSource"
class StarterSource(RecipeSource):
def __repr__(self):
return f"StarterSource"
class ArchipelagoSource(RecipeSource):
ap_item: Tuple[str]
def __init__(self, ap_item: Union[str, List[str]]):
if isinstance(ap_item, str):
ap_item = [ap_item]
self.ap_item = tuple(ap_item)
def __repr__(self):
return f"ArchipelagoSource {self.ap_item}"
class LogicSource(RecipeSource):
logic_rule: str
def __init__(self, logic_rule: str):
self.logic_rule = logic_rule
def __repr__(self):
return f"LogicSource {self.logic_rule}"
class QueenOfSauceSource(RecipeSource):
year: int
season: str
day: int
def __init__(self, year: int, season: str, day: int):
self.year = year
self.season = season
self.day = day
def __repr__(self):
return f"QueenOfSauceSource at year {self.year} {self.season} {self.day}"
class QuestSource(RecipeSource):
quest: str
def __init__(self, quest: str):
self.quest = quest
def __repr__(self):
return f"QuestSource at quest {self.quest}"
class FriendshipSource(RecipeSource):
friend: str
hearts: int
def __init__(self, friend: str, hearts: int):
self.friend = friend
self.hearts = hearts
def __repr__(self):
return f"FriendshipSource at {self.friend} {self.hearts} <3"
class CutsceneSource(FriendshipSource):
region: str
def __init__(self, region: str, friend: str, hearts: int):
super().__init__(friend, hearts)
self.region = region
def __repr__(self):
return f"CutsceneSource at {self.region}"
class SkillSource(RecipeSource):
skill: str
level: int
def __init__(self, skill: str, level: int):
self.skill = skill
self.level = level
def __repr__(self):
return f"SkillSource at level {self.level} {self.skill}"
class ShopSource(RecipeSource):
region: str
price: int
def __init__(self, region: str, price: int):
self.region = region
self.price = price
def __repr__(self):
return f"ShopSource at {self.region} costing {self.price}g"
class ShopFriendshipSource(RecipeSource):
friend: str
hearts: int
region: str
price: int
def __init__(self, friend: str, hearts: int, region: str, price: int):
self.friend = friend
self.hearts = hearts
self.region = region
self.price = price
def __repr__(self):
return f"ShopFriendshipSource at {self.region} costing {self.price}g when {self.friend} has {self.hearts} hearts"
class FestivalShopSource(ShopSource):
def __init__(self, region: str, price: int):
super().__init__(region, price)
class ShopTradeSource(ShopSource):
currency: str
def __init__(self, region: str, currency: str, price: int):
super().__init__(region, price)
self.currency = currency
def __repr__(self):
return f"ShopTradeSource at {self.region} costing {self.price} {self.currency}"
class SpecialOrderSource(RecipeSource):
special_order: str
def __init__(self, special_order: str):
self.special_order = special_order
def __repr__(self):
return f"SpecialOrderSource from {self.special_order}"

View File

@ -1,7 +1,10 @@
from dataclasses import dataclass
from typing import List, Tuple, Optional, Dict
from ..strings.region_names import Region
from typing import List, Tuple, Optional, Dict, Callable, Set
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.season_names import Season
from ..strings.villager_names import NPC, ModNPC
@ -14,7 +17,7 @@ class Villager:
birthday: str
gifts: Tuple[str]
available: bool
mod_name: Optional[str]
mod_name: str
def __repr__(self):
return f"{self.name} [Bachelor: {self.bachelor}] [Available from start: {self.available}]" \
@ -41,6 +44,23 @@ island = (Region.island_east,)
secret_woods = (Region.secret_woods,)
wizard_tower = (Region.wizard_tower,)
# Stardew Valley Expanded Locations
adventurer = (Region.adventurer_guild,)
highlands = (SVERegion.highlands_outside,)
bluemoon = (SVERegion.blue_moon_vineyard,)
aurora = (SVERegion.aurora_vineyard,)
museum = (Region.museum,)
jojamart = (Region.jojamart,)
railroad = (Region.railroad,)
junimo = (SVERegion.junimo_woods,)
# Stray Locations
witch_swamp = (Region.witch_swamp,)
witch_attic = (AlectoRegion.witch_attic,)
hat_house = (LaceyRegion.hat_house,)
the_lost_valley = (BoardingHouseRegion.the_lost_valley,)
boarding_house = (BoardingHouseRegion.boarding_house_first,)
golden_pumpkin = ("Golden Pumpkin",)
# magic_rock_candy = ("Magic Rock Candy",)
pearl = ("Pearl",)
@ -183,7 +203,7 @@ mead = ("Mead",)
pale_ale = ("Pale Ale",)
parsnip = ("Parsnip",)
# parsnip_soup = ("Parsnip Soup",)
pina_colada = ("Piña Colada",)
pina_colada = (Beverage.pina_colada,)
pam_loves = beer + cactus_fruit + glazed_yams + mead + pale_ale + parsnip + pina_colada # | parsnip_soup
# fried_calamari = ("Fried Calamari",)
pierre_loves = () # fried_calamari
@ -209,7 +229,7 @@ super_cucumber = ("Super Cucumber",)
void_essence = ("Void Essence",)
wizard_loves = purple_mushroom + solar_essence + super_cucumber + void_essence
#Custom NPC Items and Loves
# Custom NPC Items and Loves
blueberry = ("Blueberry",)
chanterelle = ("Chanterelle",)
@ -271,8 +291,72 @@ jasper_loves = apple + blueberry + diamond + dwarf_gadget + dwarvish_helm + fire
juna_loves = ancient_doll + elvish_jewelry + dinosaur_egg + strange_doll + joja_cola + hashbrowns + pancakes + \
pink_cake + jelly + ghost_crystal + prehistoric_scapula + cherry
glazed_butterfish = ("Glazed Butterfish",)
aged_blue_moon_wine = ("Aged Blue Moon Wine",)
blue_moon_wine = ("Blue Moon Wine",)
daggerfish = ("Daggerfish",)
gemfish = ("Gemfish",)
green_mushroom = ("Green Mushroom",)
monster_mushroom = ("Monster Mushroom",)
swirl_stone = ("Swirl Stone",)
torpedo_trout = ("Torpedo Trout",)
void_shard = ("Void Shard",)
ornate_treasure_chest = ("Ornate Treasure Chest",)
frog_legs = ("Frog Legs",)
void_delight = ("Void Delight",)
void_pebble = ("Void Pebble",)
void_salmon_sushi = ("Void Salmon Sushi",)
puppyfish = ("Puppyfish",)
butterfish = ("Butterfish",)
king_salmon = ("King Salmon",)
frog = ("Frog",)
kittyfish = ("Kittyfish",)
big_bark_burger = ("Big Bark Burger",)
starfruit = ("Starfruit",)
bruschetta = ("Brushetta",)
apricot = ("Apricot",)
ocean_stone = ("Ocean Stone",)
fairy_stone = ("Fairy Stone",)
lunarite = ("Lunarite",)
bean_hotpot = ("Bean Hotpot",)
petrified_slime = ("Petrified Slime",)
ornamental_fan = ("Ornamental Fan",)
ancient_sword = ("Ancient Sword",)
star_shards = ("Star Shards",)
life_elixir = ("Life Elixir",)
juice = ("Juice",)
lobster_bisque = ("Lobster Bisque",)
chowder = ("Chowder",)
goat_milk = ("Goat Milk",)
maple_syrup = ("Maple Syrup",)
cookie = ("Cookie",)
blueberry_tart = ("Blueberry Tart",)
claire_loves = green_tea + sunflower + energy_tonic + bruschetta + apricot + ocean_stone + glazed_butterfish
lance_loves = aged_blue_moon_wine + daggerfish + gemfish + golden_pumpkin + \
green_mushroom + monster_mushroom + swirl_stone + torpedo_trout + tropical_curry + void_shard + \
ornate_treasure_chest
olivia_loves = wine + chocolate_cake + pink_cake + golden_mask + golden_relic + \
blue_moon_wine + aged_blue_moon_wine
sophia_loves = fairy_rose + fairy_stone + puppyfish
victor_loves = spaghetti + battery_pack + duck_feather + lunarite + \
aged_blue_moon_wine + blue_moon_wine + butterfish
andy_loves = pearl + beer + mead + pale_ale + farmers_lunch + glazed_butterfish + butterfish + \
king_salmon + blackberry_cobbler
gunther_loves = bean_hotpot + petrified_slime + salmon_dinner + elvish_jewelry + ornamental_fan + \
dinosaur_egg + rare_disc + ancient_sword + dwarvish_helm + dwarf_gadget + golden_mask + golden_relic + \
star_shards
marlon_loves = roots_platter + life_elixir + aged_blue_moon_wine + void_delight
martin_loves = juice + ice_cream + big_bark_burger
morgan_loves = iridium_bar + void_egg + void_mayonnaise + frog + kittyfish
morris_loves = lobster_bisque + chowder + truffle_oil + star_shards + aged_blue_moon_wine
scarlett_loves = goat_cheese + duck_feather + goat_milk + cherry + maple_syrup + honey + \
chocolate_cake + pink_cake + jade + glazed_yams # actually large milk but meh
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, ...],
@ -282,6 +366,18 @@ def villager(name: str, bachelor: bool, locations: Tuple[str, ...], birthday: st
return npc
def make_bachelor(mod_name: str, npc: Villager):
if npc.mod_name:
mod_name = npc.mod_name
return Villager(npc.name, True, npc.locations, 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
josh = villager(NPC.alex, True, town + alex_house, Season.summer, universal_loves + complete_breakfast + salmon_dinner, True)
elliott = villager(NPC.elliott, True, town + beach + elliott_house, Season.fall, universal_loves + elliott_loves, True)
harvey = villager(NPC.harvey, True, town + hospital, Season.winter, universal_loves + harvey_loves, True)
@ -326,13 +422,42 @@ jasper = villager(ModNPC.jasper, True, town, Season.fall, universal_loves + jasp
juna = villager(ModNPC.juna, False, forest, Season.summer, universal_loves + juna_loves, True, ModNames.juna)
kitty = villager(ModNPC.mr_ginger, False, forest, Season.summer, universal_loves + mister_ginger_loves, True, ModNames.ginger)
shiko = villager(ModNPC.shiko, True, town, Season.winter, universal_loves + shiko_loves, True, ModNames.shiko)
wellwick = villager(ModNPC.wellwick, True, forest, Season.winter, universal_loves + wellwick_loves, True, ModNames.shiko)
wellwick = villager(ModNPC.wellwick, True, forest, Season.winter, universal_loves + wellwick_loves, True, ModNames.wellwick)
yoba = villager(ModNPC.yoba, False, secret_woods, Season.spring, universal_loves + yoba_loves, False, ModNames.yoba)
riley = villager(ModNPC.riley, True, town, Season.spring, universal_loves, True, ModNames.riley)
zic = villager(ModNPC.goblin, False, witch_swamp, Season.fall, void_mayonnaise, False, ModNames.distant_lands)
alecto = villager(ModNPC.alecto, False, witch_attic, Generic.any, universal_loves, False, ModNames.alecto)
lacey = villager(ModNPC.lacey, True, forest, Season.spring, universal_loves, True, ModNames.lacey)
# Boarding House Villagers
gregory = villager(ModNPC.gregory, True, the_lost_valley, Season.fall, universal_loves, False, ModNames.boarding_house)
sheila = villager(ModNPC.sheila, True, boarding_house, Season.spring, universal_loves, True, ModNames.boarding_house)
joel = villager(ModNPC.joel, False, boarding_house, Season.winter, universal_loves, True, ModNames.boarding_house)
# SVE Villagers
claire = villager(ModNPC.claire, True, town + jojamart, Season.fall, universal_loves + claire_loves, True, ModNames.sve)
lance = villager(ModNPC.lance, True, adventurer + highlands + island, Season.spring, lance_loves, False, ModNames.sve)
mommy = villager(ModNPC.olivia, True, town, Season.spring, universal_loves_no_rabbit_foot + olivia_loves, True, ModNames.sve)
sophia = villager(ModNPC.sophia, True, bluemoon, Season.winter, universal_loves_no_rabbit_foot + sophia_loves, True, ModNames.sve)
victor = villager(ModNPC.victor, True, town, Season.summer, universal_loves + victor_loves, True, ModNames.sve)
andy = villager(ModNPC.andy, False, forest, Season.spring, universal_loves + andy_loves, True, ModNames.sve)
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)
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)
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, make_bachelor)
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
@ -344,3 +469,27 @@ for npc in all_villagers:
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

@ -18,35 +18,49 @@ The player can choose from a number of goals, using their YAML settings.
- 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)
- Complete the [Cryptic Note](https://stardewvalleywiki.com/Secret_Notes#Secret_Note_.2310) quest, by meeting Mr Qi on floor 100 of the Skull Cavern
- Get the achievement [Master Angler](https://stardewvalleywiki.com/Fish), which requires catching every fish in the game
- Get the achievement [A Complete Collection](https://stardewvalleywiki.com/Museum), which requires donating all the artifacts and minerals to the museum
- Get the achievement [Full House](https://stardewvalleywiki.com/Children), which requires getting married and having two kids.
- Become a [Master Angler](https://stardewvalleywiki.com/Fish), which requires catching every fish in your slot
- Restore [A Complete Collection](https://stardewvalleywiki.com/Museum), which requires donating all the artifacts and minerals to the museum
- Get the achievement [Full House](https://stardewvalleywiki.com/Children), which requires getting married and having two kids
- Get recognized as the [Greatest Walnut Hunter](https://stardewvalleywiki.com/Golden_Walnut) by Mr Qi, which requires finding all 130 golden walnuts on ginger island
- Become the [Protector of the Valley](https://stardewvalleywiki.com/Adventurer%27s_Guild#Monster_Eradication_Goals) by completing all the monster slayer goals at the Adventure Guild
- Complete a [Full Shipment](https://stardewvalleywiki.com/Shipping#Collection) by shipping every item in your slot
- Become a [Gourmet Chef](https://stardewvalleywiki.com/Cooking) by cooking every recipe in your slot
- Become a [Craft Master](https://stardewvalleywiki.com/Crafting) by crafting every item
- Earn the title of [Legend](https://stardewvalleywiki.com/Gold) by earning 10 000 000g
- Solve the [Mystery of the Stardrops](https://stardewvalleywiki.com/Stardrop) by finding every stardrop
- Finish 100% of your randomizer slot with Allsanity: Complete every check in your slot
- Achieve [Perfection](https://stardewvalleywiki.com/Perfection) in your save file
The following goals [Community Center, Master Angler, Protector of the Valley, Full Shipment and Gourmet Chef] will adapt to other settings in your slots, and are therefore customizable in duration and difficulty. For example, if you set "Fishsanity" to "Exclude Legendaries", and pick the Master Angler goal, you will not need to catch the legendaries to complete the goal.
## 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)
- [Story Quests](https://stardewvalleywiki.com/Quests#List_of_Story_Quests)
- [Traveling Merchant Items](https://stardewvalleywiki.com/Traveling_Cart)
- Isolated objectives such as the [beach bridge](https://stardewvalleywiki.com/The_Beach#Tide_Pools), [Old Master Cannoli](https://stardewvalleywiki.com/Secret_Woods#Old_Master_Cannoli), [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
- Carpenter Buildings
- Backpack Upgrades
- Mine Elevator Levels
- Skill Levels
- [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)
- Arcade Machines
- Help Wanted Quests
- Participating in Festivals
- Special Orders from the town board, or from Mr Qi
- Cropsanity: Growing and Harvesting individual crop types
- Fishsanity: Catching individual fish
- Museumsanity: Donating individual items, or reaching milestones for museum donations
- Friendsanity: Reaching specific friendship levels with NPCs
- [Story Quests](https://stardewvalleywiki.com/Quests#List_of_Story_Quests)
- [Help Wanted Quests](https://stardewvalleywiki.com/Quests#Help_Wanted_Quests)
- Participating in [Festivals](https://stardewvalleywiki.com/Festivals)
- [Special Orders](https://stardewvalleywiki.com/Quests#List_of_Special_Orders) from the town board, or from [Mr Qi](https://stardewvalleywiki.com/Quests#List_of_Mr._Qi.27s_Special_Orders)
- [Cropsanity](https://stardewvalleywiki.com/Crops): Growing and Harvesting individual crop types
- [Fishsanity](https://stardewvalleywiki.com/Fish): Catching individual fish
- [Museumsanity](https://stardewvalleywiki.com/Museum): Donating individual items, or reaching milestones for museum donations
- [Friendsanity](https://stardewvalleywiki.com/Friendship): Reaching specific friendship levels with NPCs
- [Monstersanity](https://stardewvalleywiki.com/Adventurer%27s_Guild#Monster_Eradication_Goals): Completing monster slayer goals
- [Cooksanity](https://stardewvalleywiki.com/Cooking): Cooking individual recipes
- [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
## Which items can be in another player's world?
@ -55,20 +69,22 @@ For the locations which do not include a normal reward, Resource Packs and traps
A player can enable some settings 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.
* 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.
- 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)
@ -89,38 +105,43 @@ In some cases, like receiving Carpenter and Wizard buildings, the player will st
## Mods
Starting in version 4.x.x, some Stardew Valley mods unrelated to Archipelago are officially "supported".
Some Stardew Valley mods unrelated to Archipelago are officially "supported".
This means that, for these specific mods, if you decide to include them in your yaml settings, the multiworld will be generated with the assumption that you will install and play with these mods.
The multiworld will contain related items and locations for these mods, the specifics will vary from mod to mod
[Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/4.x.x/Documentation/Supported%20Mods.md)
[Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md)
List of supported mods:
- General
- [DeepWoods](https://www.nexusmods.com/stardewvalley/mods/2571)
- [Tractor Mod](https://www.nexusmods.com/stardewvalley/mods/1401)
- [Bigger Backpack](https://www.nexusmods.com/stardewvalley/mods/1845)
- [Skull Cavern Elevator](https://www.nexusmods.com/stardewvalley/mods/963)
* [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
- [Luck Skill](https://www.nexusmods.com/stardewvalley/mods/521)
- [Magic](https://www.nexusmods.com/stardewvalley/mods/2007)
- [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)
- [Binning Skill](https://www.nexusmods.com/stardewvalley/mods/14073)
* [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)
* [Binning Skill](https://www.nexusmods.com/stardewvalley/mods/14073)
- NPCs
- [Ayeisha - The Postal Worker (Custom NPC)](https://www.nexusmods.com/stardewvalley/mods/6427)
- [Mister Ginger (cat npc)](https://www.nexusmods.com/stardewvalley/mods/5295)
- [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)
* [Ayeisha - The Postal Worker (Custom NPC)](https://www.nexusmods.com/stardewvalley/mods/6427)
* [Mister Ginger (cat npc)](https://www.nexusmods.com/stardewvalley/mods/5295)
* [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 [here](https://github.com/Witchybun/SDV-Randomizer-Content-Patcher/releases)
## Multiplayer

View File

@ -4,17 +4,17 @@
- Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/))
- SMAPI ([Mod loader for Stardew Valley](https://smapi.io/))
- [StardewArchipelago Mod Release 4.x.x](https://github.com/agilbert1412/StardewArchipelago/releases)
- It is important to use a mod release of version 4.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.
- [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 can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet.
## Optional Software
- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
- (Only for the TextClient)
* (Only for the TextClient)
- Other Stardew Valley Mods [Nexus Mods](https://www.nexusmods.com/stardewvalley)
- There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/4.x.x/Documentation/Supported%20Mods.md) that you can add to your yaml to include them with the Archipelago randomization
* There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md) that you can add to your yaml to include them with the Archipelago randomization
- It is **not** recommended to further mod Stardew Valley with unsupported mods, although it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs.
- The more unsupported mods you have, and the bigger they are, the more likely things are to break.
* It is **not** recommended to further mod Stardew Valley with unsupported mods, although it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs.
* The more unsupported mods you have, and the bigger they are, the more likely things are to break.
## Configuring your YAML file
@ -80,7 +80,7 @@ For a better chat experience, you can also use the official Archipelago Text Cli
### Playing with supported mods
See the [Supported mods documentation](https://github.com/agilbert1412/StardewArchipelago/blob/4.x.x/Documentation/Supported%20Mods.md)
See the [Supported mods documentation](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md)
### Multiplayer

View File

@ -0,0 +1,65 @@
from random import Random
from .options import BuildingProgression, StardewValleyOptions, BackpackProgression, ExcludeGingerIsland, SeasonRandomization, SpecialOrderLocations, \
Monstersanity, ToolProgression, SkillProgression, Cooksanity, Chefsanity
early_candidate_rate = 4
always_early_candidates = ["Greenhouse", "Desert Obelisk", "Rusty Key"]
seasons = ["Spring", "Summer", "Fall", "Winter"]
def setup_early_items(multiworld, 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")
early_candidates.append("Progressive Coop")
early_candidates.append("Progressive Barn")
if options.backpack_progression == BackpackProgression.option_early_progressive:
early_forced.append("Progressive Backpack")
if options.tool_progression & ToolProgression.option_progressive:
early_forced.append("Progressive Fishing Rod")
early_forced.append("Progressive Pickaxe")
if options.skill_progression == SkillProgression.option_progressive:
early_forced.append("Fishing Level")
if options.quest_locations >= 0:
early_candidates.append("Magnifying Glass")
if options.special_order_locations != SpecialOrderLocations.option_disabled:
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.monstersanity == Monstersanity.option_none:
early_candidates.append("Progressive Weapon")
else:
early_candidates.append("Progressive Sword")
if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
early_candidates.append("Island Obelisk")
early_forced.extend(random.sample(early_candidates, len(early_candidates) // early_candidate_rate))
for item_name in early_forced:
if item_name in multiworld.early_items[player]:
continue
multiworld.early_items[player][item_name] = 1
def add_seasonal_candidates(early_candidates, options):
if options.season_randomization == SeasonRandomization.option_progressive:
early_candidates.extend(["Progressive Season"] * 3)
return
if options.season_randomization == SeasonRandomization.option_disabled:
return
early_candidates.extend(seasons)

View File

@ -8,11 +8,19 @@ from typing import Dict, List, Protocol, Union, Set, Optional
from BaseClasses import Item, ItemClassification
from . import data
from .data.villagers_data import all_villagers
from .data.villagers_data import get_villagers_for_mods
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
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 .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.wallet_item_names import Wallet
ITEM_CODE_OFFSET = 717000
@ -25,21 +33,20 @@ class Group(enum.Enum):
FRIENDSHIP_PACK = enum.auto()
COMMUNITY_REWARD = enum.auto()
TRASH = enum.auto()
MINES_FLOOR_10 = enum.auto()
MINES_FLOOR_20 = enum.auto()
MINES_FLOOR_50 = enum.auto()
MINES_FLOOR_60 = enum.auto()
MINES_FLOOR_80 = enum.auto()
MINES_FLOOR_90 = enum.auto()
MINES_FLOOR_110 = enum.auto()
FOOTWEAR = enum.auto()
HATS = enum.auto()
RING = enum.auto()
WEAPON = enum.auto()
WEAPON_GENERIC = enum.auto()
WEAPON_SWORD = enum.auto()
WEAPON_CLUB = enum.auto()
WEAPON_DAGGER = enum.auto()
WEAPON_SLINGSHOT = enum.auto()
PROGRESSIVE_TOOLS = enum.auto()
SKILL_LEVEL_UP = enum.auto()
BUILDING = enum.auto()
WIZARD_BUILDING = enum.auto()
ARCADE_MACHINE_BUFFS = enum.auto()
GALAXY_WEAPONS = enum.auto()
BASE_RESOURCE = enum.auto()
WARP_TOTEM = enum.auto()
GEODE = enum.auto()
@ -65,7 +72,17 @@ class Group(enum.Enum):
GINGER_ISLAND = enum.auto()
WALNUT_PURCHASE = enum.auto()
TV_CHANNEL = enum.auto()
QI_CRAFTING_RECIPE = enum.auto()
CHEFSANITY = enum.auto()
CHEFSANITY_STARTER = enum.auto()
CHEFSANITY_QOS = enum.auto()
CHEFSANITY_PURCHASE = enum.auto()
CHEFSANITY_FRIENDSHIP = enum.auto()
CHEFSANITY_SKILL = enum.auto()
CRAFTSANITY = enum.auto()
# Mods
MAGIC_SPELL = enum.auto()
MOD_WARP = enum.auto()
@dataclass(frozen=True)
@ -90,7 +107,12 @@ class ItemData:
class StardewItemFactory(Protocol):
def __call__(self, name: Union[str, ItemData]) -> Item:
def __call__(self, name: Union[str, ItemData], override_classification: ItemClassification = None) -> Item:
raise NotImplementedError
class StardewItemDeleter(Protocol):
def __call__(self, item: Item):
raise NotImplementedError
@ -113,8 +135,11 @@ def load_item_csv():
events = [
ItemData(None, "Victory", ItemClassification.progression),
ItemData(None, "Month End", ItemClassification.progression),
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),
]
all_items: List[ItemData] = load_item_csv() + events
@ -138,16 +163,19 @@ initialize_item_table()
initialize_groups()
def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item],
def get_too_many_items_error_message(locations_count: int, items_count: int) -> str:
return f"There should be at least as many locations [{locations_count}] as there are mandatory items [{items_count}]"
def create_items(item_factory: StardewItemFactory, item_deleter: StardewItemDeleter, locations_count: int, items_to_exclude: List[Item],
options: StardewValleyOptions, random: Random) -> List[Item]:
items = []
unique_items = create_unique_items(item_factory, options, random)
for item in items_to_exclude:
if item in unique_items:
unique_items.remove(item)
remove_items(item_deleter, items_to_exclude, unique_items)
remove_items_if_no_room_for_them(item_deleter, unique_items, locations_count, random)
assert len(unique_items) <= locations_count, f"There should be at least as many locations [{locations_count}] as there are mandatory items [{len(unique_items)}]"
items += unique_items
logger.debug(f"Created {len(unique_items)} unique items")
@ -162,38 +190,71 @@ def create_items(item_factory: StardewItemFactory, locations_count: int, items_t
return items
def remove_items(item_deleter: StardewItemDeleter, items_to_remove, items):
for item in items_to_remove:
if item in items:
items.remove(item)
item_deleter(item)
def remove_items_if_no_room_for_them(item_deleter: StardewItemDeleter, unique_items: List[Item], locations_count: int, random: Random):
if len(unique_items) <= locations_count:
return
number_of_items_to_remove = len(unique_items) - locations_count
removable_items = [item for item in unique_items if item.classification == ItemClassification.filler or item.classification == ItemClassification.trap]
if len(removable_items) < number_of_items_to_remove:
logger.debug(f"Player has more items than locations, trying to remove {number_of_items_to_remove} random non-progression items")
removable_items = [item for item in unique_items if not item.classification & ItemClassification.progression]
else:
logger.debug(f"Player has more items than locations, trying to remove {number_of_items_to_remove} random filler items")
assert len(removable_items) >= number_of_items_to_remove, get_too_many_items_error_message(locations_count, len(unique_items))
items_to_remove = random.sample(removable_items, number_of_items_to_remove)
remove_items(item_deleter, items_to_remove, unique_items)
def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, 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
items.append(item_factory(Wallet.metal_detector)) # Always offer at least one metal detector
create_backpack_items(item_factory, options, items)
create_mine_rewards(item_factory, items, random)
create_weapons(item_factory, options, items)
items.append(item_factory("Skull Key"))
create_elevators(item_factory, options, items)
create_tools(item_factory, options, items)
create_skills(item_factory, options, items)
create_wizard_buildings(item_factory, options, items)
create_carpenter_buildings(item_factory, options, items)
items.append(item_factory("Railroad Boulder Removed"))
items.append(item_factory(CommunityUpgrade.fruit_bats))
items.append(item_factory(CommunityUpgrade.mushroom_boxes))
items.append(item_factory("Beach Bridge"))
items.append(item_factory("Dark Talisman"))
create_tv_channels(item_factory, items)
create_special_quest_rewards(item_factory, items)
create_tv_channels(item_factory, options, items)
create_special_quest_rewards(item_factory, options, items)
create_stardrops(item_factory, options, items)
create_museum_items(item_factory, options, items)
create_arcade_machine_items(item_factory, options, items)
items.append(item_factory(random.choice(items_by_group[Group.GALAXY_WEAPONS])))
create_player_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)
create_friendsanity_items(item_factory, options, items, random)
create_festival_rewards(item_factory, options, items)
create_babies(item_factory, items, random)
create_special_order_board_rewards(item_factory, options, items)
create_special_order_qi_rewards(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_goal_items(item_factory, options, items)
items.append(item_factory("Golden Egg"))
create_magic_mod_spells(item_factory, options, items)
create_deepwoods_pendants(item_factory, options, items)
create_archaeology_items(item_factory, options, items)
return items
@ -206,18 +267,27 @@ def create_backpack_items(item_factory: StardewItemFactory, options: StardewVall
items.append(item_factory("Progressive Backpack"))
def create_mine_rewards(item_factory: StardewItemFactory, items: List[Item], random: Random):
items.append(item_factory("Rusty Sword"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_10])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_20])))
items.append(item_factory("Slingshot"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_50])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_60])))
items.append(item_factory("Master Slingshot"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_80])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_90])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_110])))
items.append(item_factory("Skull Key"))
def create_weapons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
weapons = weapons_count(options)
items.extend(item_factory(item) for item in [APWeapon.slingshot] * 2)
monstersanity = options.monstersanity
if monstersanity == Monstersanity.option_none: # Without monstersanity, might not be enough checks to split the weapons
items.extend(item_factory(item) for item in [APWeapon.weapon] * weapons)
items.extend(item_factory(item) for item in [APWeapon.footwear] * 3) # 1-2 | 3-4 | 6-7-8
return
items.extend(item_factory(item) for item in [APWeapon.sword] * weapons)
items.extend(item_factory(item) for item in [APWeapon.club] * weapons)
items.extend(item_factory(item) for item in [APWeapon.dagger] * weapons)
items.extend(item_factory(item) for item in [APWeapon.footwear] * 4) # 1-2 | 3-4 | 6-7-8 | 11-13
if monstersanity == Monstersanity.option_goals or monstersanity == Monstersanity.option_one_per_category or \
monstersanity == Monstersanity.option_short_goals or monstersanity == Monstersanity.option_very_short_goals:
return
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
rings_items = [item for item in items_by_group[Group.RING] if item.classification is not ItemClassification.filler]
else:
rings_items = [item for item in items_by_group[Group.RING]]
items.extend(item_factory(item) for item in rings_items)
def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
@ -232,8 +302,14 @@ def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOpt
def create_tools(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.tool_progression == ToolProgression.option_progressive:
items.extend(item_factory(item) for item in items_by_group[Group.PROGRESSIVE_TOOLS] * 4)
if options.tool_progression & ToolProgression.option_progressive:
for item_data in items_by_group[Group.PROGRESSIVE_TOOLS]:
name = item_data.name
if "Trash Can" in name:
items.extend([item_factory(item) for item in [item_data] * 3])
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"))
@ -246,11 +322,12 @@ def create_skills(item_factory: StardewItemFactory, options: StardewValleyOption
def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
items.append(item_factory("Earth Obelisk"))
items.append(item_factory("Water Obelisk"))
useless_buildings_classification = ItemClassification.progression_skip_balancing if world_is_perfection(options) else ItemClassification.useful
items.append(item_factory("Earth Obelisk", useless_buildings_classification))
items.append(item_factory("Water Obelisk", useless_buildings_classification))
items.append(item_factory("Desert Obelisk"))
items.append(item_factory("Junimo Hut"))
items.append(item_factory("Gold Clock"))
items.append(item_factory("Gold Clock", useless_buildings_classification))
if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
items.append(item_factory("Island Obelisk"))
if ModNames.deepwoods in options.mods:
@ -258,89 +335,107 @@ def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewVa
def create_carpenter_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.building_progression in {BuildingProgression.option_progressive,
BuildingProgression.option_progressive_early_shipping_bin}:
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Well"))
items.append(item_factory("Silo"))
items.append(item_factory("Mill"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Fish Pond"))
items.append(item_factory("Stable"))
items.append(item_factory("Slime Hutch"))
items.append(item_factory("Shipping Bin"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
if ModNames.tractor in options.mods:
items.append(item_factory("Tractor Garage"))
building_option = options.building_progression
if not building_option & BuildingProgression.option_progressive:
return
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Well"))
items.append(item_factory("Silo"))
items.append(item_factory("Mill"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Progressive Shed", ItemClassification.useful))
items.append(item_factory("Fish Pond"))
items.append(item_factory("Stable"))
items.append(item_factory("Slime Hutch"))
items.append(item_factory("Shipping Bin"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
if ModNames.tractor in options.mods:
items.append(item_factory("Tractor Garage"))
def create_special_quest_rewards(item_factory: StardewItemFactory, items: List[Item]):
items.append(item_factory("Adventurer's Guild"))
items.append(item_factory("Club Card"))
items.append(item_factory("Magnifying Glass"))
items.append(item_factory("Bear's Knowledge"))
items.append(item_factory("Iridium Snake Milk"))
def create_special_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.quest_locations < 0:
return
# items.append(item_factory("Adventurer's Guild")) # Now unlocked always!
items.append(item_factory(Wallet.club_card))
items.append(item_factory(Wallet.magnifying_glass))
if ModNames.sve in options.mods:
items.append(item_factory(Wallet.bears_knowledge))
else:
items.append(item_factory(Wallet.bears_knowledge, ItemClassification.useful)) # Not necessary outside of SVE
items.append(item_factory(Wallet.iridium_snake_milk))
items.append(item_factory("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]):
items.append(item_factory("Stardrop")) # The Mines level 100
items.append(item_factory("Stardrop")) # Old Master Cannoli
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:
items.append(item_factory("Stardrop")) #Master Angler Stardrop
items.append(item_factory("Stardrop", stardrops_classification)) # Master Angler Stardrop
if ModNames.deepwoods in options.mods:
items.append(item_factory("Stardrop")) # Petting the Unicorn
items.append(item_factory("Stardrop", stardrops_classification)) # Petting the Unicorn
if options.friendsanity != Friendsanity.option_none:
items.append(item_factory("Stardrop", stardrops_classification)) # Spouse Stardrop
def create_museum_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
items.append(item_factory("Rusty Key"))
items.append(item_factory("Dwarvish Translation Guide"))
items.append(item_factory(Wallet.rusty_key))
items.append(item_factory(Wallet.dwarvish_translation_guide))
items.append(item_factory("Ancient Seeds Recipe"))
items.append(item_factory("Stardrop", get_stardrop_classification(options)))
if options.museumsanity == Museumsanity.option_none:
return
items.extend(item_factory(item) for item in ["Magic Rock Candy"] * 10)
items.extend(item_factory(item) for item in ["Ancient Seeds"] * 5)
items.extend(item_factory(item) for item in ["Traveling Merchant Metal Detector"] * 4)
items.append(item_factory("Stardrop"))
items.append(item_factory(Wallet.metal_detector))
def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
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:
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 all_villagers:
if villager.mod_name not in options.mods and villager.mod_name is not None:
continue
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 == "Leo" and exclude_ginger_island:
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"))
items.append(item_factory(f"{villager.name} <3", classification))
if not exclude_non_bachelors:
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"))
items.append(item_factory(f"Pet <3", ItemClassification.progression_skip_balancing if need_pet else ItemClassification.useful))
def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random):
@ -368,8 +463,19 @@ def create_arcade_machine_items(item_factory: StardewItemFactory, options: Stard
def create_player_buffs(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
items.extend(item_factory(item) for item in [Buff.movement] * options.movement_buff_number.value)
items.extend(item_factory(item) for item in [Buff.luck] * options.luck_buff_number.value)
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)
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
@ -393,17 +499,19 @@ def create_seeds(item_factory: StardewItemFactory, options: StardewValleyOptions
if options.cropsanity == Cropsanity.option_disabled:
return
include_ginger_island = options.exclude_ginger_island != ExcludeGingerIsland.option_true
seed_items = [item_factory(item) for item in items_by_group[Group.CROPSANITY] if include_ginger_island or Group.GINGER_ISLAND not in item.groups]
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)
def create_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
items.append(item_factory("Deluxe Scarecrow Recipe"))
if options.festival_locations == FestivalLocations.option_disabled:
return
items.extend([*[item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler],
item_factory("Stardrop")])
festival_rewards = [item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler]
items.extend([*festival_rewards, item_factory("Stardrop", get_stardrop_classification(options))])
def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
@ -417,26 +525,102 @@ def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: St
*[item_factory(item) for item in items_by_group[Group.WALNUT_PURCHASE]]])
def create_special_order_board_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if options.special_order_locations == SpecialOrderLocations.option_disabled:
return
items.extend([item_factory(item) for item in items_by_group[Group.SPECIAL_ORDER_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])
def special_order_board_item_classification(item: ItemData, need_all_recipes: bool) -> ItemClassification:
if item.classification is ItemClassification.useful:
return ItemClassification.useful
if item.name == "Special Order Board":
return ItemClassification.progression
if need_all_recipes and "Recipe" in item.name:
return ItemClassification.progression_skip_balancing
if item.name == "Monster Musk Recipe":
return ItemClassification.progression_skip_balancing
return ItemClassification.useful
def create_special_order_qi_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if (options.special_order_locations != SpecialOrderLocations.option_board_qi or
options.exclude_ginger_island == ExcludeGingerIsland.option_true):
if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return
qi_gem_rewards = ["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"]
qi_gem_rewards = []
if options.bundle_randomization >= BundleRandomization.option_remixed:
qi_gem_rewards.append("15 Qi Gems")
qi_gem_rewards.append("15 Qi Gems")
if options.special_order_locations == SpecialOrderLocations.option_board_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"])
qi_gem_items = [item_factory(reward) for reward in qi_gem_rewards]
items.extend(qi_gem_items)
def create_tv_channels(item_factory: StardewItemFactory, items: List[Item]):
items.extend([item_factory(item) for item in items_by_group[Group.TV_CHANNEL]])
def create_tv_channels(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
channels = [channel for channel in items_by_group[Group.TV_CHANNEL]]
if options.entrance_randomization == EntranceRandomization.option_disabled:
channels = [channel for channel in channels if channel.name != "The Gateway Gazette"]
items.extend([item_factory(item) for item in channels])
def create_crafting_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
has_craftsanity = options.craftsanity == Craftsanity.option_all
crafting_recipes = []
crafting_recipes.extend([recipe for recipe in items_by_group[Group.QI_CRAFTING_RECIPE]])
if has_craftsanity:
crafting_recipes.extend([recipe for recipe in items_by_group[Group.CRAFTSANITY]])
crafting_recipes = remove_excluded_items(crafting_recipes, options)
items.extend([item_factory(item) for item in crafting_recipes])
def create_cooking_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
chefsanity = options.chefsanity
if chefsanity == Chefsanity.option_none:
return
chefsanity_recipes_by_name = {recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_STARTER]} # Dictionary to not make duplicates
if chefsanity & Chefsanity.option_queen_of_sauce:
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_QOS]})
if chefsanity & Chefsanity.option_purchases:
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_PURCHASE]})
if chefsanity & Chefsanity.option_friendship:
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_FRIENDSHIP]})
if chefsanity & Chefsanity.option_skills:
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_SKILL]})
filtered_chefsanity_recipes = remove_excluded_items(list(chefsanity_recipes_by_name.values()), options)
items.extend([item_factory(item) for item in filtered_chefsanity_recipes])
def create_shipsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
shipsanity = options.shipsanity
if shipsanity != Shipsanity.option_everything:
return
items.append(item_factory(Wallet.metal_detector))
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:
return
items.append(item_factory(Wallet.metal_detector))
def create_archaeology_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
mods = options.mods
if ModNames.archaeology not in mods:
return
items.append(item_factory(Wallet.metal_detector))
def create_filler_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions) -> List[Item]:
@ -449,10 +633,47 @@ def create_filler_festival_rewards(item_factory: StardewItemFactory, options: St
def create_magic_mod_spells(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if ModNames.magic not in options.mods:
return []
return
items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]])
def create_deepwoods_pendants(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
if ModNames.deepwoods not in options.mods:
return
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]):
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:
return
exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
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 = []
@ -464,6 +685,13 @@ def create_unique_filler_items(item_factory: StardewItemFactory, options: Starde
return items
def weapons_count(options: StardewValleyOptions):
weapon_count = 5
if ModNames.sve in options.mods:
weapon_count += 1
return weapon_count
def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
items_already_added: List[Item],
number_locations: int) -> List[Item]:
@ -477,27 +705,31 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options
priority_filler_items = []
priority_filler_items.extend(useful_resource_packs)
if include_traps:
priority_filler_items.extend(trap_items)
exclude_ginger_island = options.exclude_ginger_island == ExcludeGingerIsland.option_true
all_filler_packs = get_all_filler_items(include_traps, exclude_ginger_island)
priority_filler_items = remove_excluded_packs(priority_filler_items, exclude_ginger_island)
all_filler_packs = remove_excluded_items(get_all_filler_items(include_traps, exclude_ginger_island), options)
priority_filler_items = remove_excluded_items(priority_filler_items, options)
number_priority_items = len(priority_filler_items)
required_resource_pack = number_locations - len(items_already_added)
if required_resource_pack < number_priority_items:
chosen_priority_items = [item_factory(resource_pack) for resource_pack in
random.sample(priority_filler_items, required_resource_pack)]
random.sample(priority_filler_items, required_resource_pack)]
return chosen_priority_items
items = []
chosen_priority_items = [item_factory(resource_pack) for resource_pack in priority_filler_items]
chosen_priority_items = [item_factory(resource_pack,
ItemClassification.trap if resource_pack.classification == ItemClassification.trap else ItemClassification.useful)
for resource_pack in priority_filler_items]
items.extend(chosen_priority_items)
required_resource_pack -= number_priority_items
all_filler_packs = [filler_pack for filler_pack in all_filler_packs
if Group.MAXIMUM_ONE not in filler_pack.groups or
filler_pack.name not in [priority_item.name for priority_item in priority_filler_items]]
(filler_pack.name not in [priority_item.name for priority_item in
priority_filler_items] and filler_pack.name not in items_already_added_names)]
while required_resource_pack > 0:
resource_pack = random.choice(all_filler_packs)
@ -505,10 +737,11 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options
while exactly_2 and required_resource_pack == 1:
resource_pack = random.choice(all_filler_packs)
exactly_2 = Group.EXACTLY_TWO in resource_pack.groups
items.append(item_factory(resource_pack))
classification = ItemClassification.useful if resource_pack.classification == ItemClassification.progression else resource_pack.classification
items.append(item_factory(resource_pack, classification))
required_resource_pack -= 1
if exactly_2:
items.append(item_factory(resource_pack))
items.append(item_factory(resource_pack, classification))
required_resource_pack -= 1
if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups:
all_filler_packs.remove(resource_pack)
@ -516,11 +749,27 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options
return items
def remove_excluded_packs(packs, exclude_ginger_island: bool):
included_packs = [pack for pack in packs if Group.DEPRECATED not in pack.groups]
if exclude_ginger_island:
included_packs = [pack for pack in included_packs if Group.GINGER_ISLAND not in pack.groups]
return included_packs
def filter_deprecated_items(items: List[ItemData]) -> List[ItemData]:
return [item for item in items if Group.DEPRECATED not in item.groups]
def filter_ginger_island_items(exclude_island: bool, items: List[ItemData]) -> List[ItemData]:
return [item for item in items if not exclude_island or Group.GINGER_ISLAND not in item.groups]
def filter_mod_items(mods: Set[str], items: List[ItemData]) -> List[ItemData]:
return [item for item in items if item.mod_name is None or item.mod_name in mods]
def remove_excluded_items(items, options: StardewValleyOptions):
return remove_excluded_items_island_mods(items, options.exclude_ginger_island == ExcludeGingerIsland.option_true, options.mods.value)
def remove_excluded_items_island_mods(items, exclude_ginger_island: bool, mods: Set[str]):
deprecated_filter = filter_deprecated_items(items)
ginger_island_filter = filter_ginger_island_items(exclude_ginger_island, deprecated_filter)
mod_filter = filter_mod_items(mods, ginger_island_filter)
return mod_filter
def remove_limited_amount_packs(packs):
@ -528,9 +777,21 @@ def remove_limited_amount_packs(packs):
def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool):
all_filler_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK]]
all_filler_packs.extend(items_by_group[Group.TRASH])
all_filler_items = [pack for pack in items_by_group[Group.RESOURCE_PACK]]
all_filler_items.extend(items_by_group[Group.TRASH])
if include_traps:
all_filler_packs.extend(items_by_group[Group.TRAP])
all_filler_packs = remove_excluded_packs(all_filler_packs, exclude_ginger_island)
return all_filler_packs
all_filler_items.extend(items_by_group[Group.TRAP])
all_filler_items = remove_excluded_items_island_mods(all_filler_items, exclude_ginger_island, set())
return all_filler_items
def get_stardrop_classification(options) -> ItemClassification:
return ItemClassification.progression_skip_balancing if world_is_perfection(options) or world_is_stardrops(options) else ItemClassification.useful
def world_is_perfection(options) -> bool:
return options.goal == Goal.option_perfection
def world_is_stardrops(options) -> bool:
return options.goal == Goal.option_mystery_of_the_stardrops

View File

@ -2,16 +2,21 @@ import csv
import enum
from dataclasses import dataclass
from random import Random
from typing import Optional, Dict, Protocol, List, FrozenSet
from typing import Optional, Dict, Protocol, List, FrozenSet, Iterable
from . import data
from .options import StardewValleyOptions
from .data.fish_data import legendary_fish, special_fish, all_fish
from .bundles.bundle_room import BundleRoom
from .data.fish_data import legendary_fish, special_fish, get_fish_for_mods
from .data.museum_data import all_museum_items
from .data.villagers_data import all_villagers
from .options import ExcludeGingerIsland, Friendsanity, ArcadeMachineLocations, SpecialOrderLocations, Cropsanity, Fishsanity, Museumsanity, FestivalLocations, SkillProgression, BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression
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 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
LOCATION_CODE_OFFSET = 717000
@ -45,7 +50,7 @@ class LocationTags(enum.Enum):
COMBAT_LEVEL = enum.auto()
MINING_LEVEL = enum.auto()
BUILDING_BLUEPRINT = enum.auto()
QUEST = enum.auto()
STORY_QUEST = enum.auto()
ARCADE_MACHINE = enum.auto()
ARCADE_MACHINE_VICTORY = enum.auto()
JOTPK = enum.auto()
@ -60,8 +65,29 @@ class LocationTags(enum.Enum):
FESTIVAL_HARD = enum.auto()
SPECIAL_ORDER_BOARD = enum.auto()
SPECIAL_ORDER_QI = enum.auto()
REQUIRES_QI_ORDERS = enum.auto()
GINGER_ISLAND = enum.auto()
WALNUT_PURCHASE = enum.auto()
BABY = enum.auto()
MONSTERSANITY = enum.auto()
MONSTERSANITY_GOALS = enum.auto()
MONSTERSANITY_PROGRESSIVE_GOALS = enum.auto()
MONSTERSANITY_MONSTER = enum.auto()
SHIPSANITY = enum.auto()
SHIPSANITY_CROP = enum.auto()
SHIPSANITY_FISH = enum.auto()
SHIPSANITY_FULL_SHIPMENT = enum.auto()
COOKSANITY = enum.auto()
COOKSANITY_QOS = enum.auto()
CHEFSANITY = enum.auto()
CHEFSANITY_QOS = enum.auto()
CHEFSANITY_PURCHASE = enum.auto()
CHEFSANITY_FRIENDSHIP = enum.auto()
CHEFSANITY_SKILL = enum.auto()
CHEFSANITY_STARTER = enum.auto()
CRAFTSANITY = enum.auto()
# Mods
# Skill Mods
LUCK_LEVEL = enum.auto()
BINNING_LEVEL = enum.auto()
@ -112,10 +138,17 @@ events_locations = [
LocationData(None, Region.community_center, Goal.community_center),
LocationData(None, Region.mines_floor_120, Goal.bottom_of_the_mines),
LocationData(None, Region.skull_cavern_100, Goal.cryptic_note),
LocationData(None, Region.farm, Goal.master_angler),
LocationData(None, Region.beach, Goal.master_angler),
LocationData(None, Region.museum, Goal.complete_museum),
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, Region.farm, Goal.craft_master),
LocationData(None, Region.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),
]
@ -139,13 +172,20 @@ def extend_cropsanity_locations(randomized_locations: List[LocationData], option
if options.cropsanity == Cropsanity.option_disabled:
return
cropsanity_locations = locations_by_tag[LocationTags.CROPSANITY]
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)
def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_number_of_quests: int):
for i in range(0, desired_number_of_quests):
def extend_quests_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.quest_locations < 0:
return
story_quest_locations = locations_by_tag[LocationTags.STORY_QUEST]
story_quest_locations = filter_disabled_locations(options, story_quest_locations)
randomized_locations.extend(story_quest_locations)
for i in range(0, options.quest_locations.value):
batch = i // 7
index_this_batch = i % 7
if index_this_batch < 4:
@ -161,27 +201,29 @@ def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_
def extend_fishsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
prefix = "Fishsanity: "
if options.fishsanity == Fishsanity.option_none:
fishsanity = options.fishsanity
active_fish = get_fish_for_mods(options.mods.value)
if fishsanity == Fishsanity.option_none:
return
elif options.fishsanity == Fishsanity.option_legendaries:
elif fishsanity == Fishsanity.option_legendaries:
randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish)
elif options.fishsanity == Fishsanity.option_special:
elif fishsanity == Fishsanity.option_special:
randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish)
elif options.fishsanity == Fishsanity.option_randomized:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if random.random() < 0.4]
randomized_locations.extend(filter_ginger_island(options, fish_locations))
elif options.fishsanity == Fishsanity.option_all:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish]
randomized_locations.extend(filter_ginger_island(options, fish_locations))
elif options.fishsanity == Fishsanity.option_exclude_legendaries:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish not in legendary_fish]
randomized_locations.extend(filter_ginger_island(options, fish_locations))
elif options.fishsanity == Fishsanity.option_exclude_hard_fish:
fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish.difficulty < 80]
randomized_locations.extend(filter_ginger_island(options, fish_locations))
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 fish not in legendary_fish]
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 all_fish if fish.difficulty < 50]
randomized_locations.extend(filter_ginger_island(options, fish_locations))
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))
def extend_museumsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random):
@ -191,30 +233,31 @@ def extend_museumsanity_locations(randomized_locations: List[LocationData], opti
elif options.museumsanity == Museumsanity.option_milestones:
randomized_locations.extend(locations_by_tag[LocationTags.MUSEUM_MILESTONES])
elif options.museumsanity == Museumsanity.option_randomized:
randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"]
randomized_locations.extend(location_table[f"{prefix}{museum_item.item_name}"]
for museum_item in all_museum_items if random.random() < 0.4)
elif options.museumsanity == Museumsanity.option_all:
randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"] for museum_item in all_museum_items)
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:
return
exclude_leo = options.exclude_ginger_island == ExcludeGingerIsland.option_true
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 all_villagers:
if villager.mod_name not in options.mods and villager.mod_name is not None:
continue
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 == "Leo" and exclude_leo:
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:
@ -230,6 +273,11 @@ def extend_friendsanity_locations(randomized_locations: List[LocationData], opti
randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"])
def extend_baby_locations(randomized_locations: List[LocationData]):
baby_locations = [location for location in locations_by_tag[LocationTags.BABY]]
randomized_locations.extend(baby_locations)
def extend_festival_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.festival_locations == FestivalLocations.option_disabled:
return
@ -256,7 +304,8 @@ def extend_special_order_locations(randomized_locations: List[LocationData], opt
randomized_locations.extend(board_locations)
if options.special_order_locations == SpecialOrderLocations.option_board_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]
qi_orders = [location for location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI] if
include_arcade or LocationTags.JUNIMO_KART not in location.tags]
randomized_locations.extend(qi_orders)
@ -271,12 +320,31 @@ def extend_walnut_purchase_locations(randomized_locations: List[LocationData], o
randomized_locations.extend(locations_by_tag[LocationTags.WALNUT_PURCHASE])
def extend_mandatory_locations(randomized_locations: List[LocationData], options):
def extend_mandatory_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
mandatory_locations = [location for location in locations_by_tag[LocationTags.MANDATORY]]
filtered_mandatory_locations = filter_disabled_locations(options, mandatory_locations)
randomized_locations.extend(filtered_mandatory_locations)
def extend_situational_quest_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.quest_locations < 0:
return
if ModNames.distant_lands in options.mods:
if ModNames.alecto in options.mods:
randomized_locations.append(location_table[ModQuest.WitchOrder])
else:
randomized_locations.append(location_table[ModQuest.CorruptedCropsTask])
def extend_bundle_locations(randomized_locations: List[LocationData], bundle_rooms: List[BundleRoom]):
for room in bundle_rooms:
room_location = f"Complete {room.name}"
if room_location in location_table:
randomized_locations.append(location_table[room_location])
for bundle in room.bundles:
randomized_locations.append(location_table[bundle.name])
def extend_backpack_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.backpack_progression == BackpackProgression.option_vanilla:
return
@ -293,15 +361,99 @@ def extend_elevator_locations(randomized_locations: List[LocationData], options:
randomized_locations.extend(filtered_elevator_locations)
def extend_monstersanity_locations(randomized_locations: List[LocationData], options):
monstersanity = options.monstersanity
if monstersanity == Monstersanity.option_none:
return
if monstersanity == Monstersanity.option_one_per_monster or monstersanity == Monstersanity.option_split_goals:
monster_locations = [location for location in locations_by_tag[LocationTags.MONSTERSANITY_MONSTER]]
filtered_monster_locations = filter_disabled_locations(options, monster_locations)
randomized_locations.extend(filtered_monster_locations)
return
goal_locations = [location for location in locations_by_tag[LocationTags.MONSTERSANITY_GOALS]]
filtered_goal_locations = filter_disabled_locations(options, goal_locations)
randomized_locations.extend(filtered_goal_locations)
if monstersanity != Monstersanity.option_progressive_goals:
return
progressive_goal_locations = [location for location in locations_by_tag[LocationTags.MONSTERSANITY_PROGRESSIVE_GOALS]]
filtered_progressive_goal_locations = filter_disabled_locations(options, progressive_goal_locations)
randomized_locations.extend(filtered_progressive_goal_locations)
def extend_shipsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
shipsanity = options.shipsanity
if shipsanity == Shipsanity.option_none:
return
if shipsanity == Shipsanity.option_everything:
ship_locations = [location for location in locations_by_tag[LocationTags.SHIPSANITY]]
filtered_ship_locations = filter_disabled_locations(options, ship_locations)
randomized_locations.extend(filtered_ship_locations)
return
shipsanity_locations = set()
if shipsanity == Shipsanity.option_fish or shipsanity == Shipsanity.option_full_shipment_with_fish:
shipsanity_locations = shipsanity_locations.union({location for location in locations_by_tag[LocationTags.SHIPSANITY_FISH]})
if shipsanity == Shipsanity.option_crops:
shipsanity_locations = shipsanity_locations.union({location for location in locations_by_tag[LocationTags.SHIPSANITY_CROP]})
if shipsanity == Shipsanity.option_full_shipment or shipsanity == Shipsanity.option_full_shipment_with_fish:
shipsanity_locations = shipsanity_locations.union({location for location in locations_by_tag[LocationTags.SHIPSANITY_FULL_SHIPMENT]})
filtered_shipsanity_locations = filter_disabled_locations(options, list(shipsanity_locations))
randomized_locations.extend(filtered_shipsanity_locations)
def extend_cooksanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
cooksanity = options.cooksanity
if cooksanity == Cooksanity.option_none:
return
if cooksanity == Cooksanity.option_queen_of_sauce:
cooksanity_locations = (location for location in locations_by_tag[LocationTags.COOKSANITY_QOS])
else:
cooksanity_locations = (location for location in locations_by_tag[LocationTags.COOKSANITY])
filtered_cooksanity_locations = filter_disabled_locations(options, cooksanity_locations)
randomized_locations.extend(filtered_cooksanity_locations)
def extend_chefsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
chefsanity = options.chefsanity
if chefsanity == Chefsanity.option_none:
return
chefsanity_locations_by_name = {} # Dictionary to not make duplicates
if chefsanity & Chefsanity.option_queen_of_sauce:
chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_QOS]})
if chefsanity & Chefsanity.option_purchases:
chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_PURCHASE]})
if chefsanity & Chefsanity.option_friendship:
chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_FRIENDSHIP]})
if chefsanity & Chefsanity.option_skills:
chefsanity_locations_by_name.update({location.name: location for location in locations_by_tag[LocationTags.CHEFSANITY_SKILL]})
filtered_chefsanity_locations = filter_disabled_locations(options, list(chefsanity_locations_by_name.values()))
randomized_locations.extend(filtered_chefsanity_locations)
def extend_craftsanity_locations(randomized_locations: List[LocationData], options: StardewValleyOptions):
if options.craftsanity == Craftsanity.option_none:
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)
def create_locations(location_collector: StardewLocationCollector,
bundle_rooms: List[BundleRoom],
options: StardewValleyOptions,
random: Random):
randomized_locations = []
extend_mandatory_locations(randomized_locations, options)
extend_bundle_locations(randomized_locations, bundle_rooms)
extend_backpack_locations(randomized_locations, options)
if not options.tool_progression == ToolProgression.option_vanilla:
if options.tool_progression & ToolProgression.option_progressive:
randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE])
extend_elevator_locations(randomized_locations, options)
@ -311,7 +463,7 @@ def create_locations(location_collector: StardewLocationCollector,
if location.mod_name is None or location.mod_name in options.mods:
randomized_locations.append(location_table[location.name])
if not options.building_progression == BuildingProgression.option_vanilla:
if options.building_progression & BuildingProgression.option_progressive:
for location in locations_by_tag[LocationTags.BUILDING_BLUEPRINT]:
if location.mod_name is None or location.mod_name in options.mods:
randomized_locations.append(location_table[location.name])
@ -323,7 +475,6 @@ def create_locations(location_collector: StardewLocationCollector,
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE])
extend_cropsanity_locations(randomized_locations, options)
extend_help_wanted_quests(randomized_locations, options.help_wanted_locations.value)
extend_fishsanity_locations(randomized_locations, options, random)
extend_museumsanity_locations(randomized_locations, options, random)
extend_friendsanity_locations(randomized_locations, options)
@ -332,21 +483,34 @@ def create_locations(location_collector: StardewLocationCollector,
extend_special_order_locations(randomized_locations, options)
extend_walnut_purchase_locations(randomized_locations, options)
extend_monstersanity_locations(randomized_locations, options)
extend_shipsanity_locations(randomized_locations, options)
extend_cooksanity_locations(randomized_locations, options)
extend_chefsanity_locations(randomized_locations, options)
extend_craftsanity_locations(randomized_locations, options)
extend_quests_locations(randomized_locations, options)
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_ginger_island(options: StardewValleyOptions, locations: List[LocationData]) -> List[LocationData]:
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]
return (location for location in locations if include_island or LocationTags.GINGER_ISLAND not in location.tags)
def filter_modded_locations(options: StardewValleyOptions, locations: List[LocationData]) -> List[LocationData]:
current_mod_names = options.mods
return [location for location in locations if location.mod_name is None or location.mod_name in current_mod_names]
def filter_qi_order_locations(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]:
include_qi_orders = options.special_order_locations == SpecialOrderLocations.option_board_qi
return (location for location in locations if include_qi_orders or LocationTags.REQUIRES_QI_ORDERS not in location.tags)
def filter_disabled_locations(options: StardewValleyOptions, locations: List[LocationData]) -> List[LocationData]:
locations_first_pass = filter_ginger_island(options, locations)
locations_second_pass = filter_modded_locations(options, locations_first_pass)
return locations_second_pass
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_qi_filter = filter_qi_order_locations(options, locations_island_filter)
locations_mod_filter = filter_modded_locations(options, locations_qi_filter)
return locations_mod_filter

File diff suppressed because it is too large Load Diff

View File

View File

@ -0,0 +1,46 @@
from typing import Union
from .base_logic import BaseLogicMixin, BaseLogic
from .mine_logic import MineLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .skill_logic import SkillLogicMixin
from .tool_logic import ToolLogicMixin
from ..mods.logic.magic_logic import MagicLogicMixin
from ..stardew_rule import StardewRule
from ..strings.region_names import Region
from ..strings.skill_names import Skill, ModSkill
from ..strings.tool_names import ToolMaterial, Tool
class AbilityLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ability = AbilityLogic(*args, **kwargs)
class AbilityLogic(BaseLogic[Union[AbilityLogicMixin, RegionLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, MineLogicMixin, MagicLogicMixin]]):
def can_mine_perfectly(self) -> StardewRule:
return self.logic.mine.can_progress_in_the_mines_from_floor(160)
def can_mine_perfectly_in_the_skull_cavern(self) -> StardewRule:
return (self.logic.ability.can_mine_perfectly() &
self.logic.region.can_reach(Region.skull_cavern))
def can_farm_perfectly(self) -> StardewRule:
tool_rule = self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iridium) & self.logic.tool.can_water(4)
return tool_rule & self.logic.skill.has_farming_level(10)
def can_fish_perfectly(self) -> StardewRule:
skill_rule = self.logic.skill.has_level(Skill.fishing, 10)
return skill_rule & self.logic.tool.has_fishing_rod(4)
def can_chop_trees(self) -> StardewRule:
return self.logic.tool.has_tool(Tool.axe) & self.logic.region.can_reach(Region.forest)
def can_chop_perfectly(self) -> StardewRule:
magic_rule = (self.logic.magic.can_use_clear_debris_instead_of_tool_level(3)) & self.logic.mod.skill.has_mod_level(ModSkill.magic, 10)
tool_rule = self.logic.tool.has_tool(Tool.axe, ToolMaterial.iridium)
foraging_rule = self.logic.skill.has_level(Skill.foraging, 10)
region_rule = self.logic.region.can_reach(Region.forest)
return region_rule & ((tool_rule & foraging_rule) | magic_rule)

View File

@ -0,0 +1,40 @@
from typing import Union
from Utils import cache_self1
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 ..strings.generic_names import Generic
from ..strings.geode_names import Geode
from ..strings.region_names import Region
class ActionLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.action = ActionLogic(*args, **kwargs)
class ActionLogic(BaseLogic[Union[ActionLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
def can_watch(self, channel: str = None):
tv_rule = True_()
if channel is None:
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()
@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.has(geode)

View File

@ -0,0 +1,59 @@
from typing import Union
from .base_logic import BaseLogicMixin, BaseLogic
from .building_logic import BuildingLogicMixin
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from ..stardew_rule import StardewRule, true_
from ..strings.animal_names import Animal, coop_animals, barn_animals
from ..strings.building_names import Building
from ..strings.forageable_names import Forageable
from ..strings.generic_names import Generic
from ..strings.region_names import Region
cost_and_building_by_animal = {
Animal.chicken: (800, Building.coop),
Animal.cow: (1500, Building.barn),
Animal.goat: (4000, Building.big_barn),
Animal.duck: (1200, Building.big_coop),
Animal.sheep: (8000, Building.deluxe_barn),
Animal.rabbit: (8000, Building.deluxe_coop),
Animal.pig: (16000, Building.deluxe_barn)
}
class AnimalLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.animal = AnimalLogic(*args, **kwargs)
class AnimalLogic(BaseLogic[Union[HasLogicMixin, MoneyLogicMixin, BuildingLogicMixin]]):
def can_buy_animal(self, animal: str) -> StardewRule:
try:
price, building = cost_and_building_by_animal[animal]
except KeyError:
return true_
return self.logic.money.can_spend_at(Region.ranch, price) & self.logic.building.has_building(building)
def has_animal(self, animal: str) -> StardewRule:
if animal == Generic.any:
return self.has_any_animal()
elif animal == Building.coop:
return self.has_any_coop_animal()
elif animal == Building.barn:
return self.has_any_barn_animal()
return self.logic.has(animal)
def has_happy_animal(self, animal: str) -> StardewRule:
return self.has_animal(animal) & self.logic.has(Forageable.hay)
def has_any_animal(self) -> StardewRule:
return self.has_any_coop_animal() | self.has_any_barn_animal()
def has_any_coop_animal(self) -> StardewRule:
return self.logic.has_any(*coop_animals)
def has_any_barn_animal(self) -> StardewRule:
return self.logic.has_any(*barn_animals)

View File

@ -0,0 +1,34 @@
from typing import Union
from .base_logic import BaseLogic, BaseLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .. import options
from ..stardew_rule import StardewRule, True_
from ..strings.region_names import Region
class ArcadeLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.arcade = ArcadeLogic(*args, **kwargs)
class ArcadeLogic(BaseLogic[Union[ArcadeLogicMixin, RegionLogicMixin, ReceivedLogicMixin]]):
def has_jotpk_power_level(self, power_level: int) -> StardewRule:
if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling:
return True_()
jotpk_buffs = ("JotPK: Progressive Boots", "JotPK: Progressive Gun", "JotPK: Progressive Ammo", "JotPK: Extra Life", "JotPK: Increased Drop Rate")
return self.logic.received_n(*jotpk_buffs, count=power_level)
def has_junimo_kart_power_level(self, power_level: int) -> StardewRule:
if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling:
return True_()
return self.logic.received("Junimo Kart: Extra Life", power_level)
def has_junimo_kart_max_level(self) -> StardewRule:
play_rule = self.logic.region.can_reach(Region.junimo_kart_3)
if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling:
return play_rule
return self.logic.arcade.has_junimo_kart_power_level(8)

View File

@ -0,0 +1,53 @@
from typing import Union
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .time_logic import TimeLogicMixin
from ..stardew_rule import StardewRule
from ..strings.crop_names import all_vegetables, all_fruits, Vegetable, Fruit
from ..strings.generic_names import Generic
from ..strings.machine_names import Machine
class ArtisanLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.artisan = ArtisanLogic(*args, **kwargs)
class ArtisanLogic(BaseLogic[Union[ArtisanLogicMixin, TimeLogicMixin, HasLogicMixin]]):
def has_jelly(self) -> StardewRule:
return self.logic.artisan.can_preserves_jar(Fruit.any)
def has_pickle(self) -> StardewRule:
return self.logic.artisan.can_preserves_jar(Vegetable.any)
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)
if item == Vegetable.any:
return machine_rule & self.logic.has_any(*all_vegetables)
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)
if item == Vegetable.any:
return machine_rule & self.logic.has_any(*all_vegetables)
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)

View File

@ -0,0 +1,50 @@
from __future__ import annotations
from typing import TypeVar, Generic, Dict, Collection
from ..options import StardewValleyOptions
from ..stardew_rule import StardewRule
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.fish_rules: Dict[str, StardewRule] = {}
self.museum_rules: Dict[str, StardewRule] = {}
self.festival_rules: Dict[str, StardewRule] = {}
self.quest_rules: Dict[str, StardewRule] = {}
self.building_rules: Dict[str, StardewRule] = {}
self.special_order_rules: Dict[str, StardewRule] = {}
self.sve_location_rules: Dict[str, StardewRule] = {}
class BaseLogicMixin:
def __init__(self, *args, **kwargs):
pass
T = TypeVar("T", bound=BaseLogicMixin)
class BaseLogic(BaseLogicMixin, Generic[T]):
player: int
registry: LogicRegistry
options: StardewValleyOptions
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)
self.player = player
self.registry = registry
self.options = options
self.regions = regions
self.logic = logic

View File

@ -0,0 +1,23 @@
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

@ -0,0 +1,95 @@
from typing import Dict, Union
from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from ..options import BuildingProgression
from ..stardew_rule import StardewRule, True_, False_, Has
from ..strings.ap_names.event_names import Event
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.fish_names import WaterItem
from ..strings.material_names import Material
from ..strings.metal_names import MetalBar
class BuildingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.building = BuildingLogic(*args, **kwargs)
class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
def initialize_rules(self):
self.registry.building_rules.update({
# @formatter:off
Building.barn: self.logic.money.can_spend(6000) & self.logic.has_all(Material.wood, Material.stone),
Building.big_barn: self.logic.money.can_spend(12000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.barn),
Building.deluxe_barn: self.logic.money.can_spend(25000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.big_barn),
Building.coop: self.logic.money.can_spend(4000) & self.logic.has_all(Material.wood, Material.stone),
Building.big_coop: self.logic.money.can_spend(10000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.coop),
Building.deluxe_coop: self.logic.money.can_spend(20000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.big_coop),
Building.fish_pond: self.logic.money.can_spend(5000) & self.logic.has_all(Material.stone, WaterItem.seaweed, WaterItem.green_algae),
Building.mill: self.logic.money.can_spend(2500) & self.logic.has_all(Material.stone, Material.wood, ArtisanGood.cloth),
Building.shed: self.logic.money.can_spend(15000) & self.logic.has(Material.wood),
Building.big_shed: self.logic.money.can_spend(20000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.shed),
Building.silo: self.logic.money.can_spend(100) & self.logic.has_all(Material.stone, Material.clay, MetalBar.copper),
Building.slime_hutch: self.logic.money.can_spend(10000) & self.logic.has_all(Material.stone, MetalBar.quartz, MetalBar.iridium),
Building.stable: self.logic.money.can_spend(10000) & self.logic.has_all(Material.hardwood, MetalBar.iron),
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.cellar: self.logic.money.can_spend(100000) & self.logic.building.has_house(2),
# @formatter:on
})
def update_rules(self, new_rules: Dict[str, StardewRule]):
self.registry.building_rules.update(new_rules)
@cache_self1
def has_building(self, building: str) -> StardewRule:
# Shipping bin is special. The mod auto-builds it when received, no need to go to Robin.
if building is Building.shipping_bin:
if not self.options.building_progression & BuildingProgression.option_progressive:
return True_()
return self.logic.received(building)
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
count = 1
if building in [Building.coop, Building.barn, Building.shed]:
building = f"Progressive {building}"
elif building.startswith("Big"):
count = 2
building = " ".join(["Progressive", *building.split(" ")[1:]])
elif building.startswith("Deluxe"):
count = 3
building = " ".join(["Progressive", *building.split(" ")[1:]])
return self.logic.received(building, count) & carpenter_rule
@cache_self1
def has_house(self, upgrade_level: int) -> StardewRule:
if upgrade_level < 1:
return True_()
if upgrade_level > 3:
return False_()
carpenter_rule = self.logic.received(Event.can_construct_buildings)
if self.options.building_progression & BuildingProgression.option_progressive:
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)
if upgrade_level == 2:
return carpenter_rule & Has(Building.kids_room, self.registry.building_rules)
# if upgrade_level == 3:
return carpenter_rule & Has(Building.cellar, self.registry.building_rules)

View File

@ -0,0 +1,66 @@
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 .region_logic import RegionLogicMixin
from .skill_logic import SkillLogicMixin
from ..bundles.bundle import Bundle
from ..stardew_rule import StardewRule, And, True_
from ..strings.currency_names import Currency
from ..strings.machine_names import Machine
from ..strings.quality_names import CropQuality, ForageQuality, FishQuality, ArtisanQuality
from ..strings.region_names import Region
class BundleLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bundle = BundleLogic(*args, **kwargs)
class BundleLogic(BaseLogic[Union[HasLogicMixin, RegionLogicMixin, MoneyLogicMixin, FarmingLogicMixin, FishingLogicMixin, SkillLogicMixin]]):
# Should be cached
def can_complete_bundle(self, bundle: Bundle) -> StardewRule:
item_rules = []
qualities = []
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)
item_rules.append(bundle_item.item_name)
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
def get_quality_rules(self, qualities: List[str]) -> StardewRule:
crop_quality = CropQuality.get_highest(qualities)
fish_quality = FishQuality.get_highest(qualities)
forage_quality = ForageQuality.get_highest(qualities)
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))
if fish_quality != FishQuality.basic:
quality_rules.append(self.logic.fishing.can_catch_quality_fish(fish_quality))
if forage_quality != ForageQuality.basic:
quality_rules.append(self.logic.skill.can_forage_quality(forage_quality))
if artisan_quality != ArtisanQuality.basic:
quality_rules.append(self.logic.has(Machine.cask))
if not quality_rules:
return True_()
return And(*quality_rules)
@cached_property
def can_complete_community_center(self) -> StardewRule:
return (self.logic.region.can_reach_location("Complete Crafts Room") &
self.logic.region.can_reach_location("Complete Pantry") &
self.logic.region.can_reach_location("Complete Fish Tank") &
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"))

View File

@ -0,0 +1,57 @@
from functools import cached_property
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
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 ..strings.ap_names.ap_weapon_names import APWeapon
from ..strings.performance_names import Performance
valid_weapons = (APWeapon.weapon, APWeapon.sword, APWeapon.club, APWeapon.dagger)
class CombatLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.combat = CombatLogic(*args, **kwargs)
class CombatLogic(BaseLogic[Union[CombatLogicMixin, RegionLogicMixin, ReceivedLogicMixin, MagicLogicMixin]]):
@cache_self1
def can_fight_at_level(self, level: str) -> StardewRule:
if level == Performance.basic:
return self.logic.combat.has_any_weapon | self.logic.magic.has_any_spell()
if level == Performance.decent:
return self.logic.combat.has_decent_weapon | self.logic.magic.has_decent_spells()
if level == Performance.good:
return self.logic.combat.has_good_weapon | self.logic.magic.has_good_spells()
if level == Performance.great:
return self.logic.combat.has_great_weapon | self.logic.magic.has_great_spells()
if level == Performance.galaxy:
return self.logic.combat.has_galaxy_weapon | self.logic.magic.has_amazing_spells()
if level == Performance.maximum:
return self.logic.combat.has_galaxy_weapon | self.logic.magic.has_amazing_spells() # Someday we will have the ascended weapons in AP
return False_()
@cached_property
def has_any_weapon(self) -> StardewRule:
return self.logic.received_any(*valid_weapons)
@cached_property
def has_decent_weapon(self) -> StardewRule:
return 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))
@cached_property
def has_great_weapon(self) -> StardewRule:
return 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))

View File

@ -0,0 +1,108 @@
from functools import cached_property
from typing import Union
from Utils import cache_self1
from .action_logic import ActionLogicMixin
from .base_logic import BaseLogicMixin, BaseLogic
from .building_logic import BuildingLogicMixin
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
from .season_logic import SeasonLogicMixin
from .skill_logic import SkillLogicMixin
from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \
QueenOfSauceSource, CookingRecipe, ShopFriendshipSource, \
all_cooking_recipes_by_name
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 ..strings.skill_names import Skill
from ..strings.tv_channel_names import Channel
class CookingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cooking = CookingLogic(*args, **kwargs)
class CookingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin,
BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]):
@cached_property
def can_cook_in_kitchen(self) -> StardewRule:
return self.logic.building.has_house(1) | self.logic.skill.has_level(Skill.foraging, 9)
# Should be cached
def can_cook(self, recipe: CookingRecipe = None) -> StardewRule:
cook_rule = self.logic.region.can_reach(Region.kitchen)
if recipe is None:
return cook_rule
recipe_rule = self.logic.cooking.knows_recipe(recipe.source, recipe.meal)
ingredients_rule = self.logic.has_all(*sorted(recipe.ingredients))
return cook_rule & recipe_rule & ingredients_rule
# Should be cached
def knows_recipe(self, source: RecipeSource, meal_name: str) -> StardewRule:
if self.options.chefsanity == Chefsanity.option_none:
return self.logic.cooking.can_learn_recipe(source)
if isinstance(source, StarterSource):
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, ShopTradeSource) and self.options.chefsanity & Chefsanity.option_purchases:
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, ShopSource) and self.options.chefsanity & Chefsanity.option_purchases:
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, SkillSource) and self.options.chefsanity & Chefsanity.option_skills:
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, CutsceneSource) and self.options.chefsanity & Chefsanity.option_friendship:
return self.logic.cooking.received_recipe(meal_name)
if isinstance(source, FriendshipSource) and self.options.chefsanity & Chefsanity.option_friendship:
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:
return self.logic.cooking.received_recipe(meal_name)
return self.logic.cooking.can_learn_recipe(source)
@cache_self1
def can_learn_recipe(self, source: RecipeSource) -> StardewRule:
if isinstance(source, StarterSource):
return True_()
if isinstance(source, ShopTradeSource):
return self.logic.money.can_trade_at(source.region, source.currency, source.price)
if isinstance(source, ShopSource):
return self.logic.money.can_spend_at(source.region, source.price)
if isinstance(source, SkillSource):
return self.logic.skill.has_level(source.skill, source.level)
if isinstance(source, CutsceneSource):
return self.logic.region.can_reach(source.region) & self.logic.relationship.has_hearts(source.friend, source.hearts)
if isinstance(source, FriendshipSource):
return self.logic.relationship.has_hearts(source.friend, source.hearts)
if isinstance(source, QueenOfSauceSource):
return self.logic.action.can_watch(Channel.queen_of_sauce) & self.logic.season.has(source.season)
if isinstance(source, ShopFriendshipSource):
return self.logic.money.can_spend_at(source.region, source.price) & self.logic.relationship.has_hearts(source.friend, source.hearts)
return False_()
@cache_self1
def received_recipe(self, meal_name: str):
return self.logic.received(f"{meal_name} Recipe")
@cached_property
def can_cook_everything(self) -> StardewRule:
cooksanity_prefix = "Cook "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
for location in locations_by_tag[LocationTags.COOKSANITY]:
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
continue
if location.mod_name and location.mod_name not in self.options.mods:
continue
all_recipes_names.append(location.name[len(cooksanity_prefix):])
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
return And(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))

View File

@ -0,0 +1,111 @@
from functools import cached_property
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .money_logic import MoneyLogicMixin
from .quest_logic import QuestLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
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
from ..locations import locations_by_tag, LocationTags
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland
from ..stardew_rule import StardewRule, True_, False_, And
from ..strings.region_names import Region
class CraftingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.crafting = CraftingLogic(*args, **kwargs)
class CraftingLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, MoneyLogicMixin, RelationshipLogicMixin,
SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
@cache_self1
def can_craft(self, recipe: CraftingRecipe = None) -> StardewRule:
if recipe is None:
return True_()
learn_rule = self.logic.crafting.knows_recipe(recipe)
ingredients_rule = self.logic.has_all(*recipe.ingredients)
return learn_rule & ingredients_rule
@cache_self1
def knows_recipe(self, recipe: CraftingRecipe) -> StardewRule:
if isinstance(recipe.source, ArchipelagoSource):
return self.logic.received_all(*recipe.source.ap_item)
if isinstance(recipe.source, FestivalShopSource):
if self.options.festival_locations == options.FestivalLocations.option_disabled:
return self.logic.crafting.can_learn_recipe(recipe)
else:
return self.logic.crafting.received_recipe(recipe.item)
if isinstance(recipe.source, QuestSource):
if self.options.quest_locations < 0:
return self.logic.crafting.can_learn_recipe(recipe)
else:
return self.logic.crafting.received_recipe(recipe.item)
if self.options.craftsanity == Craftsanity.option_none:
return self.logic.crafting.can_learn_recipe(recipe)
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:
return self.logic.crafting.received_recipe(recipe.item)
return self.logic.crafting.can_learn_recipe(recipe)
@cache_self1
def can_learn_recipe(self, recipe: CraftingRecipe) -> StardewRule:
if isinstance(recipe.source, StarterSource):
return True_()
if isinstance(recipe.source, ArchipelagoSource):
return self.logic.received_all(*recipe.source.ap_item)
if isinstance(recipe.source, ShopTradeSource):
return self.logic.money.can_trade_at(recipe.source.region, recipe.source.currency, recipe.source.price)
if isinstance(recipe.source, ShopSource):
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, 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):
return self.logic.relationship.has_hearts(recipe.source.friend, recipe.source.hearts)
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)
return self.logic.crafting.received_recipe(recipe.item)
if isinstance(recipe.source, LogicSource):
if recipe.source.logic_rule == "Cellar":
return self.logic.region.can_reach(Region.cellar)
return False_()
@cache_self1
def received_recipe(self, item_name: str):
return self.logic.received(f"{item_name} Recipe")
@cached_property
def can_craft_everything(self) -> StardewRule:
craftsanity_prefix = "Craft "
all_recipes_names = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
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 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))

View File

@ -0,0 +1,72 @@
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

@ -0,0 +1,41 @@
from typing import Union
from .base_logic import BaseLogicMixin, BaseLogic
from .has_logic import HasLogicMixin
from .skill_logic import SkillLogicMixin
from ..stardew_rule import StardewRule, True_, False_
from ..strings.fertilizer_names import Fertilizer
from ..strings.quality_names import CropQuality
class FarmingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.farming = FarmingLogic(*args, **kwargs)
class FarmingLogic(BaseLogic[Union[HasLogicMixin, SkillLogicMixin, FarmingLogicMixin]]):
def has_fertilizer(self, tier: int) -> StardewRule:
if tier <= 0:
return True_()
if tier == 1:
return self.logic.has(Fertilizer.basic)
if tier == 2:
return self.logic.has(Fertilizer.quality)
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_()

View File

@ -0,0 +1,100 @@
from typing import Union, List
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
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 ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, True_, False_, And
from ..strings.fish_names import SVEFish
from ..strings.quality_names import FishQuality
from ..strings.region_names import Region
from ..strings.skill_names import Skill
class FishingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fishing = FishingLogic(*args, **kwargs)
class FishingLogic(BaseLogic[Union[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
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
def can_fish_at(self, region: str) -> StardewRule:
return self.logic.skill.can_fish() & self.logic.region.can_reach(region)
@cache_self1
def can_catch_fish(self, fish: FishItem) -> StardewRule:
quest_rule = True_()
if fish.extended_family:
quest_rule = self.logic.fishing.can_start_extended_family_quest()
region_rule = self.logic.region.can_reach_any(fish.locations)
season_rule = self.logic.season.has_any(fish.seasons)
if fish.difficulty == -1:
difficulty_rule = self.logic.skill.can_crab_pot
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")
else:
item_rule = True_()
return quest_rule & region_rule & season_rule & difficulty_rule & item_rule
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:
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))
def can_catch_quality_fish(self, fish_quality: str) -> StardewRule:
if fish_quality == FishQuality.basic:
return True_()
rod_rule = self.logic.tool.has_fishing_rod(2)
if fish_quality == FishQuality.silver:
return rod_rule
if fish_quality == FishQuality.gold:
return rod_rule & self.logic.skill.has_level(Skill.fishing, 4)
if fish_quality == FishQuality.iridium:
return rod_rule & self.logic.skill.has_level(Skill.fishing, 10)
return False_()
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:
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)

View File

@ -0,0 +1,20 @@
from functools import cached_property
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from ..stardew_rule import StardewRule
from ..strings.animal_product_names import AnimalProduct
from ..strings.gift_names import Gift
class GiftLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gifts = GiftLogic(*args, **kwargs)
class GiftLogic(BaseLogic[HasLogicMixin]):
@cached_property
def has_any_universal_love(self) -> StardewRule:
return self.logic.has_any(Gift.golden_pumpkin, Gift.pearl, "Prismatic Shard", AnimalProduct.rabbit_foot)

View File

@ -0,0 +1,34 @@
from .base_logic import BaseLogic
from ..stardew_rule import StardewRule, And, Or, Has, Count
class HasLogicMixin(BaseLogic[None]):
# Should be cached
def has(self, item: str) -> StardewRule:
return Has(item, self.registry.item_rules)
def has_all(self, *items: str):
assert items, "Can't have all of no items."
return 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))
def has_n(self, *items: str, count: int):
return self.count(count, *(self.has(item) for item in items))
@staticmethod
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"
if count == 1:
return Or(*rules)
if count == len(rules):
return And(*rules)
return Count(list(rules), count)

View File

@ -0,0 +1,673 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Collection
from .ability_logic import AbilityLogicMixin
from .action_logic import ActionLogicMixin
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 .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 .has_logic import HasLogicMixin
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 .quest_logic import QuestLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
from .season_logic import SeasonLogicMixin
from .shipping_logic import ShippingLogicMixin
from .skill_logic import SkillLogicMixin
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 ..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 ..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.community_upgrade_names import CommunityUpgrade
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.craftable_names import Consumable, Furniture, Ring, Fishing, Lighting, WildSeeds
from ..strings.crop_names import Fruit, Vegetable
from ..strings.currency_names import Currency
from ..strings.decoration_names import Decoration
from ..strings.fertilizer_names import Fertilizer, SpeedGro, RetainingSoil
from ..strings.festival_check_names import FestivalCheck
from ..strings.fish_names import Fish, Trash, WaterItem, WaterChest
from ..strings.flower_names import Flower
from ..strings.food_names import Meal, Beverage
from ..strings.forageable_names import Forageable
from ..strings.fruit_tree_names import Sapling
from ..strings.generic_names import Generic
from ..strings.geode_names import Geode
from ..strings.gift_names import Gift
from ..strings.ingredient_names import Ingredient
from ..strings.machine_names import Machine
from ..strings.material_names import Material
from ..strings.metal_names import Ore, MetalBar, Mineral, Fossil
from ..strings.monster_drop_names import Loot
from ..strings.monster_names import Monster
from ..strings.region_names import Region
from ..strings.season_names import Season
from ..strings.seed_names import Seed, TreeSeed
from ..strings.skill_names import Skill
from ..strings.tool_names import Tool, ToolMaterial
from ..strings.villager_names import NPC
from ..strings.wallet_item_names import Wallet
@dataclass(frozen=False, repr=False)
class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogicMixin, TravelingMerchantLogicMixin, TimeLogicMixin,
SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, GiftLogicMixin,
BuildingLogicMixin, ShippingLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, WalletLogicMixin, AnimalLogicMixin,
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, CropLogicMixin,
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin):
player: int
options: StardewValleyOptions
regions: Collection[str]
def __init__(self, player: int, options: StardewValleyOptions, regions: Collection[str]):
self.registry = LogicRegistry()
super().__init__(player, self.registry, options, 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.museum_rules.update({donation.item_name: self.museum.can_find_museum_item(donation) for donation in all_museum_items})
for recipe in all_cooking_recipes:
if recipe.mod_name and recipe.mod_name not in self.options.mods:
continue
can_cook_rule = self.cooking.can_cook(recipe)
if recipe.meal in self.registry.cooking_rules:
can_cook_rule = can_cook_rule | self.registry.cooking_rules[recipe.meal]
self.registry.cooking_rules[recipe.meal] = can_cook_rule
for recipe in all_crafting_recipes:
if recipe.mod_name and recipe.mod_name not in self.options.mods:
continue
can_craft_rule = self.crafting.can_craft(recipe)
if recipe.item in self.registry.crafting_rules:
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),
})
# @formatter:off
self.registry.item_rules.update({
"Energy Tonic": self.money.can_spend_at(Region.hospital, 1000),
WaterChest.fishing_chest: self.fishing.can_fish_chests(),
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),
"JotPK Big Buff": self.arcade.has_jotpk_power_level(7),
"JotPK Max Buff": self.arcade.has_jotpk_power_level(9),
"JotPK Medium Buff": self.arcade.has_jotpk_power_level(4),
"JotPK Small Buff": self.arcade.has_jotpk_power_level(2),
"Junimo Kart Big Buff": self.arcade.has_junimo_kart_power_level(6),
"Junimo Kart Max Buff": self.arcade.has_junimo_kart_power_level(8),
"Junimo Kart Medium Buff": self.arcade.has_junimo_kart_power_level(4),
"Junimo Kart Small Buff": self.arcade.has_junimo_kart_power_level(2),
"Magic Rock Candy": self.region.can_reach(Region.desert) & self.has("Prismatic Shard"),
"Muscle Remedy": self.money.can_spend_at(Region.hospital, 1000),
# self.has(Ingredient.vinegar)),
# self.received("Deluxe Fertilizer Recipe") & self.has(MetalBar.iridium) & self.has(SVItem.sap),
# | (self.ability.can_cook() & self.relationship.has_hearts(NPC.emily, 3) & self.has(Forageable.leek) & self.has(Forageable.dandelion) &
# | (self.ability.can_cook() & self.relationship.has_hearts(NPC.jodi, 7) & self.has(AnimalProduct.cow_milk) & self.has(Ingredient.sugar)),
Animal.chicken: self.animal.can_buy_animal(Animal.chicken),
Animal.cow: self.animal.can_buy_animal(Animal.cow),
Animal.dinosaur: self.building.has_building(Building.big_coop) & self.has(AnimalProduct.dinosaur_egg),
Animal.duck: self.animal.can_buy_animal(Animal.duck),
Animal.goat: self.animal.can_buy_animal(Animal.goat),
Animal.ostrich: self.building.has_building(Building.barn) & self.has(AnimalProduct.ostrich_egg) & self.has(Machine.ostrich_incubator),
Animal.pig: self.animal.can_buy_animal(Animal.pig),
Animal.rabbit: self.animal.can_buy_animal(Animal.rabbit),
Animal.sheep: self.animal.can_buy_animal(Animal.sheep),
AnimalProduct.any_egg: self.has_any(AnimalProduct.chicken_egg, AnimalProduct.duck_egg),
AnimalProduct.brown_egg: self.animal.has_animal(Animal.chicken),
AnimalProduct.chicken_egg: self.has_any(AnimalProduct.egg, AnimalProduct.brown_egg, AnimalProduct.large_egg, AnimalProduct.large_brown_egg),
AnimalProduct.cow_milk: self.has_any(AnimalProduct.milk, AnimalProduct.large_milk),
AnimalProduct.duck_egg: self.animal.has_animal(Animal.duck),
AnimalProduct.duck_feather: self.animal.has_happy_animal(Animal.duck),
AnimalProduct.egg: self.animal.has_animal(Animal.chicken),
AnimalProduct.goat_milk: self.has(Animal.goat),
AnimalProduct.golden_egg: self.received(AnimalProduct.golden_egg) & (self.money.can_spend_at(Region.ranch, 100000) | self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100)),
AnimalProduct.large_brown_egg: self.animal.has_happy_animal(Animal.chicken),
AnimalProduct.large_egg: self.animal.has_happy_animal(Animal.chicken),
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.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)),
AnimalProduct.sturgeon_roe: self.has(Fish.sturgeon) & self.building.has_building(Building.fish_pond),
AnimalProduct.truffle: self.animal.has_animal(Animal.pig) & self.season.has_any_not_winter(),
AnimalProduct.void_egg: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)),
AnimalProduct.wool: self.animal.has_animal(Animal.rabbit) | self.animal.has_animal(Animal.sheep),
AnimalProduct.slime_egg_green: self.has(Machine.slime_egg_press) & self.has(Loot.slime),
AnimalProduct.slime_egg_blue: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(3),
AnimalProduct.slime_egg_red: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(6),
AnimalProduct.slime_egg_purple: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(9),
AnimalProduct.slime_egg_tiger: self.has(Fish.lionfish) & self.building.has_building(Building.fish_pond),
ArtisanGood.aged_roe: self.artisan.can_preserves_jar(AnimalProduct.roe),
ArtisanGood.battery_pack: (self.has(Machine.lightning_rod) & self.season.has_any_not_winter()) | self.has(Machine.solar_panel),
ArtisanGood.caviar: self.artisan.can_preserves_jar(AnimalProduct.sturgeon_roe),
ArtisanGood.cheese: (self.has(AnimalProduct.cow_milk) & self.has(Machine.cheese_press)) | (self.region.can_reach(Region.desert) & self.has(Mineral.emerald)),
ArtisanGood.cloth: (self.has(AnimalProduct.wool) & self.has(Machine.loom)) | (self.region.can_reach(Region.desert) & self.has(Mineral.aquamarine)),
ArtisanGood.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.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.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"),
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.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),
Fish.mussel: self.tool.can_forage(Generic.any, Region.beach) or self.has(Fish.mussel_node),
Fish.mussel_node: self.region.can_reach(Region.island_west),
Fish.oyster: self.tool.can_forage(Generic.any, Region.beach),
Fish.periwinkle: self.skill.can_crab_pot_at(Region.town),
Fish.shrimp: self.skill.can_crab_pot_at(Region.beach),
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),
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.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),
Fossil.snake_vertebrae: self.region.can_reach(Region.island_west) & self.tool.has_tool(Tool.hoe),
Geode.artifact_trove: self.has(Geode.omni) & self.region.can_reach(Region.desert),
Geode.frozen: self.mine.can_mine_in_the_mines_floor_41_80(),
Geode.geode: self.mine.can_mine_in_the_mines_floor_1_40(),
Geode.golden_coconut: self.region.can_reach(Region.island_north),
Geode.magma: self.mine.can_mine_in_the_mines_floor_81_120() | (self.has(Fish.lava_eel) & self.building.has_building(Building.fish_pond)),
Geode.omni: self.mine.can_mine_in_the_mines_floor_41_80() | self.region.can_reach(Region.desert) | self.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),
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.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,
Gift.void_ghost_pendant: self.money.can_trade_at(Region.desert, Loot.void_essence, 200) & self.relationship.has_hearts(NPC.krobus, 10),
Gift.wilted_bouquet: self.has(Machine.furnace) & self.has(Gift.bouquet) & self.has(Material.coal),
Ingredient.oil: self.money.can_spend_at(Region.pierre_store, 200) | (self.has(Machine.oil_maker) & (self.has(Vegetable.corn) | self.has(Flower.sunflower) | self.has(Seed.sunflower))),
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.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.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.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.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),
MetalBar.iridium: self.can_smelt(Ore.iridium),
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.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,
Trash.broken_glasses: self.skill.can_crab_pot,
Trash.driftwood: self.skill.can_crab_pot,
Trash.joja_cola: self.money.can_spend_at(Region.saloon, 75),
Trash.soggy_newspaper: self.skill.can_crab_pot,
Trash.trash: self.skill.can_crab_pot,
TreeSeed.acorn: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
TreeSeed.mahogany: self.region.can_reach(Region.secret_woods) & self.tool.has_tool(Tool.axe, ToolMaterial.iron) & self.skill.has_level(Skill.foraging, 1),
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(),
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.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
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.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
# For some recipes, the cooked item can be obtained directly, so we either cook it or get it
for recipe in self.registry.cooking_rules:
cooking_rule = self.registry.cooking_rules[recipe]
obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_()
self.registry.item_rules[recipe] = obtention_rule | cooking_rule
# For some recipes, the crafted item can be obtained directly, so we either craft it or get it
for recipe in self.registry.crafting_rules:
crafting_rule = self.registry.crafting_rules[recipe]
obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_()
self.registry.item_rules[recipe] = obtention_rule | crafting_rule
self.building.initialize_rules()
self.building.update_rules(self.mod.building.get_modded_building_rules())
self.quest.initialize_rules()
self.quest.update_rules(self.mod.quest.get_modded_quest_rules())
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.tub_o_flowers: self.money.can_spend(2000),
FestivalCheck.rarecrow_5: self.money.can_spend(2500),
FestivalCheck.luau_soup: self.can_succeed_luau_soup(),
FestivalCheck.moonlight_jellies: True_(),
FestivalCheck.moonlight_jellies_banner: self.money.can_spend(800),
FestivalCheck.starport_decal: self.money.can_spend(1000),
FestivalCheck.smashing_stone: True_(),
FestivalCheck.grange_display: self.can_succeed_grange_display(),
FestivalCheck.rarecrow_1: True_(), # only cost star tokens
FestivalCheck.fair_stardrop: True_(), # only cost star tokens
FestivalCheck.spirit_eve_maze: True_(),
FestivalCheck.jack_o_lantern: self.money.can_spend(2000),
FestivalCheck.rarecrow_2: self.money.can_spend(5000),
FestivalCheck.fishing_competition: self.can_win_fishing_competition(),
FestivalCheck.rarecrow_4: self.money.can_spend(5000),
FestivalCheck.mermaid_pearl: self.has(Forageable.secret_note),
FestivalCheck.cone_hat: self.money.can_spend(2500),
FestivalCheck.iridium_fireplace: self.money.can_spend(15000),
FestivalCheck.rarecrow_7: self.money.can_spend(5000) & self.museum.can_donate_museum_artifacts(20),
FestivalCheck.rarecrow_8: self.money.can_spend(5000) & self.museum.can_donate_museum_items(40),
FestivalCheck.lupini_red_eagle: self.money.can_spend(1200),
FestivalCheck.lupini_portrait_mermaid: self.money.can_spend(1200),
FestivalCheck.lupini_solar_kingdom: self.money.can_spend(1200),
FestivalCheck.lupini_clouds: self.time.has_year_two & self.money.can_spend(1200),
FestivalCheck.lupini_1000_years: self.time.has_year_two & self.money.can_spend(1200),
FestivalCheck.lupini_three_trees: self.time.has_year_two & self.money.can_spend(1200),
FestivalCheck.lupini_the_serpent: self.time.has_year_three & self.money.can_spend(1200),
FestivalCheck.lupini_tropical_fish: self.time.has_year_three & self.money.can_spend(1200),
FestivalCheck.lupini_land_of_clay: self.time.has_year_three & self.money.can_spend(1200),
FestivalCheck.secret_santa: self.gifts.has_any_universal_love,
FestivalCheck.legend_of_the_winter_star: True_(),
FestivalCheck.rarecrow_3: True_(),
FestivalCheck.all_rarecrows: self.region.can_reach(Region.farm) & self.has_all_rarecrows(),
})
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 can_smelt(self, item: str) -> StardewRule:
return self.has(Machine.furnace) & self.has(item)
def can_complete_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
def can_finish_grandpa_evaluation(self) -> StardewRule:
# https://stardewvalleywiki.com/Grandpa
rules_worth_a_point = [
self.money.can_have_earned_total(50000), # 50 000g
self.money.can_have_earned_total(100000), # 100 000g
self.money.can_have_earned_total(200000), # 200 000g
self.money.can_have_earned_total(300000), # 300 000g
self.money.can_have_earned_total(500000), # 500 000g
self.money.can_have_earned_total(1000000), # 1 000 000g first point
self.money.can_have_earned_total(1000000), # 1 000 000g second point
self.skill.has_total_level(30), # Total Skills: 30
self.skill.has_total_level(50), # Total Skills: 50
self.museum.can_complete_museum(), # Completing the museum for a point
# Catching every fish not expected
# Shipping every item not expected
self.relationship.can_get_married() & self.building.has_house(2),
self.relationship.has_hearts("5", 8), # 5 Friends
self.relationship.has_hearts("10", 8), # 10 friends
self.pet.has_hearts(5), # Max Pet
self.bundle.can_complete_community_center, # Community Center Completion
self.bundle.can_complete_community_center, # CC Ceremony first point
self.bundle.can_complete_community_center, # CC Ceremony second point
self.received(Wallet.skull_key), # Skull Key obtained
self.wallet.has_rusty_key(), # Rusty key obtained
]
return self.count(12, *rules_worth_a_point)
def 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,
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)
# There are a few other valid items, but I don't feel like coding them all
return fish_rule | aged_rule
def can_succeed_grange_display(self) -> StardewRule:
if self.options.festival_locations != FestivalLocations.option_hard:
return True_()
animal_rule = self.animal.has_animal(Generic.any)
artisan_rule = self.artisan.can_keg(Generic.any) | self.artisan.can_preserves_jar(Generic.any)
cooking_rule = self.money.can_spend_at(Region.saloon, 220) # Salads at the bar are good enough
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, ]
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]
vegetable_rule = self.has_any(*good_vegetables)
return animal_rule & artisan_rule & cooking_rule & fish_rule & \
forage_rule & fruit_rule & mineral_rule & vegetable_rule
def can_win_fishing_competition(self) -> StardewRule:
return self.skill.can_fish(difficulty=60)
def has_island_trader(self) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return False_()
return self.region.can_reach(Region.island_trader)
def has_walnut(self, number: int) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return False_()
if number <= 0:
return True_()
# 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)
reach_west = self.region.can_reach(Region.island_west)
reach_hut = self.region.can_reach(Region.leo_hut)
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_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_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),
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,
reach_caves, reach_southeast, reach_field_office, reach_pirate_cove)
if number <= 5:
return 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)
if number <= 50:
return reach_entire_island
gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz)
return reach_entire_island & self.has(Fruit.banana) & self.has_all(*gems) & self.ability.can_mine_perfectly() & \
self.ability.can_fish_perfectly() & self.has(Furniture.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) & \
self.can_complete_field_office()
def has_all_stardrops(self) -> StardewRule:
other_rules = []
number_of_stardrops_to_receive = 0
number_of_stardrops_to_receive += 1 # The Mines level 100
number_of_stardrops_to_receive += 1 # Old Master Cannoli
number_of_stardrops_to_receive += 1 # Museum Stardrop
number_of_stardrops_to_receive += 1 # Krobus Stardrop
if self.options.fishsanity == Fishsanity.option_none: # Master Angler Stardrop
other_rules.append(self.fishing.can_catch_every_fish())
else:
number_of_stardrops_to_receive += 1
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:
number_of_stardrops_to_receive += 1
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
number_of_stardrops_to_receive += 1
if not other_rules:
return self.received("Stardrop", number_of_stardrops_to_receive)
return self.received("Stardrop", number_of_stardrops_to_receive) & 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")
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)
def has_abandoned_jojamart(self) -> StardewRule:
return self.received(CommunityUpgrade.movie_theater, 1)
def has_movie_theater(self) -> StardewRule:
return self.received(CommunityUpgrade.movie_theater, 2)
def can_use_obelisk(self, obelisk: str) -> StardewRule:
return self.region.can_reach(Region.farm) & self.received(obelisk)
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

@ -0,0 +1,58 @@
# Logic mixin
Mixins are used to split the logic building methods in multiple classes, so it's more scoped and easier to extend specific methods.
One single instance of Logic is necessary so mods can change the logics. This means that, when calling itself, a `Logic` class has to call its instance in
the `logic`, because it might have been overriden.
```python
class TimeLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.time = TimeLogic(*args, **kwargs)
class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]):
def has_lived_months(self, number: int) -> StardewRule:
return self.logic.received(Event.month_end, number)
def has_year_two(self) -> StardewRule:
return self.logic.time.has_lived_months(4)
def has_year_three(self) -> StardewRule:
return self.logic.time.has_lived_months(8)
```
Creating the rules for actual items has to be outside the `logic` instance. Once the vanilla logic builder is created, mods will be able to replace the logic
implementations by their own modified version. For instance, the `combat` logic can be replaced by the magic mod to extends its methods to add spells in the
combat logic.
## Logic class created on the fly (idea)
The logic class could be created dynamically, based on the `LogicMixin` provided by the content packs. This would allow replacing completely mixins, instead of
overriding their logic afterward. Might be too complicated for no real gain tho...
# Content pack (idea)
Instead of using modules to hold the data, and have each mod adding their data to existing content, each mod data should be in a `ContentPack`. Vanilla, Ginger
Island, or anything that could be disabled would be in a content pack as well.
Eventually, Vanilla content could even be disabled (a split would be required for items that are necessary to all content packs) to have a Ginger Island only
play through created without the heavy vanilla logic computation.
## Unpacking
Steps to unpack content follows the same steps has the world initialisation. Content pack however need to be unpacked in a specific order, based on their
dependencies. Vanilla would always be first, then anything that depends only on Vanilla, etc.
1. In `generate_early`, content packs are selected. The logic builders are created and content packs are unpacked so all their content is in the proper
item/npc/weapon lists.
- `ContentPack` instances are shared across players. However, some mods need to modify content of other packs. In that case, an instance of the content is
created specifically for that player (For instance, SVE changes the Wizard). This probably does not happen enough to require sharing those instances. If
necessary, a FlyWeight design pattern could be used.
2. In `create_regions`, AP regions and entrances are unpacked, and randomized if needed.
3. In `create_items`, AP items are unpacked, and randomized.
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`.

View File

@ -0,0 +1,86 @@
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .combat_logic import CombatLogicMixin
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 ..strings.performance_names import Performance
from ..strings.region_names import Region
from ..strings.skill_names import Skill
from ..strings.tool_names import Tool, ToolMaterial
class MineLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mine = MineLogic(*args, **kwargs)
class MineLogic(BaseLogic[Union[MineLogicMixin, RegionLogicMixin, ReceivedLogicMixin, CombatLogicMixin, ToolLogicMixin, SkillLogicMixin]]):
# Regions
def can_mine_in_the_mines_floor_1_40(self) -> StardewRule:
return self.logic.region.can_reach(Region.mines_floor_5)
def can_mine_in_the_mines_floor_41_80(self) -> StardewRule:
return self.logic.region.can_reach(Region.mines_floor_45)
def can_mine_in_the_mines_floor_81_120(self) -> StardewRule:
return self.logic.region.can_reach(Region.mines_floor_85)
def can_mine_in_the_skull_cavern(self) -> StardewRule:
return (self.logic.mine.can_progress_in_the_mines_from_floor(120) &
self.logic.region.can_reach(Region.skull_cavern))
@cache_self1
def get_weapon_rule_for_floor_tier(self, tier: int):
if tier >= 4:
return self.logic.combat.can_fight_at_level(Performance.galaxy)
if tier >= 3:
return self.logic.combat.can_fight_at_level(Performance.great)
if tier >= 2:
return self.logic.combat.can_fight_at_level(Performance.good)
if tier >= 1:
return self.logic.combat.can_fight_at_level(Performance.decent)
return self.logic.combat.can_fight_at_level(Performance.basic)
@cache_self1
def can_progress_in_the_mines_from_floor(self, floor: int) -> StardewRule:
tier = floor // 40
rules = []
weapon_rule = self.logic.mine.get_weapon_rule_for_floor_tier(tier)
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:
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)
@cache_self1
def has_mine_elevator_to_floor(self, floor: int) -> StardewRule:
if floor < 0:
floor = 0
if self.options.elevator_progression != options.ElevatorProgression.option_vanilla:
return self.logic.received("Progressive Mine Elevator", floor // 5)
return True_()
@cache_self1
def can_progress_in_the_skull_cavern_from_floor(self, floor: int) -> StardewRule:
tier = floor // 50
rules = []
weapon_rule = self.logic.combat.has_great_weapon
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:
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)

View File

@ -0,0 +1,99 @@
from typing import Union
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .buff_logic import BuffLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .time_logic import TimeLogicMixin
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_
from ..strings.ap_names.event_names import Event
from ..strings.currency_names import Currency
from ..strings.region_names import Region
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")
class MoneyLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.money = MoneyLogic(*args, **kwargs)
class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, BuffLogicMixin]]):
@cache_self1
def can_have_earned_total(self, amount: int) -> StardewRule:
if amount < 1000:
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))
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)
if amount < 2000:
selling_any_rule = pierre_rule | willy_rule | clint_rule | robin_rule | shipping_rule
return selling_any_rule
if amount < 5000:
selling_all_rule = (pierre_rule & willy_rule & clint_rule & robin_rule) | shipping_rule
return selling_all_rule
if amount < 10000:
return shipping_rule
seed_rules = self.logic.received(Event.can_shop_at_pierre)
if amount < 40000:
return shipping_rule & seed_rules
percent_progression_items_needed = min(90, amount // 20000)
return shipping_rule & seed_rules & HasProgressionPercent(self.player, percent_progression_items_needed)
@cache_self1
def can_spend(self, amount: int) -> StardewRule:
if self.options.starting_money == -1:
return True_()
return self.logic.money.can_have_earned_total(amount * 5)
# Should be cached
def can_spend_at(self, region: str, amount: int) -> StardewRule:
return self.logic.region.can_reach(region) & self.logic.money.can_spend(amount)
# Should be cached
def can_trade(self, currency: str, amount: int) -> StardewRule:
if amount == 0:
return True_()
if currency == Currency.money:
return self.can_spend(amount)
if currency == Currency.star_token:
return self.logic.region.can_reach(Region.fair)
if currency == Currency.qi_coin:
return self.logic.region.can_reach(Region.casino) & self.logic.buff.has_max_luck()
if currency == Currency.qi_gem:
if self.options.special_order_locations == SpecialOrderLocations.option_board_qi:
number_rewards = min(len(qi_gem_rewards), max(1, (amount // 10)))
return self.logic.received_n(*qi_gem_rewards, count=number_rewards)
number_rewards = 2
return self.logic.received_n(*qi_gem_rewards, count=number_rewards) & self.logic.region.can_reach(Region.qi_walnut_room) & \
self.logic.region.can_reach(Region.saloon) & self.can_have_earned_total(5000)
if currency == Currency.golden_walnut:
return self.can_spend_walnut(amount)
return self.logic.has(currency) & self.logic.time.has_lived_months(amount)
# Should be cached
def can_trade_at(self, region: str, currency: str, amount: int) -> StardewRule:
if amount == 0:
return True_()
if currency == Currency.money:
return self.logic.money.can_spend_at(region, amount)
return self.logic.region.can_reach(region) & self.can_trade(currency, amount)
def can_spend_walnut(self, amount: int) -> StardewRule:
return False_()

View File

@ -0,0 +1,69 @@
from functools import cached_property
from typing import Iterable, Union, Hashable
from Utils import cache_self1
from .base_logic import BaseLogicMixin, BaseLogic
from .combat_logic import CombatLogicMixin
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 ..strings.region_names import Region
class MonsterLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.monster = MonsterLogic(*args, **kwargs)
class MonsterLogic(BaseLogic[Union[MonsterLogicMixin, RegionLogicMixin, CombatLogicMixin, TimeLogicMixin]]):
@cached_property
def all_monsters_by_name(self):
return monster_data.all_monsters_by_name_given_mods(self.options.mods.value)
@cached_property
def all_monsters_by_category(self):
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)
return region_rule & combat_rule & time_rule
@cache_self1
def can_kill_many(self, monster: monster_data.StardewMonster) -> StardewRule:
return self.logic.monster.can_kill(monster, MAX_MONTHS / 3)
@cache_self1
def can_kill_max(self, monster: monster_data.StardewMonster) -> StardewRule:
return self.logic.monster.can_kill(monster, MAX_MONTHS)
# Should be cached
def can_kill_any(self, monsters: (Iterable[monster_data.StardewMonster], Hashable), amount_tier: int = 0) -> StardewRule:
rules = [self.logic.monster.can_kill(monster, amount_tier) for monster in monsters]
return Or(*rules)
# 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)
def can_complete_all_monster_slaying_goals(self) -> StardewRule:
rules = [self.logic.time.has_lived_max_months]
exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
island_regions = [Region.volcano_floor_5, Region.volcano_floor_10, Region.island_west, Region.dangerous_skull_cavern]
for category in self.all_monsters_by_category:
if exclude_island and all(all(location in island_regions for location in monster.locations)
for monster in self.all_monsters_by_category[category]):
continue
rules.append(self.logic.monster.can_kill_any(self.all_monsters_by_category[category]))
return And(*rules)

View File

@ -0,0 +1,80 @@
from typing import Union
from Utils import cache_self1
from .action_logic import ActionLogicMixin
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
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 ..strings.region_names import Region
class MuseumLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.museum = MuseumLogic(*args, **kwargs)
class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, ActionLogicMixin, 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)
def can_donate_museum_artifacts(self, number: int) -> StardewRule:
return self.logic.region.can_reach(Region.museum) & self.logic.museum.can_find_museum_artifacts(number)
@cache_self1
def can_find_museum_item(self, item: MuseumItem) -> StardewRule:
if item.locations:
region_rule = self.logic.region.can_reach_all_except_one(item.locations)
else:
region_rule = False_()
if item.geodes:
geodes_rule = 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_()
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
def can_find_museum_artifacts(self, number: int) -> StardewRule:
rules = []
for artifact in all_museum_artifacts:
rules.append(self.logic.museum.can_find_museum_item(artifact))
return self.logic.count(number, *rules)
def can_find_museum_minerals(self, number: int) -> StardewRule:
rules = []
for mineral in all_museum_minerals:
rules.append(self.logic.museum.can_find_museum_item(mineral))
return self.logic.count(number, *rules)
def can_find_museum_items(self, number: int) -> StardewRule:
rules = []
for donation in all_museum_items:
rules.append(self.logic.museum.can_find_museum_item(donation))
return self.logic.count(number, *rules)
def can_complete_museum(self) -> StardewRule:
rules = [self.logic.region.can_reach(Region.museum)]
if self.options.museumsanity == options.Museumsanity.option_none:
rules.append(self.logic.received("Traveling Merchant Metal Detector", 2))
else:
rules.append(self.logic.received("Traveling Merchant Metal Detector", 3))
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)
def can_donate(self, item: str) -> StardewRule:
return self.logic.has(item) & self.logic.region.can_reach(Region.museum)

View File

@ -0,0 +1,50 @@
import math
from typing import Union
from .base_logic import BaseLogicMixin, BaseLogic
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 ..stardew_rule import StardewRule, True_
from ..strings.region_names import Region
from ..strings.villager_names import NPC
class PetLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.pet = PetLogic(*args, **kwargs)
class PetLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, TimeLogicMixin, ToolLogicMixin]]):
def has_hearts(self, hearts: int = 1) -> StardewRule:
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))
def can_befriend_pet(self, hearts: int) -> StardewRule:
if hearts <= 0:
return True_()
points = hearts * 200
points_per_month = 12 * 14
points_per_water_month = 18 * 14
farm_rule = self.logic.region.can_reach(Region.farm)
time_with_water_rule = self.logic.tool.can_water(0) & self.logic.time.has_lived_months(points // points_per_water_month)
time_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,128 @@
from typing import Dict, Union
from .base_logic import BaseLogicMixin, BaseLogic
from .building_logic import BuildingLogicMixin
from .combat_logic import CombatLogicMixin
from .cooking_logic import CookingLogicMixin
from .fishing_logic import FishingLogicMixin
from .has_logic import HasLogicMixin
from .mine_logic import MineLogicMixin
from .money_logic import MoneyLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
from .season_logic import SeasonLogicMixin
from .skill_logic import SkillLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .wallet_logic import WalletLogicMixin
from ..stardew_rule import StardewRule, Has, True_
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.craftable_names import Craftable
from ..strings.crop_names import Fruit, Vegetable
from ..strings.fish_names import Fish
from ..strings.food_names import Meal
from ..strings.forageable_names import Forageable
from ..strings.machine_names import Machine
from ..strings.material_names import Material
from ..strings.metal_names import MetalBar, Ore, Mineral
from ..strings.monster_drop_names import Loot
from ..strings.quest_names import Quest
from ..strings.region_names import Region
from ..strings.season_names import Season
from ..strings.tool_names import Tool
from ..strings.villager_names import NPC
from ..strings.wallet_item_names import Wallet
class QuestLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.quest = QuestLogic(*args, **kwargs)
class QuestLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, MoneyLogicMixin, MineLogicMixin, RegionLogicMixin, RelationshipLogicMixin, ToolLogicMixin,
FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillLogicMixin, WalletLogicMixin, QuestLogicMixin, BuildingLogicMixin, TimeLogicMixin]]):
def initialize_rules(self):
self.update_rules({
Quest.introductions: True_(),
Quest.how_to_win_friends: self.logic.quest.can_complete_quest(Quest.introductions),
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.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)),
Quest.meet_the_wizard: self.logic.quest.can_complete_quest(Quest.rat_problem),
Quest.forging_ahead: self.logic.has(Ore.copper) & self.logic.has(Machine.furnace),
Quest.smelting: self.logic.has(MetalBar.copper),
Quest.initiation: self.logic.mine.can_mine_in_the_mines_floor_1_40(),
Quest.robins_lost_axe: self.logic.season.has(Season.spring) & self.logic.relationship.can_meet(NPC.robin),
Quest.jodis_request: self.logic.season.has(Season.spring) & self.logic.has(Vegetable.cauliflower) & self.logic.relationship.can_meet(NPC.jodi),
Quest.mayors_shorts: self.logic.season.has(Season.summer) & self.logic.relationship.has_hearts(NPC.marnie, 2) &
self.logic.relationship.can_meet(NPC.lewis),
Quest.blackberry_basket: self.logic.season.has(Season.fall) & self.logic.relationship.can_meet(NPC.linus),
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),
Quest.cows_delight: self.logic.season.has(Season.fall) & self.logic.has(Vegetable.amaranth) & self.logic.relationship.can_meet(NPC.marnie),
Quest.the_skull_key: self.logic.received(Wallet.skull_key),
Quest.crop_research: self.logic.season.has(Season.summer) & self.logic.has(Fruit.melon) & self.logic.relationship.can_meet(NPC.demetrius),
Quest.knee_therapy: self.logic.season.has(Season.summer) & self.logic.has(Fruit.hot_pepper) & self.logic.relationship.can_meet(NPC.george),
Quest.robins_request: self.logic.season.has(Season.winter) & self.logic.has(Material.hardwood) & self.logic.relationship.can_meet(NPC.robin),
Quest.qis_challenge: True_(), # The skull cavern floor 25 already has rules
Quest.the_mysterious_qi: (self.logic.region.can_reach_all((Region.bus_tunnel, Region.railroad, Region.mayor_house)) &
self.logic.has_all(ArtisanGood.battery_pack, Forageable.rainbow_shell, Vegetable.beet, Loot.solar_essence)),
Quest.carving_pumpkins: self.logic.season.has(Season.fall) & self.logic.has(Vegetable.pumpkin) & self.logic.relationship.can_meet(NPC.caroline),
Quest.a_winter_mystery: self.logic.season.has(Season.winter),
Quest.strange_note: self.logic.has(Forageable.secret_note) & self.logic.has(ArtisanGood.maple_syrup),
Quest.cryptic_note: self.logic.has(Forageable.secret_note),
Quest.fresh_fruit: self.logic.season.has(Season.spring) & self.logic.has(Fruit.apricot) & self.logic.relationship.can_meet(NPC.emily),
Quest.aquatic_research: self.logic.season.has(Season.summer) & self.logic.has(Fish.pufferfish) & self.logic.relationship.can_meet(NPC.demetrius),
Quest.a_soldiers_star: (self.logic.season.has(Season.summer) & self.logic.time.has_year_two & self.logic.has(Fruit.starfruit) &
self.logic.relationship.can_meet(NPC.kent)),
Quest.mayors_need: self.logic.season.has(Season.summer) & self.logic.has(ArtisanGood.truffle_oil) & self.logic.relationship.can_meet(NPC.lewis),
Quest.wanted_lobster: (self.logic.season.has(Season.fall) & self.logic.season.has(Season.fall) & self.logic.has(Fish.lobster) &
self.logic.relationship.can_meet(NPC.gus)),
Quest.pam_needs_juice: self.logic.season.has(Season.fall) & self.logic.has(ArtisanGood.battery_pack) & self.logic.relationship.can_meet(NPC.pam),
Quest.fish_casserole: self.logic.relationship.has_hearts(NPC.jodi, 4) & self.logic.has(Fish.largemouth_bass),
Quest.catch_a_squid: self.logic.season.has(Season.winter) & self.logic.has(Fish.squid) & self.logic.relationship.can_meet(NPC.willy),
Quest.fish_stew: self.logic.season.has(Season.winter) & self.logic.has(Fish.albacore) & self.logic.relationship.can_meet(NPC.gus),
Quest.pierres_notice: self.logic.season.has(Season.spring) & self.logic.has(Meal.sashimi) & self.logic.relationship.can_meet(NPC.pierre),
Quest.clints_attempt: self.logic.season.has(Season.winter) & self.logic.has(Mineral.amethyst) & self.logic.relationship.can_meet(NPC.emily),
Quest.a_favor_for_clint: self.logic.season.has(Season.winter) & self.logic.has(MetalBar.iron) & self.logic.relationship.can_meet(NPC.clint),
Quest.staff_of_power: self.logic.season.has(Season.winter) & self.logic.has(MetalBar.iridium) & self.logic.relationship.can_meet(NPC.wizard),
Quest.grannys_gift: self.logic.season.has(Season.spring) & self.logic.has(Forageable.leek) & self.logic.relationship.can_meet(NPC.evelyn),
Quest.exotic_spirits: self.logic.season.has(Season.winter) & self.logic.has(Forageable.coconut) & self.logic.relationship.can_meet(NPC.gus),
Quest.catch_a_lingcod: self.logic.season.has(Season.winter) & self.logic.has(Fish.lingcod) & self.logic.relationship.can_meet(NPC.willy),
Quest.dark_talisman: self.logic.region.can_reach(Region.railroad) & self.logic.wallet.has_rusty_key() & self.logic.relationship.can_meet(
NPC.krobus),
Quest.goblin_problem: self.logic.region.can_reach(Region.witch_swamp),
Quest.magic_ink: self.logic.relationship.can_meet(NPC.wizard),
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),
})
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)
def has_club_card(self) -> StardewRule:
if self.options.quest_locations < 0:
return self.logic.quest.can_complete_quest(Quest.the_mysterious_qi)
return self.logic.received(Wallet.club_card)
def has_magnifying_glass(self) -> StardewRule:
if self.options.quest_locations < 0:
return self.logic.quest.can_complete_quest(Quest.a_winter_mystery)
return self.logic.received(Wallet.magnifying_glass)
def has_dark_talisman(self) -> StardewRule:
if self.options.quest_locations < 0:
return self.logic.quest.can_complete_quest(Quest.dark_talisman)
return self.logic.received(Wallet.dark_talisman)

View File

@ -0,0 +1,35 @@
from typing import Optional
from .base_logic import BaseLogic, BaseLogicMixin
from .has_logic import HasLogicMixin
from ..stardew_rule import StardewRule, Received, And, Or, 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."
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))
def received_any(self, *items: str):
assert items, "Can't receive any of no items."
return 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."
assert count >= 0, "Can't receive a negative amount of item."
return self.logic.count(count, *(self.received(item) for item in items))
def received_n(self, *items: str, count: int):
assert items, "Can't receive n of no items."
assert count >= 0, "Can't receive a negative amount of item."
return TotalReceived(count, items, self.player)

View File

@ -0,0 +1,65 @@
from typing import Tuple, Union
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 ..strings.region_names import Region
main_outside_area = {Region.menu, Region.stardew_valley, Region.farm_house, Region.farm, Region.town, Region.beach, Region.mountain, Region.forest,
Region.bus_stop, Region.backwoods, Region.bus_tunnel, Region.tunnel_entrance}
always_accessible_regions_without_er = {*main_outside_area, Region.community_center, Region.pantry, Region.crafts_room, Region.fish_tank, Region.boiler_room,
Region.vault, Region.bulletin_board, Region.mines, Region.hospital, Region.carpenter, Region.alex_house,
Region.elliott_house, Region.ranch, Region.farm_cave, Region.wizard_tower, Region.tent, Region.pierre_store,
Region.saloon, Region.blacksmith, Region.trailer, Region.museum, Region.mayor_house, Region.haley_house,
Region.sam_house, Region.jojamart, Region.fish_shop}
always_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: main_outside_area,
EntranceRandomization.option_chaos: always_accessible_regions_without_er}
class RegionLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.region = RegionLogic(*args, **kwargs)
class RegionLogic(BaseLogic[Union[RegionLogicMixin, HasLogicMixin]]):
@cache_self1
def can_reach(self, region_name: str) -> StardewRule:
if region_name in always_regions_by_setting[self.options.entrance_randomization]:
return true_
if region_name not in self.regions:
return false_
return Reach(region_name, "Region", self.player)
@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))
@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))
@cache_self1
def can_reach_all_except_one(self, region_names: Tuple[str, ...]) -> StardewRule:
region_names = list(region_names)
num_required = len(region_names) - 1
if num_required <= 0:
num_required = len(region_names)
return self.logic.count(num_required, *(self.logic.region.can_reach(spot) for spot in region_names))
@cache_self1
def can_reach_location(self, location_name: str) -> StardewRule:
return Reach(location_name, "Location", self.player)
# @cache_self1
# def can_reach_entrance(self, entrance_name: str) -> StardewRule:
# return Reach(entrance_name, "Entrance", self.player)

View File

@ -0,0 +1,185 @@
import math
from functools import cached_property
from typing import Union, List
from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
from .building_logic import BuildingLogicMixin
from .gift_logic import GiftLogicMixin
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.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 ..strings.ap_names.mods.mod_items import SVEQuestItem
from ..strings.crop_names import Fruit
from ..strings.generic_names import Generic
from ..strings.gift_names import Gift
from ..strings.region_names import Region
from ..strings.season_names import Season
from ..strings.villager_names import NPC, ModNPC
possible_kids = ("Cute Baby", "Ugly Baby")
def heart_item_name(npc: Union[str, Villager]) -> str:
if isinstance(npc, Villager):
npc = npc.name
return f"{npc} <3"
class RelationshipLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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)
def can_date(self, npc: str) -> StardewRule:
return self.logic.relationship.has_hearts(npc, 8) & self.logic.has(Gift.bouquet)
def can_marry(self, npc: str) -> StardewRule:
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)
def has_children(self, number_children: int) -> StardewRule:
if number_children <= 0:
return True_()
if self.options.friendsanity == Friendsanity.option_none:
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:
return True_()
baby_rules = [self.logic.relationship.can_get_married(), self.logic.building.has_house(2), self.logic.relationship.has_hearts(Generic.bachelor, 12),
self.logic.relationship.has_children(number_children - 1)]
return And(*baby_rules)
# 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)
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:
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)
# 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)
@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]
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"))
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)
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
meet_rule = self.logic.relationship.can_meet(npc)
rules.append(meet_rule)
rules.append(self.logic.gifts.has_any_universal_love)
return And(*rules)
# Should be cached
def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule:
if hearts <= 0:
return True_()
previous_heart = hearts - self.options.friendsanity_heart_size
previous_heart_rule = self.logic.relationship.has_hearts(npc, previous_heart)
if npc not in all_villagers_by_name or not self.npc_is_in_current_slot(npc):
return previous_heart_rule
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:
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))
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

View File

@ -0,0 +1,44 @@
from typing import Iterable, Union
from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
from .received_logic import ReceivedLogicMixin
from .time_logic import TimeLogicMixin
from ..options import SeasonRandomization
from ..stardew_rule import StardewRule, True_, Or, And
from ..strings.generic_names import Generic
from ..strings.season_names import Season
class SeasonLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.season = SeasonLogic(*args, **kwargs)
class SeasonLogic(BaseLogic[Union[SeasonLogicMixin, TimeLogicMixin, ReceivedLogicMixin]]):
@cache_self1
def has(self, season: str) -> StardewRule:
if season == Generic.any:
return True_()
seasons_order = [Season.spring, Season.summer, Season.fall, Season.winter]
if self.options.season_randomization == SeasonRandomization.option_progressive:
return self.logic.received(Season.progressive, seasons_order.index(season))
if self.options.season_randomization == SeasonRandomization.option_disabled:
if season == Season.spring:
return True_()
return self.logic.time.has_lived_months(1)
return self.logic.received(season)
def has_any(self, seasons: Iterable[str]):
if not seasons:
return True_()
return 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))

View File

@ -0,0 +1,60 @@
from functools import cached_property
from typing import Union, List
from Utils import cache_self1
from .base_logic import BaseLogic, BaseLogicMixin
from .building_logic import BuildingLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from ..locations import LocationTags, locations_by_tag
from ..options import ExcludeGingerIsland, Shipsanity
from ..options import SpecialOrderLocations
from ..stardew_rule import StardewRule, And
from ..strings.ap_names.event_names import Event
from ..strings.building_names import Building
class ShippingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.shipping = ShippingLogic(*args, **kwargs)
class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, BuildingLogicMixin, RegionLogicMixin, HasLogicMixin]]):
@cached_property
def can_use_shipping_bin(self) -> StardewRule:
return self.logic.building.has_building(Building.shipping_bin)
@cache_self1
def can_ship(self, item: str) -> StardewRule:
return self.logic.received(Event.can_ship_items) & self.logic.has(item)
def can_ship_everything(self) -> StardewRule:
shipsanity_prefix = "Shipsanity: "
all_items_to_ship = []
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_qi = self.options.special_order_locations != SpecialOrderLocations.option_board_qi
mod_list = self.options.mods.value
for location in locations_by_tag[LocationTags.SHIPSANITY_FULL_SHIPMENT]:
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
continue
if exclude_qi and LocationTags.REQUIRES_QI_ORDERS in location.tags:
continue
if location.mod_name and location.mod_name not in mod_list:
continue
all_items_to_ship.append(location.name[len(shipsanity_prefix):])
return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship)
def can_ship_everything_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
if self.options.shipsanity == Shipsanity.option_none:
return self.can_ship_everything()
rules = [self.logic.building.has_building(Building.shipping_bin)]
for shipsanity_location in locations_by_tag[LocationTags.SHIPSANITY]:
if shipsanity_location.name not in all_location_names_in_slot:
continue
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
return And(*rules)

View File

@ -0,0 +1,172 @@
from functools import cached_property
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 .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 .. import options
from ..data import all_crops
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 ..strings.craftable_names import Fishing
from ..strings.machine_names import Machine
from ..strings.performance_names import Performance
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
fishing_regions = (Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west)
class SkillLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.skill = SkillLogic(*args, **kwargs)
class SkillLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, ToolLogicMixin, SkillLogicMixin,
CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
# Should be cached
def can_earn_level(self, skill: str, level: int) -> StardewRule:
if level <= 0:
return True_()
tool_level = (level - 1) // 2
tool_material = ToolMaterial.tiers[tool_level]
months = max(1, level - 1)
months_rule = self.logic.time.has_lived_months(months)
previous_level_rule = self.logic.skill.has_level(skill, level - 1)
if skill == Skill.fishing:
xp_rule = self.logic.tool.has_tool(Tool.fishing_rod, ToolMaterial.tiers[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)
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)
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)
xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5)
elif skill == Skill.combat:
combat_tier = Performance.tiers[tool_level]
xp_rule = self.logic.combat.can_fight_at_level(combat_tier)
xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5)
elif skill in all_mod_skills:
# 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)
else:
raise Exception(f"Unknown skill: {skill}")
return previous_level_rule & months_rule & xp_rule
# Should be cached
def has_level(self, skill: str, level: int) -> StardewRule:
if level <= 0:
return True_()
if self.options.skill_progression == options.SkillProgression.option_progressive:
return self.logic.received(f"{skill} Level", level)
return self.logic.skill.can_earn_level(skill, level)
@cache_self1
def has_farming_level(self, level: int) -> StardewRule:
return self.logic.skill.has_level(Skill.farming, level)
# Should be cached
def has_total_level(self, level: int, allow_modded_skills: bool = False) -> StardewRule:
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 allow_modded_skills:
skills_items += get_mod_skill_levels(self.options.mods)
return self.logic.received_n(*skills_items, count=level)
months_with_4_skills = max(1, (level // 4) - 1)
months_with_5_skills = max(1, (level // 5) - 1)
rule_with_fishing = self.logic.time.has_lived_months(months_with_5_skills) & self.logic.skill.can_get_fishing_xp
if level > 40:
return rule_with_fishing
return self.logic.time.has_lived_months(months_with_4_skills) | rule_with_fishing
@cached_property
def can_get_farming_xp(self) -> StardewRule:
crop_rules = []
for crop in all_crops:
crop_rules.append(self.logic.crop.can_grow(crop))
return Or(*crop_rules)
@cached_property
def can_get_foraging_xp(self) -> StardewRule:
tool_rule = self.logic.tool.has_tool(Tool.axe)
tree_rule = self.logic.region.can_reach(Region.forest) & self.logic.season.has_any_not_winter()
stump_rule = self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.axe, ToolMaterial.copper)
return tool_rule & (tree_rule | stump_rule)
@cached_property
def can_get_mining_xp(self) -> StardewRule:
tool_rule = self.logic.tool.has_tool(Tool.pickaxe)
stone_rule = self.logic.region.can_reach_any((Region.mines_floor_5, Region.quarry, Region.skull_cavern_25, Region.volcano_floor_5))
return tool_rule & stone_rule
@cached_property
def can_get_combat_xp(self) -> StardewRule:
tool_rule = self.logic.combat.has_any_weapon()
enemy_rule = self.logic.region.can_reach_any((Region.mines_floor_5, Region.skull_cavern_25, Region.volcano_floor_5))
return tool_rule & enemy_rule
@cached_property
def can_get_fishing_xp(self) -> StardewRule:
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()
# Should be cached
def can_fish(self, regions: Union[str, Tuple[str, ...]] = None, difficulty: int = 0) -> StardewRule:
if isinstance(regions, str):
regions = regions,
if regions is None or len(regions) == 0:
regions = fishing_regions
skill_required = min(10, max(0, int((difficulty / 10) - 1)))
if difficulty <= 40:
skill_required = 0
skill_rule = self.logic.skill.has_level(Skill.fishing, skill_required)
region_rule = self.logic.region.can_reach_any(regions)
number_fishing_rod_required = 1 if difficulty < 50 else (2 if difficulty < 80 else 4)
return self.logic.tool.has_fishing_rod(number_fishing_rod_required) & skill_rule & region_rule
@cache_self1
def can_crab_pot_at(self, region: str) -> StardewRule:
return self.logic.skill.can_crab_pot & self.logic.region.can_reach(region)
@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:
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
water_region_rules = self.logic.region.can_reach_any(fishing_regions)
return crab_pot_rule & water_region_rules
def can_forage_quality(self, quality: str) -> StardewRule:
if quality == ForageQuality.basic:
return True_()
if quality == ForageQuality.silver:
return self.has_level(Skill.foraging, 5)
if quality == ForageQuality.gold:
return self.has_level(Skill.foraging, 9)
return False_()

View File

@ -0,0 +1,111 @@
from typing import Dict, Union
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
from .money_logic import MoneyLogicMixin
from .monster_logic import MonsterLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
from .season_logic import SeasonLogicMixin
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 ..strings.animal_product_names import AnimalProduct
from ..strings.ap_names.event_names import Event
from ..strings.ap_names.transport_names import Transportation
from ..strings.artisan_good_names import ArtisanGood
from ..strings.crop_names import Vegetable, Fruit
from ..strings.fertilizer_names import Fertilizer
from ..strings.fish_names import Fish
from ..strings.forageable_names import Forageable
from ..strings.machine_names import Machine
from ..strings.material_names import Material
from ..strings.metal_names import Mineral
from ..strings.monster_drop_names import Loot
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
class SpecialOrderLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.special_order = SpecialOrderLogic(*args, **kwargs)
class SpecialOrderLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, MoneyLogicMixin,
ShippingLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, RelationshipLogicMixin, ToolLogicMixin, SkillLogicMixin,
MineLogicMixin, CookingLogicMixin, BuffLogicMixin,
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(),
SpecialOrder.rock_rejuivenation: (self.logic.relationship.has_hearts(NPC.emily, 4) &
self.logic.has_all(Mineral.ruby, Mineral.topaz, Mineral.emerald, Mineral.jade, Mineral.amethyst,
ArtisanGood.cloth)),
SpecialOrder.gifts_for_george: self.logic.season.has(Season.spring) & self.logic.has(Forageable.leek),
SpecialOrder.fragments_of_the_past: self.logic.monster.can_kill(Monster.skeleton),
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.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.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),
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_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.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...
})
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)
def has_island_transport(self) -> StardewRule:
return self.logic.received(Transportation.island_obelisk) | self.logic.received(Transportation.boat_repair)

View File

@ -0,0 +1,38 @@
from functools import cached_property
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_
MAX_MONTHS = 12
MONTH_COEFFICIENT = 24 // MAX_MONTHS
class TimeLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.time = TimeLogic(*args, **kwargs)
class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]):
@cache_self1
def has_lived_months(self, number: int) -> StardewRule:
if number <= 0:
return True_()
number = min(number, MAX_MONTHS)
return HasProgressionPercent(self.player, number * MONTH_COEFFICIENT)
@cached_property
def has_lived_max_months(self) -> StardewRule:
return self.logic.time.has_lived_months(MAX_MONTHS)
@cached_property
def has_year_two(self) -> StardewRule:
return self.logic.time.has_lived_months(4)
@cached_property
def has_year_three(self) -> StardewRule:
return self.logic.time.has_lived_months(8)

View File

@ -0,0 +1,81 @@
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 ..mods.logic.magic_logic import MagicLogicMixin
from ..options import ToolProgression
from ..stardew_rule import StardewRule, True_, False_
from ..strings.ap_names.skill_level_names import ModSkillLevel
from ..strings.region_names import Region
from ..strings.skill_names import ModSkill
from ..strings.spells import MagicSpell
from ..strings.tool_names import ToolMaterial, Tool
tool_materials = {
ToolMaterial.copper: 1,
ToolMaterial.iron: 2,
ToolMaterial.gold: 3,
ToolMaterial.iridium: 4
}
tool_upgrade_prices = {
ToolMaterial.copper: 2000,
ToolMaterial.iron: 5000,
ToolMaterial.gold: 10000,
ToolMaterial.iridium: 25000
}
class ToolLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tool = ToolLogic(*args, **kwargs)
class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, MagicLogicMixin]]):
# Should be cached
def has_tool(self, tool: str, material: str = ToolMaterial.basic) -> StardewRule:
if material == ToolMaterial.basic or tool == Tool.scythe:
return True_()
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(tool_upgrade_prices[material])
def can_use_tool_at(self, tool: str, material: str, region: str) -> StardewRule:
return self.has_tool(tool, material) & self.logic.region.can_reach(region)
@cache_self1
def has_fishing_rod(self, level: int) -> StardewRule:
if self.options.tool_progression & ToolProgression.option_progressive:
return self.logic.received(f"Progressive {Tool.fishing_rod}", level)
if level <= 1:
return self.logic.region.can_reach(Region.beach)
prices = {2: 500, 3: 1800, 4: 7500}
level = min(level, 4)
return self.logic.money.can_spend_at(Region.fish_shop, prices[level])
# Should be cached
def can_forage(self, season: Union[str, Iterable[str]], region: str = Region.forest, need_hoe: bool = False) -> StardewRule:
season_rule = False_()
if isinstance(season, str):
season_rule = self.logic.season.has(season)
elif isinstance(season, Iterable):
season_rule = self.logic.season.has_any(season)
region_rule = self.logic.region.can_reach(region)
if need_hoe:
return season_rule & region_rule & self.logic.tool.has_tool(Tool.hoe)
return season_rule & region_rule
@cache_self1
def can_water(self, level: int) -> StardewRule:
tool_rule = self.logic.tool.has_tool(Tool.watering_can, ToolMaterial.tiers[level])
spell_rule = self.logic.received(MagicSpell.water) & self.logic.magic.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, level)
return tool_rule | spell_rule

View File

@ -0,0 +1,26 @@
from typing import Union
from .base_logic import BaseLogic, BaseLogicMixin
from .received_logic import ReceivedLogicMixin
from ..stardew_rule import True_
from ..strings.calendar_names import Weekday
class TravelingMerchantLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.traveling_merchant = TravelingMerchantLogic(*args, **kwargs)
class TravelingMerchantLogic(BaseLogic[Union[TravelingMerchantLogicMixin, ReceivedLogicMixin]]):
def has_days(self, number_days: int = 1):
if number_days <= 0:
return True_()
traveling_merchant_days = tuple(f"Traveling Merchant: {day}" for day in Weekday.all_days)
if number_days == 1:
return self.logic.received_any(*traveling_merchant_days)
tier = min(7, max(1, number_days))
return self.logic.received_n(*traveling_merchant_days, count=tier)

View File

@ -0,0 +1,19 @@
from .base_logic import BaseLogic, BaseLogicMixin
from .received_logic import ReceivedLogicMixin
from ..stardew_rule import StardewRule
from ..strings.wallet_item_names import Wallet
class WalletLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.wallet = WalletLogic(*args, **kwargs)
class WalletLogic(BaseLogic[ReceivedLogicMixin]):
def can_speak_dwarf(self) -> StardewRule:
return self.logic.received(Wallet.dwarvish_translation_guide)
def has_rusty_key(self) -> StardewRule:
return self.logic.received(Wallet.rusty_key)

View File

@ -1,16 +0,0 @@
from typing import Union
from ...strings.artisan_good_names import ArtisanGood
from ...strings.building_names import ModBuilding
from ..mod_data import ModNames
from ...strings.metal_names import MetalBar
from ...strings.region_names import Region
def get_modded_building_rules(vanilla_logic, active_mods):
buildings = {}
if ModNames.tractor in active_mods:
buildings.update({
ModBuilding.tractor_garage: vanilla_logic.can_spend_money_at(Region.carpenter, 150000) & vanilla_logic.has(MetalBar.iron) &
vanilla_logic.has(MetalBar.iridium) & vanilla_logic.has(ArtisanGood.battery_pack)})
return buildings

View File

@ -0,0 +1,28 @@
from typing import Dict, Union
from ..mod_data import ModNames
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.has_logic import HasLogicMixin
from ...logic.money_logic import MoneyLogicMixin
from ...stardew_rule import StardewRule
from ...strings.artisan_good_names import ArtisanGood
from ...strings.building_names import ModBuilding
from ...strings.metal_names import MetalBar
from ...strings.region_names import Region
class ModBuildingLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.building = ModBuildingLogic(*args, **kwargs)
class ModBuildingLogic(BaseLogic[Union[MoneyLogicMixin, HasLogicMixin]]):
def get_modded_building_rules(self) -> Dict[str, StardewRule]:
buildings = dict()
if ModNames.tractor in self.options.mods:
tractor_rule = (self.logic.money.can_spend_at(Region.carpenter, 150000) &
self.logic.has_all(MetalBar.iron, MetalBar.iridium, ArtisanGood.battery_pack))
buildings.update({ModBuilding.tractor_garage: tractor_rule})
return buildings

View File

@ -1,35 +0,0 @@
from ...strings.craftable_names import Craftable
from ...strings.performance_names import Performance
from ...strings.skill_names import Skill
from ...strings.tool_names import Tool, ToolMaterial
from ...strings.ap_names.transport_names import ModTransportation
from ...stardew_rule import StardewRule, True_, And
from ... import options
def can_reach_woods_depth(vanilla_logic, depth: int) -> StardewRule:
tier = int(depth / 25) + 1
rules = []
if depth > 10:
rules.append(vanilla_logic.has(Craftable.bomb) | vanilla_logic.has_tool(Tool.axe, ToolMaterial.iridium))
if depth > 30:
rules.append(vanilla_logic.received(ModTransportation.woods_obelisk))
if depth > 50:
rules.append(vanilla_logic.can_do_combat_at_level(Performance.great) & vanilla_logic.can_cook() &
vanilla_logic.received(ModTransportation.woods_obelisk))
if vanilla_logic.options.skill_progression == options.SkillProgression.option_progressive:
combat_tier = min(10, max(0, tier + 5))
rules.append(vanilla_logic.has_skill_level(Skill.combat, combat_tier))
return And(rules)
def has_woods_rune_to_depth(vanilla_logic, floor: int) -> StardewRule:
if vanilla_logic.options.elevator_progression == options.ElevatorProgression.option_vanilla:
return True_()
return vanilla_logic.received("Progressive Woods Obelisk Sigils", count=int(floor / 10))
def can_chop_to_depth(vanilla_logic, floor: int) -> StardewRule:
previous_elevator = max(floor - 10, 0)
return (has_woods_rune_to_depth(vanilla_logic, previous_elevator) &
can_reach_woods_depth(vanilla_logic, previous_elevator))

View File

@ -0,0 +1,73 @@
from typing import Union
from ... import options
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.combat_logic import CombatLogicMixin
from ...logic.cooking_logic import CookingLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
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 ...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.tool_names import Tool, ToolMaterial
class DeepWoodsLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.deepwoods = DeepWoodsLogic(*args, **kwargs)
class DeepWoodsLogic(BaseLogic[Union[SkillLogicMixin, ReceivedLogicMixin, HasLogicMixin, CombatLogicMixin, ToolLogicMixin, SkillLogicMixin,
CookingLogicMixin]]):
def can_reach_woods_depth(self, depth: int) -> StardewRule:
# Assuming you can always do the 10 first floor
if depth <= 10:
return true_
rules = []
if depth > 10:
rules.append(self.logic.has(Bomb.bomb) | self.logic.tool.has_tool(Tool.axe, ToolMaterial.iridium))
if depth > 30:
rules.append(self.logic.received(ModTransportation.woods_obelisk))
if depth > 50:
rules.append(self.logic.combat.can_fight_at_level(Performance.great) & self.logic.cooking.can_cook() &
self.logic.received(ModTransportation.woods_obelisk))
tier = int(depth / 25) + 1
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)
def has_woods_rune_to_depth(self, floor: int) -> StardewRule:
if self.options.elevator_progression == ElevatorProgression.option_vanilla:
return True_()
return self.logic.received(DeepWoodsItem.obelisk_sigil, int(floor / 10))
def can_chop_to_depth(self, floor: int) -> StardewRule:
previous_elevator = max(floor - 10, 0)
return (self.has_woods_rune_to_depth(previous_elevator) &
self.can_reach_woods_depth(previous_elevator))
def can_pull_sword(self) -> StardewRule:
rules = [self.logic.received(DeepWoodsItem.pendant_depths) & self.logic.received(DeepWoodsItem.pendant_community) &
self.logic.received(DeepWoodsItem.pendant_elder),
self.logic.skill.has_total_level(40)]
if ModNames.luck_skill in self.options.mods:
rules.append(self.logic.received(SkillLevel.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)

View File

@ -0,0 +1,18 @@
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.received_logic import ReceivedLogicMixin
from ...mods.mod_data import ModNames
from ...options import ElevatorProgression
from ...stardew_rule import StardewRule, True_
class ModElevatorLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.elevator = ModElevatorLogic(*args, **kwargs)
class ModElevatorLogic(BaseLogic[ReceivedLogicMixin]):
def has_skull_cavern_elevator_to_floor(self, floor: int) -> StardewRule:
if self.options.elevator_progression != ElevatorProgression.option_vanilla and ModNames.skull_cavern_elevator in self.options.mods:
return self.logic.received("Progressive Skull Cavern Elevator", floor // 25)
return True_()

View File

@ -0,0 +1,289 @@
from typing import Dict, Union
from ..mod_data import ModNames
from ... import options
from ...data.craftable_data import all_crafting_recipes_by_name
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.fishing_logic import FishingLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.money_logic import MoneyLogicMixin
from ...logic.museum_logic import MuseumLogicMixin
from ...logic.quest_logic import QuestLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
from ...logic.region_logic import RegionLogicMixin
from ...logic.relationship_logic import RelationshipLogicMixin
from ...logic.season_logic import SeasonLogicMixin
from ...logic.skill_logic import SkillLogicMixin
from ...logic.time_logic import TimeLogicMixin
from ...logic.tool_logic import ToolLogicMixin
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.food_names import SVEMeal, SVEBeverage
from ...strings.forageable_names import SVEForage, DistantLandsForageable, Forageable
from ...strings.gift_names import SVEGift
from ...strings.ingredient_names import Ingredient
from ...strings.material_names import Material
from ...strings.metal_names import all_fossils, all_artifacts, Ore, ModFossil
from ...strings.monster_drop_names import ModLoot, Loot
from ...strings.performance_names import Performance
from ...strings.quest_names import ModQuest
from ...strings.region_names import Region, SVERegion, DeepWoodsRegion, BoardingHouseRegion
from ...strings.season_names import Season
from ...strings.seed_names import SVESeed, DistantLandsSeed
from ...strings.skill_names import Skill
from ...strings.tool_names import Tool, ToolMaterial
from ...strings.villager_names import ModNPC
display_types = [ModCraftable.wooden_display, ModCraftable.hardwood_display]
display_items = all_artifacts + all_fossils
class ModItemLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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]]):
def get_modded_item_rules(self) -> Dict[str, StardewRule]:
items = dict()
if ModNames.sve in self.options.mods:
items.update(self.get_sve_item_rules())
if ModNames.archaeology in self.options.mods:
items.update(self.get_archaeology_item_rules())
if ModNames.distant_lands in self.options.mods:
items.update(self.get_distant_lands_item_rules())
if ModNames.boarding_house in self.options.mods:
items.update(self.get_boarding_house_item_rules())
return items
def modify_vanilla_item_rules_with_mod_additions(self, item_rule: Dict[str, StardewRule]):
if ModNames.sve in self.options.mods:
item_rule.update(self.get_modified_item_rules_for_sve(item_rule))
if ModNames.deepwoods in self.options.mods:
item_rule.update(self.get_modified_item_rules_for_deep_woods(item_rule))
return item_rule
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,
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 &
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(
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.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),
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)),
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) &
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)) &
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),
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_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
}
# @formatter:on
def get_modified_item_rules_for_sve(self, items: Dict[str, StardewRule]):
return {
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)),
}
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
Ingredient.wheat_flour: items[Ingredient.wheat_flour] | self.logic.tool.can_use_tool_at(Tool.axe, ToolMaterial.gold, DeepWoodsRegion.floor_50),
# Gingerbread House
}
if self.options.tool_progression & options.ToolProgression.option_progressive:
options_to_update.update({
Ore.iridium: items[Ore.iridium] | self.logic.tool.can_use_tool_at(Tool.axe, ToolMaterial.iridium, DeepWoodsRegion.floor_50), # Iridium Tree
})
return options_to_update
def get_archaeology_item_rules(self):
archaeology_item_rules = {}
preservation_chamber_rule = self.logic.has(ModMachine.preservation_chamber)
hardwood_preservation_chamber_rule = self.logic.has(ModMachine.hardwood_preservation_chamber)
for item in display_items:
for display_type in display_types:
if item == "Trilobite":
location_name = f"{display_type}: Trilobite Fossil"
else:
location_name = f"{display_type}: {item}"
display_item_rule = self.logic.crafting.can_craft(all_crafting_recipes_by_name[display_type]) & self.logic.has(item)
if "Wooden" in display_type:
archaeology_item_rules[location_name] = display_item_rule & preservation_chamber_rule
else:
archaeology_item_rules[location_name] = display_item_rule & hardwood_preservation_chamber_rule
return archaeology_item_rules
def get_distant_lands_item_rules(self):
return {
DistantLandsForageable.swamp_herb: self.logic.region.can_reach(Region.witch_swamp),
DistantLandsForageable.brown_amanita: self.logic.region.can_reach(Region.witch_swamp),
DistantLandsSeed.vile_ancient_fruit: self.logic.quest.can_complete_quest(ModQuest.WitchOrder) | self.logic.quest.can_complete_quest(
ModQuest.CorruptedCropsTask),
DistantLandsSeed.void_mint: self.logic.quest.can_complete_quest(ModQuest.WitchOrder) | self.logic.quest.can_complete_quest(
ModQuest.CorruptedCropsTask),
DistantLandsCrop.void_mint: self.logic.season.has_any_not_winter() & self.logic.has(DistantLandsSeed.void_mint),
DistantLandsCrop.vile_ancient_fruit: self.logic.season.has_any_not_winter() & self.logic.has(DistantLandsSeed.vile_ancient_fruit),
}
def get_boarding_house_item_rules(self):
return {
# Mob Drops from lost valley enemies
ModArtisanGood.pterodactyl_egg: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_claw: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_ribs: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_vertebra: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_skull: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_phalange: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_l_wing_bone: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.pterodactyl_r_wing_bone: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_skull: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_tooth: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_femur: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_pelvis: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_ribs: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_vertebra: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.dinosaur_claw: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.good),
ModFossil.neanderthal_skull: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.great),
ModFossil.neanderthal_ribs: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.great),
ModFossil.neanderthal_pelvis: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.great),
ModFossil.neanderthal_limb_bones: self.logic.region.can_reach_any((BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1,
BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level(
Performance.great),
}
def has_seed_unlocked(self, seed_name: str):
if self.options.cropsanity == Cropsanity.option_disabled:
return True_()
return self.logic.received(seed_name)

View File

@ -1,80 +0,0 @@
from ...strings.region_names import MagicRegion
from ...mods.mod_data import ModNames
from ...strings.spells import MagicSpell
from ...strings.ap_names.skill_level_names import ModSkillLevel
from ...stardew_rule import Count, StardewRule, False_
from ... import options
def can_use_clear_debris_instead_of_tool_level(vanilla_logic, level: int) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
return vanilla_logic.received(MagicSpell.clear_debris) & can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, level)
def can_use_altar(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
return vanilla_logic.can_reach_region(MagicRegion.altar)
def has_any_spell(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
return can_use_altar(vanilla_logic)
def has_attack_spell_count(vanilla_logic, count: int) -> StardewRule:
attack_spell_rule = [vanilla_logic.received(MagicSpell.fireball), vanilla_logic.received(
MagicSpell.frostbite), vanilla_logic.received(MagicSpell.shockwave), vanilla_logic.received(MagicSpell.spirit),
vanilla_logic.received(MagicSpell.meteor)
]
return Count(count, attack_spell_rule)
def has_support_spell_count(vanilla_logic, count: int) -> StardewRule:
support_spell_rule = [can_use_altar(vanilla_logic), vanilla_logic.received(ModSkillLevel.magic_level, 2),
vanilla_logic.received(MagicSpell.descend), vanilla_logic.received(MagicSpell.heal),
vanilla_logic.received(MagicSpell.tendrils)]
return Count(count, support_spell_rule)
def has_decent_spells(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 2)
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 1)
return magic_resource_rule & magic_attack_options_rule
def has_good_spells(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 4)
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 2)
magic_support_options_rule = has_support_spell_count(vanilla_logic, 1)
return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule
def has_great_spells(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 6)
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 3)
magic_support_options_rule = has_support_spell_count(vanilla_logic, 1)
return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule
def has_amazing_spells(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 8)
magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 4)
magic_support_options_rule = has_support_spell_count(vanilla_logic, 2)
return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule
def can_blink(vanilla_logic) -> StardewRule:
if ModNames.magic not in vanilla_logic.options.mods:
return False_()
return vanilla_logic.received(MagicSpell.blink) & can_use_altar(vanilla_logic)

View File

@ -0,0 +1,82 @@
from typing import Union
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.has_logic import HasLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
from ...logic.region_logic import RegionLogicMixin
from ...mods.mod_data import ModNames
from ...stardew_rule import StardewRule, False_
from ...strings.ap_names.skill_level_names import ModSkillLevel
from ...strings.region_names import MagicRegion
from ...strings.spells import MagicSpell
class MagicLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.magic = MagicLogic(*args, **kwargs)
# TODO add logic.mods.magic for altar
class MagicLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
def can_use_clear_debris_instead_of_tool_level(self, level: int) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
return self.logic.received(MagicSpell.clear_debris) & self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, level)
def can_use_altar(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
return self.logic.region.can_reach(MagicRegion.altar)
def has_any_spell(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
return self.can_use_altar()
def has_attack_spell_count(self, count: int) -> StardewRule:
attack_spell_rule = [self.logic.received(MagicSpell.fireball), self.logic.received(MagicSpell.frostbite), self.logic.received(MagicSpell.shockwave),
self.logic.received(MagicSpell.spirit), self.logic.received(MagicSpell.meteor)]
return self.logic.count(count, *attack_spell_rule)
def has_support_spell_count(self, count: int) -> StardewRule:
support_spell_rule = [self.can_use_altar(), self.logic.received(ModSkillLevel.magic_level, 2),
self.logic.received(MagicSpell.descend), self.logic.received(MagicSpell.heal),
self.logic.received(MagicSpell.tendrils)]
return self.logic.count(count, *support_spell_rule)
def has_decent_spells(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 2)
magic_attack_options_rule = self.has_attack_spell_count(1)
return magic_resource_rule & magic_attack_options_rule
def has_good_spells(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 4)
magic_attack_options_rule = self.has_attack_spell_count(2)
magic_support_options_rule = self.has_support_spell_count(1)
return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule
def has_great_spells(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 6)
magic_attack_options_rule = self.has_attack_spell_count(3)
magic_support_options_rule = self.has_support_spell_count(1)
return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule
def has_amazing_spells(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
magic_resource_rule = self.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, 8)
magic_attack_options_rule = self.has_attack_spell_count(4)
magic_support_options_rule = self.has_support_spell_count(2)
return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule
def can_blink(self) -> StardewRule:
if ModNames.magic not in self.options.mods:
return False_()
return self.logic.received(MagicSpell.blink) & self.can_use_altar()

View File

@ -0,0 +1,21 @@
from .buildings_logic import ModBuildingLogicMixin
from .deepwoods_logic import DeepWoodsLogicMixin
from .elevator_logic import ModElevatorLogicMixin
from .item_logic import ModItemLogicMixin
from .magic_logic import MagicLogicMixin
from .quests_logic import ModQuestLogicMixin
from .skills_logic import ModSkillLogicMixin
from .special_orders_logic import ModSpecialOrderLogicMixin
from .sve_logic import SVELogicMixin
from ...logic.base_logic import BaseLogicMixin
class ModLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mod = ModLogic(*args, **kwargs)
class ModLogic(ModElevatorLogicMixin, MagicLogicMixin, ModSkillLogicMixin, ModItemLogicMixin, ModQuestLogicMixin, ModBuildingLogicMixin,
ModSpecialOrderLogicMixin, DeepWoodsLogicMixin, SVELogicMixin):
pass

View File

@ -0,0 +1,21 @@
from typing import Tuple
from ...mods.mod_data import ModNames
from ...options import Mods
def get_mod_skill_levels(mods: Mods) -> Tuple[str]:
skills_items = []
if ModNames.luck_skill in mods:
skills_items.append("Luck Level")
if ModNames.socializing_skill in mods:
skills_items.append("Socializing Level")
if ModNames.magic in mods:
skills_items.append("Magic Level")
if ModNames.archaeology in mods:
skills_items.append("Archaeology Level")
if ModNames.binning_skill in mods:
skills_items.append("Binning Level")
if ModNames.cooking_skill in mods:
skills_items.append("Cooking Level")
return tuple(skills_items)

View File

@ -1,31 +0,0 @@
from typing import Union
from ...strings.quest_names import ModQuest
from ..mod_data import ModNames
from ...strings.food_names import Meal, Beverage
from ...strings.monster_drop_names import Loot
from ...strings.villager_names import ModNPC
from ...strings.season_names import Season
from ...strings.region_names import Region
def get_modded_quest_rules(vanilla_logic, active_mods):
quests = {}
if ModNames.juna in active_mods:
quests.update({
ModQuest.JunaCola: vanilla_logic.has_relationship(ModNPC.juna, 3) & vanilla_logic.has(Beverage.joja_cola),
ModQuest.JunaSpaghetti: vanilla_logic.has_relationship(ModNPC.juna, 6) & vanilla_logic.has(Meal.spaghetti)
})
if ModNames.ginger in active_mods:
quests.update({
ModQuest.MrGinger: vanilla_logic.has_relationship(ModNPC.mr_ginger, 6) & vanilla_logic.has(Loot.void_essence)
})
if ModNames.ayeisha in active_mods:
quests.update({
ModQuest.AyeishaEnvelope: (vanilla_logic.has_season(Season.spring) | vanilla_logic.has_season(Season.fall)) &
vanilla_logic.can_reach_region(Region.mountain),
ModQuest.AyeishaRing: vanilla_logic.has_season(Season.winter) & vanilla_logic.can_reach_region(Region.forest)
})
return quests

View File

@ -0,0 +1,128 @@
from typing import Dict, Union
from ..mod_data import ModNames
from ...logic.base_logic import BaseLogic, BaseLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.quest_logic import QuestLogicMixin
from ...logic.monster_logic import MonsterLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
from ...logic.region_logic import RegionLogicMixin
from ...logic.relationship_logic import RelationshipLogicMixin
from ...logic.season_logic import SeasonLogicMixin
from ...logic.time_logic import TimeLogicMixin
from ...stardew_rule import StardewRule
from ...strings.animal_product_names import AnimalProduct
from ...strings.artisan_good_names import ArtisanGood
from ...strings.crop_names import Fruit, SVEFruit, SVEVegetable, Vegetable
from ...strings.fertilizer_names import Fertilizer
from ...strings.food_names import Meal, Beverage
from ...strings.forageable_names import SVEForage
from ...strings.material_names import Material
from ...strings.metal_names import Ore, MetalBar
from ...strings.monster_drop_names import Loot
from ...strings.monster_names import Monster
from ...strings.quest_names import Quest, ModQuest
from ...strings.region_names import Region, SVERegion, BoardingHouseRegion
from ...strings.season_names import Season
from ...strings.villager_names import ModNPC, NPC
from ...strings.wallet_item_names import Wallet
class ModQuestLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.quest = ModQuestLogic(*args, **kwargs)
class ModQuestLogic(BaseLogic[Union[HasLogicMixin, QuestLogicMixin, ReceivedLogicMixin, RegionLogicMixin,
TimeLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MonsterLogicMixin]]):
def get_modded_quest_rules(self) -> Dict[str, StardewRule]:
quests = dict()
quests.update(self._get_juna_quest_rules())
quests.update(self._get_mr_ginger_quest_rules())
quests.update(self._get_ayeisha_quest_rules())
quests.update(self._get_sve_quest_rules())
quests.update(self._get_distant_lands_quest_rules())
quests.update(self._get_boarding_house_quest_rules())
quests.update((self._get_hat_mouse_quest_rules()))
return quests
def _get_juna_quest_rules(self):
if ModNames.juna not in self.options.mods:
return {}
return {
ModQuest.JunaCola: self.logic.relationship.has_hearts(ModNPC.juna, 3) & self.logic.has(Beverage.joja_cola),
ModQuest.JunaSpaghetti: self.logic.relationship.has_hearts(ModNPC.juna, 6) & self.logic.has(Meal.spaghetti)
}
def _get_mr_ginger_quest_rules(self):
if ModNames.ginger not in self.options.mods:
return {}
return {
ModQuest.MrGinger: self.logic.relationship.has_hearts(ModNPC.mr_ginger, 6) & self.logic.has(Loot.void_essence)
}
def _get_ayeisha_quest_rules(self):
if ModNames.ayeisha not in self.options.mods:
return {}
return {
ModQuest.AyeishaEnvelope: (self.logic.season.has(Season.spring) | self.logic.season.has(Season.fall)),
ModQuest.AyeishaRing: self.logic.season.has(Season.winter)
}
def _get_sve_quest_rules(self):
if ModNames.sve not in self.options.mods:
return {}
return {
ModQuest.RailroadBoulder: self.logic.received(Wallet.skull_key) & self.logic.has_all(*(Ore.iridium, Material.coal)) &
self.logic.region.can_reach(Region.blacksmith) & self.logic.region.can_reach(Region.railroad),
ModQuest.GrandpasShed: self.logic.has_all(*(Material.hardwood, MetalBar.iron, ArtisanGood.battery_pack, Material.stone)) &
self.logic.region.can_reach(SVERegion.grandpas_shed),
ModQuest.MarlonsBoat: self.logic.has_all(*(Loot.void_essence, Loot.solar_essence, Loot.slime, Loot.bat_wing, Loot.bug_meat)) &
self.logic.relationship.can_meet(ModNPC.lance) & self.logic.region.can_reach(SVERegion.guild_summit),
ModQuest.AuroraVineyard: self.logic.has(Fruit.starfruit) & self.logic.region.can_reach(SVERegion.aurora_vineyard),
ModQuest.MonsterCrops: self.logic.has_all(*(SVEVegetable.monster_mushroom, SVEFruit.slime_berry, SVEFruit.monster_fruit, SVEVegetable.void_root)),
ModQuest.VoidSoul: self.logic.has(SVEForage.void_soul) & self.logic.region.can_reach(Region.farm) &
self.logic.season.has_any_not_winter() & self.logic.region.can_reach(SVERegion.badlands_entrance) &
self.logic.relationship.has_hearts(NPC.krobus, 10) & self.logic.quest.can_complete_quest(ModQuest.MonsterCrops) &
self.logic.monster.can_kill_any((Monster.shadow_brute, Monster.shadow_shaman, Monster.shadow_sniper)),
}
def _get_distant_lands_quest_rules(self):
if ModNames.distant_lands not in self.options.mods:
return {}
return {
ModQuest.CorruptedCropsTask: self.logic.region.can_reach(Region.wizard_tower) & self.logic.has(Fertilizer.deluxe) &
self.logic.quest.can_complete_quest(Quest.magic_ink),
ModQuest.WitchOrder: self.logic.region.can_reach(Region.witch_swamp) & self.logic.has(Fertilizer.deluxe) &
self.logic.quest.can_complete_quest(Quest.magic_ink),
ModQuest.ANewPot: self.logic.region.can_reach(Region.saloon) &
self.logic.region.can_reach(Region.sam_house) & self.logic.region.can_reach(Region.pierre_store) &
self.logic.region.can_reach(Region.blacksmith) & self.logic.has(MetalBar.iron) & self.logic.relationship.has_hearts(ModNPC.goblin,
6),
ModQuest.FancyBlanketTask: self.logic.region.can_reach(Region.haley_house) & self.logic.has(AnimalProduct.wool) &
self.logic.has(ArtisanGood.cloth) & self.logic.relationship.has_hearts(ModNPC.goblin, 10) &
self.logic.relationship.has_hearts(NPC.emily, 8) & self.logic.season.has(Season.winter)
}
def _get_boarding_house_quest_rules(self):
if ModNames.boarding_house not in self.options.mods:
return {}
return {
ModQuest.PumpkinSoup: self.logic.region.can_reach(BoardingHouseRegion.boarding_house_first) & self.logic.has(Vegetable.pumpkin)
}
def _get_hat_mouse_quest_rules(self):
if ModNames.lacey not in self.options.mods:
return {}
return {
ModQuest.HatMouseHat: self.logic.relationship.has_hearts(ModNPC.lacey, 2) & self.logic.time.has_lived_months(4)
}

View File

@ -1,94 +0,0 @@
from typing import List, Union
from . import magic
from ...strings.building_names import Building
from ...strings.geode_names import Geode
from ...strings.region_names import Region
from ...strings.skill_names import ModSkill
from ...strings.spells import MagicSpell
from ...strings.machine_names import Machine
from ...strings.tool_names import Tool, ToolMaterial
from ...mods.mod_data import ModNames
from ...data.villagers_data import all_villagers
from ...stardew_rule import Count, StardewRule, False_
from ... import options
def append_mod_skill_level(skills_items: List[str], active_mods):
if ModNames.luck_skill in active_mods:
skills_items.append("Luck Level")
if ModNames.socializing_skill in active_mods:
skills_items.append("Socializing Level")
if ModNames.magic in active_mods:
skills_items.append("Magic Level")
if ModNames.archaeology in active_mods:
skills_items.append("Archaeology Level")
if ModNames.binning_skill in active_mods:
skills_items.append("Binning Level")
if ModNames.cooking_skill in active_mods:
skills_items.append("Cooking Level")
def can_earn_mod_skill_level(logic, skill: str, level: int) -> StardewRule:
if ModNames.luck_skill in logic.options.mods and skill == ModSkill.luck:
return can_earn_luck_skill_level(logic, level)
if ModNames.magic in logic.options.mods and skill == ModSkill.magic:
return can_earn_magic_skill_level(logic, level)
if ModNames.socializing_skill in logic.options.mods and skill == ModSkill.socializing:
return can_earn_socializing_skill_level(logic, level)
if ModNames.archaeology in logic.options.mods and skill == ModSkill.archaeology:
return can_earn_archaeology_skill_level(logic, level)
if ModNames.cooking_skill in logic.options.mods and skill == ModSkill.cooking:
return can_earn_cooking_skill_level(logic, level)
if ModNames.binning_skill in logic.options.mods and skill == ModSkill.binning:
return can_earn_binning_skill_level(logic, level)
return False_()
def can_earn_luck_skill_level(vanilla_logic, level: int) -> StardewRule:
if level >= 6:
return vanilla_logic.can_fish_chests() | vanilla_logic.can_open_geode(Geode.magma)
else:
return vanilla_logic.can_fish_chests() | vanilla_logic.can_open_geode(Geode.geode)
def can_earn_magic_skill_level(vanilla_logic, level: int) -> StardewRule:
spell_count = [vanilla_logic.received(MagicSpell.clear_debris), vanilla_logic.received(MagicSpell.water),
vanilla_logic.received(MagicSpell.blink), vanilla_logic.received(MagicSpell.fireball),
vanilla_logic.received(MagicSpell.frostbite),
vanilla_logic.received(MagicSpell.descend), vanilla_logic.received(MagicSpell.tendrils),
vanilla_logic.received(MagicSpell.shockwave),
vanilla_logic.received(MagicSpell.meteor),
vanilla_logic.received(MagicSpell.spirit)]
return magic.can_use_altar(vanilla_logic) & Count(level, spell_count)
def can_earn_socializing_skill_level(vanilla_logic, level: int) -> StardewRule:
villager_count = []
for villager in all_villagers:
if villager.mod_name in vanilla_logic.options.mods or villager.mod_name is None:
villager_count.append(vanilla_logic.can_earn_relationship(villager.name, level))
return Count(level * 2, villager_count)
def can_earn_archaeology_skill_level(vanilla_logic, level: int) -> StardewRule:
if level >= 6:
return vanilla_logic.can_do_panning() | vanilla_logic.has_tool(Tool.hoe, ToolMaterial.gold)
else:
return vanilla_logic.can_do_panning() | vanilla_logic.has_tool(Tool.hoe, ToolMaterial.basic)
def can_earn_cooking_skill_level(vanilla_logic, level: int) -> StardewRule:
if level >= 6:
return vanilla_logic.can_cook() & vanilla_logic.can_fish() & vanilla_logic.can_reach_region(Region.saloon) & \
vanilla_logic.has_building(Building.coop) & vanilla_logic.has_building(Building.barn)
else:
return vanilla_logic.can_cook()
def can_earn_binning_skill_level(vanilla_logic, level: int) -> StardewRule:
if level >= 6:
return vanilla_logic.can_reach_region(Region.town) & vanilla_logic.has(Machine.recycling_machine) & \
(vanilla_logic.can_fish() | vanilla_logic.can_crab_pot())
else:
return vanilla_logic.can_reach_region(Region.town) | (vanilla_logic.has(Machine.recycling_machine) &
(vanilla_logic.can_fish() | vanilla_logic.can_crab_pot()))

View File

@ -0,0 +1,110 @@
from typing import Union
from .magic_logic import MagicLogicMixin
from ...data.villagers_data import all_villagers
from ...logic.action_logic import ActionLogicMixin
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.building_logic import BuildingLogicMixin
from ...logic.cooking_logic import CookingLogicMixin
from ...logic.fishing_logic import FishingLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
from ...logic.region_logic import RegionLogicMixin
from ...logic.relationship_logic import RelationshipLogicMixin
from ...logic.tool_logic import ToolLogicMixin
from ...mods.mod_data import ModNames
from ...options import SkillProgression
from ...stardew_rule import StardewRule, False_, True_
from ...strings.ap_names.mods.mod_items import SkillLevel
from ...strings.craftable_names import ModCraftable, ModMachine
from ...strings.building_names import Building
from ...strings.geode_names import Geode
from ...strings.machine_names import Machine
from ...strings.region_names import Region
from ...strings.skill_names import ModSkill
from ...strings.spells import MagicSpell
from ...strings.tool_names import Tool, ToolMaterial
class ModSkillLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.skill = ModSkillLogic(*args, **kwargs)
class ModSkillLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, ActionLogicMixin, RelationshipLogicMixin, BuildingLogicMixin,
ToolLogicMixin, FishingLogicMixin, CookingLogicMixin, MagicLogicMixin]]):
def has_mod_level(self, skill: str, level: int) -> StardewRule:
if level <= 0:
return True_()
if self.options.skill_progression == SkillProgression.option_progressive:
return self.logic.received(f"{skill} Level", level)
return self.can_earn_mod_skill_level(skill, level)
def can_earn_mod_skill_level(self, skill: str, level: int) -> StardewRule:
if ModNames.luck_skill in self.options.mods and skill == ModSkill.luck:
return self.can_earn_luck_skill_level(level)
if ModNames.magic in self.options.mods and skill == ModSkill.magic:
return self.can_earn_magic_skill_level(level)
if ModNames.socializing_skill in self.options.mods and skill == ModSkill.socializing:
return self.can_earn_socializing_skill_level(level)
if ModNames.archaeology in self.options.mods and skill == ModSkill.archaeology:
return self.can_earn_archaeology_skill_level(level)
if ModNames.cooking_skill in self.options.mods and skill == ModSkill.cooking:
return self.can_earn_cooking_skill_level(level)
if ModNames.binning_skill in self.options.mods and skill == ModSkill.binning:
return self.can_earn_binning_skill_level(level)
return False_()
def can_earn_luck_skill_level(self, level: int) -> StardewRule:
if level >= 6:
return self.logic.fishing.can_fish_chests() | self.logic.action.can_open_geode(Geode.magma)
if level >= 3:
return self.logic.fishing.can_fish_chests() | self.logic.action.can_open_geode(Geode.geode)
return True_() # You can literally wake up and or get them by opening starting chests.
def can_earn_magic_skill_level(self, level: int) -> StardewRule:
spell_count = [self.logic.received(MagicSpell.clear_debris), self.logic.received(MagicSpell.water),
self.logic.received(MagicSpell.blink), self.logic.received(MagicSpell.fireball),
self.logic.received(MagicSpell.frostbite),
self.logic.received(MagicSpell.descend), self.logic.received(MagicSpell.tendrils),
self.logic.received(MagicSpell.shockwave),
self.logic.received(MagicSpell.meteor),
self.logic.received(MagicSpell.spirit)]
return self.logic.count(level, *spell_count)
def can_earn_socializing_skill_level(self, level: int) -> StardewRule:
villager_count = []
for villager in all_villagers:
if villager.mod_name in self.options.mods or villager.mod_name is None:
villager_count.append(self.logic.relationship.can_earn_relationship(villager.name, level))
return self.logic.count(level * 2, *villager_count)
def can_earn_archaeology_skill_level(self, level: int) -> StardewRule:
shifter_rule = True_()
preservation_rule = True_()
if self.options.skill_progression == self.options.skill_progression.option_progressive:
shifter_rule = self.logic.has(ModCraftable.water_shifter)
preservation_rule = self.logic.has(ModMachine.hardwood_preservation_chamber)
if level >= 8:
return (self.logic.action.can_pan() & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.gold)) & shifter_rule & preservation_rule
if level >= 5:
return (self.logic.action.can_pan() & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iron)) & shifter_rule
if level >= 3:
return self.logic.action.can_pan() | self.logic.tool.has_tool(Tool.hoe, ToolMaterial.copper)
return self.logic.action.can_pan() | self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic)
def can_earn_cooking_skill_level(self, level: int) -> StardewRule:
if level >= 6:
return self.logic.cooking.can_cook() & self.logic.region.can_reach(Region.saloon) & \
self.logic.building.has_building(Building.coop) & self.logic.building.has_building(Building.barn)
else:
return self.logic.cooking.can_cook()
def can_earn_binning_skill_level(self, level: int) -> StardewRule:
if level >= 6:
return self.logic.has(Machine.recycling_machine)
else:
return True_() # You can always earn levels 1-5 with trash cans

View File

@ -1,10 +0,0 @@
from ...stardew_rule import Count, StardewRule, True_
from ...mods.mod_data import ModNames
from ... import options
def has_skull_cavern_elevator_to_floor(self, floor: int) -> StardewRule:
if self.options.elevator_progression != options.ElevatorProgression.option_vanilla and \
ModNames.skull_cavern_elevator in self.options.mods:
return self.received("Progressive Skull Cavern Elevator", floor // 25)
return True_()

View File

@ -1,24 +0,0 @@
from typing import Union
from ...strings.craftable_names import Craftable
from ...strings.food_names import Meal
from ...strings.material_names import Material
from ...strings.monster_drop_names import Loot
from ...strings.region_names import Region
from ...strings.special_order_names import SpecialOrder, ModSpecialOrder
from ...strings.villager_names import ModNPC
from ..mod_data import ModNames
def get_modded_special_orders_rules(vanilla_logic, active_mods):
special_orders = {}
if ModNames.juna in active_mods:
special_orders.update({
ModSpecialOrder.junas_monster_mash: vanilla_logic.has_relationship(ModNPC.juna, 4) &
vanilla_logic.can_complete_special_order(SpecialOrder.a_curious_substance) &
vanilla_logic.has_rusty_key() &
vanilla_logic.can_reach_region(Region.forest) & vanilla_logic.has(Craftable.monster_musk) &
vanilla_logic.has("Energy Tonic") & vanilla_logic.has(Material.sap) & vanilla_logic.has(Loot.bug_meat) &
vanilla_logic.has(Craftable.oil_of_garlic) & vanilla_logic.has(Meal.strange_bun)
})
return special_orders

View File

@ -0,0 +1,76 @@
from typing import Union
from ...data.craftable_data import all_crafting_recipes_by_name
from ..mod_data import ModNames
from ...logic.action_logic import ActionLogicMixin
from ...logic.artisan_logic import ArtisanLogicMixin
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.crafting_logic import CraftingLogicMixin
from ...logic.crop_logic import CropLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
from ...logic.region_logic import RegionLogicMixin
from ...logic.relationship_logic import RelationshipLogicMixin
from ...logic.season_logic import SeasonLogicMixin
from ...logic.wallet_logic import WalletLogicMixin
from ...strings.ap_names.community_upgrade_names import CommunityUpgrade
from ...strings.artisan_good_names import ArtisanGood
from ...strings.craftable_names import Consumable, Edible, Bomb
from ...strings.crop_names import Fruit
from ...strings.fertilizer_names import Fertilizer
from ...strings.food_names import Meal
from ...strings.geode_names import Geode
from ...strings.material_names import Material
from ...strings.metal_names import MetalBar, Artifact
from ...strings.monster_drop_names import Loot
from ...strings.region_names import Region, SVERegion
from ...strings.special_order_names import SpecialOrder, ModSpecialOrder
from ...strings.villager_names import ModNPC
class ModSpecialOrderLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.special_order = ModSpecialOrderLogic(*args, **kwargs)
class ModSpecialOrderLogic(BaseLogic[Union[ActionLogicMixin, ArtisanLogicMixin, CraftingLogicMixin, CropLogicMixin, HasLogicMixin, RegionLogicMixin,
ReceivedLogicMixin, RelationshipLogicMixin, SeasonLogicMixin, WalletLogicMixin]]):
def get_modded_special_orders_rules(self):
special_orders = {}
if ModNames.juna in self.options.mods:
special_orders.update({
ModSpecialOrder.junas_monster_mash: self.logic.relationship.has_hearts(ModNPC.juna, 4) &
self.registry.special_order_rules[SpecialOrder.a_curious_substance] &
self.logic.wallet.has_rusty_key() &
self.logic.region.can_reach(Region.forest) & self.logic.has(Consumable.monster_musk) &
self.logic.has("Energy Tonic") & self.logic.has(Material.sap) & self.logic.has(Loot.bug_meat) &
self.logic.has(Edible.oil_of_garlic) & self.logic.has(Meal.strange_bun)
})
if ModNames.sve in self.options.mods:
special_orders.update({
ModSpecialOrder.andys_cellar: self.logic.has(Material.stone) & self.logic.has(Material.wood) & self.logic.has(Material.hardwood) &
self.logic.has(MetalBar.iron) & self.logic.received(CommunityUpgrade.movie_theater, 1) &
self.logic.region.can_reach(SVERegion.fairhaven_farm),
ModSpecialOrder.a_mysterious_venture: self.logic.has(Bomb.cherry_bomb) & self.logic.has(Bomb.bomb) & self.logic.has(Bomb.mega_bomb) &
self.logic.region.can_reach(Region.adventurer_guild),
ModSpecialOrder.an_elegant_reception: self.logic.artisan.can_keg(Fruit.starfruit) & self.logic.has(ArtisanGood.cheese) &
self.logic.has(ArtisanGood.goat_cheese) & self.logic.season.has_any_not_winter() &
self.logic.region.can_reach(SVERegion.jenkins_cellar),
ModSpecialOrder.fairy_garden: self.logic.has(Consumable.fairy_dust) &
self.logic.region.can_reach(Region.island_south) & (
self.logic.action.can_open_geode(Geode.frozen) | self.logic.action.can_open_geode(Geode.omni)) &
self.logic.region.can_reach(SVERegion.blue_moon_vineyard),
ModSpecialOrder.homemade_fertilizer: self.logic.crafting.can_craft(all_crafting_recipes_by_name[Fertilizer.quality]) &
self.logic.region.can_reach(SVERegion.susans_house) # quest requires you make the fertilizer
})
if ModNames.jasper in self.options.mods:
special_orders.update({
ModSpecialOrder.dwarf_scroll: self.logic.has_all(*(Artifact.dwarf_scroll_i, Artifact.dwarf_scroll_ii, Artifact.dwarf_scroll_iii,
Artifact.dwarf_scroll_iv,)),
ModSpecialOrder.geode_order: self.logic.has_all(*(Geode.geode, Geode.frozen, Geode.magma, Geode.omni,)) &
self.logic.relationship.has_hearts(ModNPC.jasper, 8)
})
return special_orders

View File

@ -0,0 +1,55 @@
from typing import Union
from ..mod_regions import SVERegion
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.combat_logic import CombatLogicMixin
from ...logic.cooking_logic import CookingLogicMixin
from ...logic.has_logic import HasLogicMixin
from ...logic.money_logic import MoneyLogicMixin
from ...logic.quest_logic import QuestLogicMixin
from ...logic.received_logic import ReceivedLogicMixin
from ...logic.region_logic import RegionLogicMixin
from ...logic.relationship_logic import RelationshipLogicMixin
from ...logic.season_logic import SeasonLogicMixin
from ...logic.time_logic import TimeLogicMixin
from ...logic.tool_logic import ToolLogicMixin
from ...strings.ap_names.mods.mod_items import SVELocation, SVERunes, SVEQuestItem
from ...strings.quest_names import Quest
from ...strings.region_names import Region
from ...strings.tool_names import Tool, ToolMaterial
from ...strings.wallet_item_names import Wallet
from ...stardew_rule import Or
from ...strings.quest_names import ModQuest
class SVELogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sve = SVELogic(*args, **kwargs)
class SVELogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, QuestLogicMixin, RegionLogicMixin, RelationshipLogicMixin, TimeLogicMixin, ToolLogicMixin,
CookingLogicMixin, MoneyLogicMixin, CombatLogicMixin, SeasonLogicMixin, QuestLogicMixin]]):
def initialize_rules(self):
self.registry.sve_location_rules.update({
SVELocation.tempered_galaxy_sword: self.logic.money.can_spend_at(SVERegion.alesia_shop, 350000),
SVELocation.tempered_galaxy_dagger: self.logic.money.can_spend_at(SVERegion.isaac_shop, 600000),
SVELocation.tempered_galaxy_hammer: self.logic.money.can_spend_at(SVERegion.isaac_shop, 400000),
})
def has_any_rune(self):
rune_list = SVERunes.nexus_items
return Or(*(self.logic.received(rune) for rune in rune_list))
def has_iridium_bomb(self):
if self.options.quest_locations < 0:
return self.logic.quest.can_complete_quest(ModQuest.RailroadBoulder)
return self.logic.received(SVEQuestItem.iridium_bomb)
def can_buy_bear_recipe(self):
access_rule = (self.logic.quest.can_complete_quest(Quest.strange_note) & self.logic.tool.has_tool(Tool.axe, ToolMaterial.basic) &
self.logic.tool.has_tool(Tool.pickaxe, ToolMaterial.basic))
forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods, Region.mountain))
knowledge_rule = self.logic.received(Wallet.bears_knowledge)
return access_rule & forage_rule & knowledge_rule

View File

@ -21,6 +21,13 @@ class ModNames:
ayeisha = "Ayeisha - The Postal Worker (Custom NPC)"
riley = "Custom NPC - Riley"
skull_cavern_elevator = "Skull Cavern Elevator"
sve = "Stardew Valley Expanded"
alecto = "Alecto the Witch"
distant_lands = "Distant Lands - Witch Swamp Overhaul"
lacey = "Hat Mouse Lacey"
boarding_house = "Boarding House and Bus Stop Extension"
jasper_sve = jasper + "," + sve
all_mods = frozenset({ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack,
@ -28,4 +35,5 @@ all_mods = frozenset({ModNames.deepwoods, ModNames.tractor, ModNames.big_backpac
ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna,
ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene,
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator})
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator, ModNames.sve, ModNames.alecto,
ModNames.distant_lands, ModNames.lacey, ModNames.boarding_house})

View File

@ -0,0 +1,40 @@
from typing import Dict, Tuple
from .mod_data import ModNames
from ..strings.monster_names import Monster
from ..strings.region_names import SVERegion, DeepWoodsRegion, BoardingHouseRegion
sve_monsters_locations: Dict[str, Tuple[str, ...]] = {
Monster.shadow_brute_dangerous: (SVERegion.highlands_cavern,),
Monster.shadow_sniper: (SVERegion.highlands_cavern,),
Monster.shadow_shaman_dangerous: (SVERegion.highlands_cavern,),
Monster.mummy_dangerous: (SVERegion.crimson_badlands,),
Monster.royal_serpent: (SVERegion.crimson_badlands,),
Monster.skeleton_dangerous: (SVERegion.crimson_badlands,),
Monster.skeleton_mage: (SVERegion.crimson_badlands,),
Monster.dust_sprite_dangerous: (SVERegion.highlands_outside,),
}
deepwoods_monsters_locations: Dict[str, Tuple[str, ...]] = {
Monster.shadow_brute: (DeepWoodsRegion.floor_10,),
Monster.cave_fly: (DeepWoodsRegion.floor_10,),
Monster.green_slime: (DeepWoodsRegion.floor_10,),
}
boardinghouse_monsters_locations: Dict[str, Tuple[str, ...]] = {
Monster.shadow_brute: (BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, BoardingHouseRegion.lost_valley_house_2,),
Monster.pepper_rex: (BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_1, BoardingHouseRegion.lost_valley_house_2,),
Monster.iridium_bat: (BoardingHouseRegion.lost_valley_ruins, BoardingHouseRegion.lost_valley_house_2,),
Monster.grub: (BoardingHouseRegion.abandoned_mines_1a, BoardingHouseRegion.abandoned_mines_1b, BoardingHouseRegion.abandoned_mines_2a,
BoardingHouseRegion.abandoned_mines_2b,),
Monster.bug: (BoardingHouseRegion.abandoned_mines_1a, BoardingHouseRegion.abandoned_mines_1b,),
Monster.bat: (BoardingHouseRegion.abandoned_mines_2a, BoardingHouseRegion.abandoned_mines_2b,),
Monster.cave_fly: (BoardingHouseRegion.abandoned_mines_3, BoardingHouseRegion.abandoned_mines_4, BoardingHouseRegion.abandoned_mines_5,),
Monster.frost_bat: (BoardingHouseRegion.abandoned_mines_3, BoardingHouseRegion.abandoned_mines_4, BoardingHouseRegion.abandoned_mines_5,),
}
modded_monsters_locations: Dict[str, Dict[str, Tuple[str, ...]]] = {
ModNames.sve: sve_monsters_locations,
ModNames.deepwoods: deepwoods_monsters_locations,
ModNames.boarding_house: boardinghouse_monsters_locations
}

View File

@ -1,8 +1,10 @@
from ..strings.entrance_names import DeepWoodsEntrance, EugeneEntrance, \
JasperEntrance, AlecEntrance, YobaEntrance, JunaEntrance, MagicEntrance, AyeishaEntrance, RileyEntrance
from ..strings.region_names import Region, DeepWoodsRegion, EugeneRegion, JasperRegion, \
AlecRegion, YobaRegion, JunaRegion, MagicRegion, AyeishaRegion, RileyRegion
from ..region_classes import RegionData, ConnectionData, RandomizationFlag, ModRegionData
from typing import Dict, List
from ..strings.entrance_names import Entrance, DeepWoodsEntrance, EugeneEntrance, LaceyEntrance, BoardingHouseEntrance, \
JasperEntrance, AlecEntrance, YobaEntrance, JunaEntrance, MagicEntrance, AyeishaEntrance, RileyEntrance, SVEEntrance, AlectoEntrance
from ..strings.region_names import Region, DeepWoodsRegion, EugeneRegion, JasperRegion, BoardingHouseRegion, \
AlecRegion, YobaRegion, JunaRegion, MagicRegion, AyeishaRegion, RileyRegion, SVERegion, AlectoRegion, LaceyRegion
from ..region_classes import RegionData, ConnectionData, ModificationFlag, RandomizationFlag, ModRegionData
from .mod_data import ModNames
deep_woods_regions = [
@ -131,6 +133,232 @@ riley_entrances = [
flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA)
]
stardew_valley_expanded_regions = [
RegionData(Region.backwoods, [SVEEntrance.backwoods_to_grove]),
RegionData(SVERegion.enchanted_grove, [SVEEntrance.grove_to_outpost_warp, SVEEntrance.grove_to_wizard_warp,
SVEEntrance.grove_to_farm_warp, SVEEntrance.grove_to_guild_warp, SVEEntrance.grove_to_junimo_warp,
SVEEntrance.grove_to_spring_warp, SVEEntrance.grove_to_aurora_warp]),
RegionData(SVERegion.grove_farm_warp, [SVEEntrance.farm_warp_to_farm]),
RegionData(SVERegion.grove_aurora_warp, [SVEEntrance.aurora_warp_to_aurora]),
RegionData(SVERegion.grove_guild_warp, [SVEEntrance.guild_warp_to_guild]),
RegionData(SVERegion.grove_junimo_warp, [SVEEntrance.junimo_warp_to_junimo]),
RegionData(SVERegion.grove_spring_warp, [SVEEntrance.spring_warp_to_spring]),
RegionData(SVERegion.grove_outpost_warp, [SVEEntrance.outpost_warp_to_outpost]),
RegionData(SVERegion.grove_wizard_warp, [SVEEntrance.wizard_warp_to_wizard]),
RegionData(SVERegion.galmoran_outpost, [SVEEntrance.outpost_to_badlands_entrance, SVEEntrance.use_alesia_shop,
SVEEntrance.use_isaac_shop]),
RegionData(SVERegion.badlands_entrance, [SVEEntrance.badlands_entrance_to_badlands]),
RegionData(SVERegion.crimson_badlands, [SVEEntrance.badlands_to_cave]),
RegionData(SVERegion.badlands_cave),
RegionData(Region.bus_stop, [SVEEntrance.bus_stop_to_shed]),
RegionData(SVERegion.grandpas_shed, [SVEEntrance.grandpa_shed_to_interior, SVEEntrance.grandpa_shed_to_town]),
RegionData(SVERegion.grandpas_shed_interior, [SVEEntrance.grandpa_interior_to_upstairs]),
RegionData(SVERegion.grandpas_shed_upstairs),
RegionData(Region.forest,
[SVEEntrance.forest_to_fairhaven, SVEEntrance.forest_to_west, SVEEntrance.forest_to_lost_woods,
SVEEntrance.forest_to_bmv, SVEEntrance.forest_to_marnie_shed]),
RegionData(SVERegion.marnies_shed),
RegionData(SVERegion.fairhaven_farm),
RegionData(Region.town, [SVEEntrance.town_to_bmv, SVEEntrance.town_to_jenkins,
SVEEntrance.town_to_bridge, SVEEntrance.town_to_plot]),
RegionData(SVERegion.blue_moon_vineyard, [SVEEntrance.bmv_to_sophia, SVEEntrance.bmv_to_beach]),
RegionData(SVERegion.sophias_house),
RegionData(SVERegion.jenkins_residence, [SVEEntrance.jenkins_to_cellar]),
RegionData(SVERegion.jenkins_cellar),
RegionData(SVERegion.unclaimed_plot, [SVEEntrance.plot_to_bridge]),
RegionData(SVERegion.shearwater),
RegionData(Region.museum, [SVEEntrance.museum_to_gunther_bedroom]),
RegionData(SVERegion.gunther_bedroom),
RegionData(Region.fish_shop, [SVEEntrance.fish_shop_to_willy_bedroom]),
RegionData(SVERegion.willy_bedroom),
RegionData(Region.mountain, [SVEEntrance.mountain_to_guild_summit]),
RegionData(SVERegion.guild_summit, [SVEEntrance.guild_to_interior, SVEEntrance.guild_to_mines,
SVEEntrance.summit_to_highlands]),
RegionData(Region.railroad, [SVEEntrance.to_susan_house, SVEEntrance.enter_summit, SVEEntrance.railroad_to_grampleton_station]),
RegionData(SVERegion.grampleton_station, [SVEEntrance.grampleton_station_to_grampleton_suburbs]),
RegionData(SVERegion.grampleton_suburbs, [SVEEntrance.grampleton_suburbs_to_scarlett_house]),
RegionData(SVERegion.scarlett_house),
RegionData(Region.wizard_basement, [SVEEntrance.wizard_to_fable_reef]),
RegionData(SVERegion.fable_reef, [SVEEntrance.fable_reef_to_guild]),
RegionData(SVERegion.first_slash_guild, [SVEEntrance.first_slash_guild_to_hallway]),
RegionData(SVERegion.first_slash_hallway, [SVEEntrance.first_slash_hallway_to_room]),
RegionData(SVERegion.first_slash_spare_room),
RegionData(SVERegion.highlands_outside, [SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave]),
RegionData(SVERegion.highlands_cavern, [SVEEntrance.to_dwarf_prison]),
RegionData(SVERegion.dwarf_prison),
RegionData(SVERegion.lances_house, [SVEEntrance.lance_to_ladder]),
RegionData(SVERegion.lances_ladder, [SVEEntrance.lance_ladder_to_highlands]),
RegionData(SVERegion.forest_west, [SVEEntrance.forest_west_to_spring, SVEEntrance.west_to_aurora,
SVEEntrance.use_bear_shop]),
RegionData(SVERegion.aurora_vineyard, [SVEEntrance.to_aurora_basement]),
RegionData(SVERegion.aurora_vineyard_basement),
RegionData(Region.secret_woods, [SVEEntrance.secret_woods_to_west]),
RegionData(SVERegion.bear_shop),
RegionData(SVERegion.sprite_spring, [SVEEntrance.sprite_spring_to_cave]),
RegionData(SVERegion.sprite_spring_cave),
RegionData(SVERegion.lost_woods, [SVEEntrance.lost_woods_to_junimo_woods]),
RegionData(SVERegion.junimo_woods, [SVEEntrance.use_purple_junimo]),
RegionData(SVERegion.purple_junimo_shop),
RegionData(SVERegion.alesia_shop),
RegionData(SVERegion.isaac_shop),
RegionData(SVERegion.summit),
RegionData(SVERegion.susans_house),
RegionData(Region.mountain, [Entrance.mountain_to_adventurer_guild, Entrance.mountain_to_the_mines], ModificationFlag.MODIFIED)
]
mandatory_sve_connections = [
ConnectionData(SVEEntrance.town_to_jenkins, SVERegion.jenkins_residence, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(SVEEntrance.jenkins_to_cellar, SVERegion.jenkins_cellar, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.forest_to_bmv, SVERegion.blue_moon_vineyard),
ConnectionData(SVEEntrance.bmv_to_beach, Region.beach),
ConnectionData(SVEEntrance.town_to_plot, SVERegion.unclaimed_plot),
ConnectionData(SVEEntrance.town_to_bmv, SVERegion.blue_moon_vineyard),
ConnectionData(SVEEntrance.town_to_bridge, SVERegion.shearwater),
ConnectionData(SVEEntrance.plot_to_bridge, SVERegion.shearwater),
ConnectionData(SVEEntrance.bus_stop_to_shed, SVERegion.grandpas_shed),
ConnectionData(SVEEntrance.grandpa_shed_to_interior, SVERegion.grandpas_shed_interior, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(SVEEntrance.grandpa_interior_to_upstairs, SVERegion.grandpas_shed_upstairs, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.grandpa_shed_to_town, Region.town),
ConnectionData(SVEEntrance.bmv_to_sophia, SVERegion.sophias_house, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(SVEEntrance.summit_to_highlands, SVERegion.highlands_outside, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.guild_to_interior, Region.adventurer_guild, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.backwoods_to_grove, SVERegion.enchanted_grove, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(SVEEntrance.grove_to_outpost_warp, SVERegion.grove_outpost_warp),
ConnectionData(SVEEntrance.outpost_warp_to_outpost, SVERegion.galmoran_outpost, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.grove_to_wizard_warp, SVERegion.grove_wizard_warp),
ConnectionData(SVEEntrance.wizard_warp_to_wizard, Region.wizard_basement, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.grove_to_aurora_warp, SVERegion.grove_aurora_warp),
ConnectionData(SVEEntrance.aurora_warp_to_aurora, SVERegion.aurora_vineyard_basement, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.grove_to_farm_warp, SVERegion.grove_farm_warp),
ConnectionData(SVEEntrance.to_aurora_basement, SVERegion.aurora_vineyard_basement, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.farm_warp_to_farm, Region.farm),
ConnectionData(SVEEntrance.grove_to_guild_warp, SVERegion.grove_guild_warp),
ConnectionData(SVEEntrance.guild_warp_to_guild, Region.adventurer_guild, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.grove_to_junimo_warp, SVERegion.grove_junimo_warp),
ConnectionData(SVEEntrance.junimo_warp_to_junimo, SVERegion.junimo_woods, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.use_purple_junimo, SVERegion.purple_junimo_shop),
ConnectionData(SVEEntrance.grove_to_spring_warp, SVERegion.grove_spring_warp),
ConnectionData(SVEEntrance.spring_warp_to_spring, SVERegion.sprite_spring, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.wizard_to_fable_reef, SVERegion.fable_reef, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.fable_reef_to_guild, SVERegion.first_slash_guild, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.outpost_to_badlands_entrance, SVERegion.badlands_entrance, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.badlands_entrance_to_badlands, SVERegion.crimson_badlands, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.badlands_to_cave, SVERegion.badlands_cave, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.guild_to_mines, Region.mines, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(SVEEntrance.mountain_to_guild_summit, SVERegion.guild_summit),
ConnectionData(SVEEntrance.forest_to_west, SVERegion.forest_west),
ConnectionData(SVEEntrance.secret_woods_to_west, SVERegion.forest_west),
ConnectionData(SVEEntrance.west_to_aurora, SVERegion.aurora_vineyard, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData(SVEEntrance.forest_to_lost_woods, SVERegion.lost_woods),
ConnectionData(SVEEntrance.lost_woods_to_junimo_woods, SVERegion.junimo_woods),
ConnectionData(SVEEntrance.forest_to_marnie_shed, SVERegion.marnies_shed, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData(SVEEntrance.forest_west_to_spring, SVERegion.sprite_spring, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.to_susan_house, SVERegion.susans_house, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.enter_summit, SVERegion.summit, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.forest_to_fairhaven, SVERegion.fairhaven_farm, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData(SVEEntrance.highlands_to_lance, SVERegion.lances_house, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.lance_to_ladder, SVERegion.lances_ladder, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.lance_ladder_to_highlands, SVERegion.highlands_outside, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.highlands_to_cave, SVERegion.highlands_cavern, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.use_bear_shop, SVERegion.bear_shop),
ConnectionData(SVEEntrance.use_purple_junimo, SVERegion.purple_junimo_shop),
ConnectionData(SVEEntrance.use_alesia_shop, SVERegion.alesia_shop),
ConnectionData(SVEEntrance.use_isaac_shop, SVERegion.isaac_shop),
ConnectionData(SVEEntrance.to_dwarf_prison, SVERegion.dwarf_prison, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.railroad_to_grampleton_station, SVERegion.grampleton_station),
ConnectionData(SVEEntrance.grampleton_station_to_grampleton_suburbs, SVERegion.grampleton_suburbs),
ConnectionData(SVEEntrance.grampleton_suburbs_to_scarlett_house, SVERegion.scarlett_house, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.first_slash_guild_to_hallway, SVERegion.first_slash_hallway, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.first_slash_hallway_to_room, SVERegion.first_slash_spare_room, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(SVEEntrance.sprite_spring_to_cave, SVERegion.sprite_spring_cave, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.fish_shop_to_willy_bedroom, SVERegion.willy_bedroom, flag=RandomizationFlag.BUILDINGS),
ConnectionData(SVEEntrance.museum_to_gunther_bedroom, SVERegion.gunther_bedroom, flag=RandomizationFlag.BUILDINGS),
]
alecto_regions = [
RegionData(Region.witch_hut, [AlectoEntrance.witch_hut_to_witch_attic]),
RegionData(AlectoRegion.witch_attic)
]
alecto_entrances = [
ConnectionData(AlectoEntrance.witch_hut_to_witch_attic, AlectoRegion.witch_attic, flag=RandomizationFlag.BUILDINGS)
]
lacey_regions = [
RegionData(Region.forest, [LaceyEntrance.forest_to_hat_house]),
RegionData(LaceyRegion.hat_house)
]
lacey_entrances = [
ConnectionData(LaceyEntrance.forest_to_hat_house, LaceyRegion.hat_house, flag=RandomizationFlag.BUILDINGS)
]
boarding_house_regions = [
RegionData(Region.bus_stop, [BoardingHouseEntrance.bus_stop_to_boarding_house_plateau]),
RegionData(BoardingHouseRegion.boarding_house_plateau, [BoardingHouseEntrance.boarding_house_plateau_to_boarding_house_first,
BoardingHouseEntrance.boarding_house_plateau_to_buffalo_ranch,
BoardingHouseEntrance.boarding_house_plateau_to_abandoned_mines_entrance]),
RegionData(BoardingHouseRegion.boarding_house_first, [BoardingHouseEntrance.boarding_house_first_to_boarding_house_second]),
RegionData(BoardingHouseRegion.boarding_house_second),
RegionData(BoardingHouseRegion.buffalo_ranch),
RegionData(BoardingHouseRegion.abandoned_mines_entrance, [BoardingHouseEntrance.abandoned_mines_entrance_to_abandoned_mines_1a,
BoardingHouseEntrance.abandoned_mines_entrance_to_the_lost_valley]),
RegionData(BoardingHouseRegion.abandoned_mines_1a, [BoardingHouseEntrance.abandoned_mines_1a_to_abandoned_mines_1b]),
RegionData(BoardingHouseRegion.abandoned_mines_1b, [BoardingHouseEntrance.abandoned_mines_1b_to_abandoned_mines_2a]),
RegionData(BoardingHouseRegion.abandoned_mines_2a, [BoardingHouseEntrance.abandoned_mines_2a_to_abandoned_mines_2b]),
RegionData(BoardingHouseRegion.abandoned_mines_2b, [BoardingHouseEntrance.abandoned_mines_2b_to_abandoned_mines_3]),
RegionData(BoardingHouseRegion.abandoned_mines_3, [BoardingHouseEntrance.abandoned_mines_3_to_abandoned_mines_4]),
RegionData(BoardingHouseRegion.abandoned_mines_4, [BoardingHouseEntrance.abandoned_mines_4_to_abandoned_mines_5]),
RegionData(BoardingHouseRegion.abandoned_mines_5, [BoardingHouseEntrance.abandoned_mines_5_to_the_lost_valley]),
RegionData(BoardingHouseRegion.the_lost_valley, [BoardingHouseEntrance.the_lost_valley_to_gregory_tent,
BoardingHouseEntrance.lost_valley_to_lost_valley_minecart,
BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins]),
RegionData(BoardingHouseRegion.gregory_tent),
RegionData(BoardingHouseRegion.lost_valley_ruins, [BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_1,
BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_2]),
RegionData(BoardingHouseRegion.lost_valley_minecart),
RegionData(BoardingHouseRegion.lost_valley_house_1),
RegionData(BoardingHouseRegion.lost_valley_house_2)
]
boarding_house_entrances = [
ConnectionData(BoardingHouseEntrance.bus_stop_to_boarding_house_plateau, BoardingHouseRegion.boarding_house_plateau),
ConnectionData(BoardingHouseEntrance.boarding_house_plateau_to_boarding_house_first, BoardingHouseRegion.boarding_house_first,
flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(BoardingHouseEntrance.boarding_house_first_to_boarding_house_second, BoardingHouseRegion.boarding_house_second,
flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.boarding_house_plateau_to_buffalo_ranch, BoardingHouseRegion.buffalo_ranch,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(BoardingHouseEntrance.boarding_house_plateau_to_abandoned_mines_entrance, BoardingHouseRegion.abandoned_mines_entrance,
flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(BoardingHouseEntrance.abandoned_mines_entrance_to_the_lost_valley, BoardingHouseRegion.lost_valley_minecart, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_entrance_to_abandoned_mines_1a, BoardingHouseRegion.abandoned_mines_1a,
flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_1a_to_abandoned_mines_1b, BoardingHouseRegion.abandoned_mines_1b, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_1b_to_abandoned_mines_2a, BoardingHouseRegion.abandoned_mines_2a, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_2a_to_abandoned_mines_2b, BoardingHouseRegion.abandoned_mines_2b, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_2b_to_abandoned_mines_3, BoardingHouseRegion.abandoned_mines_3, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_3_to_abandoned_mines_4, BoardingHouseRegion.abandoned_mines_4, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_4_to_abandoned_mines_5, BoardingHouseRegion.abandoned_mines_5, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.abandoned_mines_5_to_the_lost_valley, BoardingHouseRegion.the_lost_valley, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.the_lost_valley_to_gregory_tent, BoardingHouseRegion.gregory_tent, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.lost_valley_to_lost_valley_minecart, BoardingHouseRegion.lost_valley_minecart),
ConnectionData(BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins, BoardingHouseRegion.lost_valley_ruins, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_1, BoardingHouseRegion.lost_valley_house_1, flag=RandomizationFlag.BUILDINGS),
ConnectionData(BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_2, BoardingHouseRegion.lost_valley_house_2, flag=RandomizationFlag.BUILDINGS)
]
vanilla_connections_to_remove_by_mod: Dict[str, List[ConnectionData]] = {
ModNames.sve: [ConnectionData(Entrance.mountain_to_the_mines, Region.mines,
flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.mountain_to_adventurer_guild, Region.adventurer_guild,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA),
]
}
ModDataList = {
ModNames.deepwoods: ModRegionData(ModNames.deepwoods, deep_woods_regions, deep_woods_entrances),
ModNames.eugene: ModRegionData(ModNames.eugene, eugene_regions, eugene_entrances),
@ -141,4 +369,8 @@ ModDataList = {
ModNames.magic: ModRegionData(ModNames.magic, magic_regions, magic_entrances),
ModNames.ayeisha: ModRegionData(ModNames.ayeisha, ayeisha_regions, ayeisha_entrances),
ModNames.riley: ModRegionData(ModNames.riley, riley_regions, riley_entrances),
ModNames.sve: ModRegionData(ModNames.sve, stardew_valley_expanded_regions, mandatory_sve_connections),
ModNames.alecto: ModRegionData(ModNames.alecto, alecto_regions, alecto_entrances),
ModNames.lacey: ModRegionData(ModNames.lacey, lacey_regions, lacey_entrances),
ModNames.boarding_house: ModRegionData(ModNames.boarding_house, boarding_house_regions, boarding_house_entrances),
}

View File

@ -1,21 +1,32 @@
from dataclasses import dataclass
from typing import Dict
from typing import Protocol, ClassVar
from Options import Range, NamedRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink, Option
from Options import Range, NamedRange, Toggle, Choice, OptionSet, PerGameCommonOptions, DeathLink
from .mods.mod_data import ModNames
class StardewValleyOption(Protocol):
internal_name: ClassVar[str]
class Goal(Choice):
"""What's your goal with this play-through?
Community Center: Complete the Community Center.
Grandpa's Evaluation: Succeed grandpa's evaluation with 4 lit candles.
Bottom of the Mines: Reach level 120 in the mineshaft.
Cryptic Note: Complete the quest "Cryptic Note" where Mr Qi asks you to reach floor 100 in the Skull Cavern.
Master Angler: Catch every fish in the game. Pairs well with Fishsanity.
Complete Collection: Complete the museum by donating every possible item. Pairs well with Museumsanity.
Full House: Get married and have two children. Pairs well with Friendsanity.
Community Center: Complete the Community Center
Grandpa's Evaluation: Succeed Grandpa's evaluation with 4 lit candles
Bottom of the Mines: Reach level 120 in the mineshaft
Cryptic Note: Complete the quest "Cryptic Note" where Mr Qi asks you to reach floor 100 in the Skull Cavern
Master Angler: Catch every fish. Adapts to chosen Fishsanity option
Complete Collection: Complete the museum by donating every possible item. Pairs well with Museumsanity
Full House: Get married and have two children. Pairs well with Friendsanity
Greatest Walnut Hunter: Find all 130 Golden Walnuts
Perfection: Attain Perfection, based on the vanilla definition.
Protector of the Valley: Complete all the monster slayer goals. Adapts to Monstersanity
Full Shipment: Ship every item in the collection tab. Adapts to Shipsanity
Gourmet Chef: Cook every recipe. Adapts to Cooksanity
Craft Master: Craft every item.
Legend: Earn 10 000 000g
Mystery of the Stardrops: Find every stardrop
Allsanity: Complete every check in your slot
Perfection: Attain Perfection, based on the vanilla definition
"""
internal_name = "goal"
display_name = "Goal"
@ -28,16 +39,18 @@ class Goal(Choice):
option_complete_collection = 5
option_full_house = 6
option_greatest_walnut_hunter = 7
option_protector_of_the_valley = 8
option_full_shipment = 9
option_gourmet_chef = 10
option_craft_master = 11
option_legend = 12
option_mystery_of_the_stardrops = 13
# option_junimo_kart =
# option_prairie_king =
# option_fector_challenge =
# option_craft_master =
# option_mystery_of_the_stardrops =
# option_protector_of_the_valley =
# option_full_shipment =
# option_legend =
# option_beloved_farmer =
# option_master_of_the_five_ways =
option_allsanity = 24
option_perfection = 25
@classmethod
@ -48,6 +61,20 @@ class Goal(Choice):
return super().get_option_name(value)
class FarmType(Choice):
"""What farm to play on?"""
internal_name = "farm_type"
display_name = "Farm Type"
default = "random"
option_standard = 0
option_riverland = 1
option_forest = 2
option_hill_top = 3
option_wilderness = 4
option_four_corners = 5
option_beach = 6
class StartingMoney(NamedRange):
"""Amount of gold when arriving at the farm.
Set to -1 or unlimited for infinite money"""
@ -90,28 +117,36 @@ class BundleRandomization(Choice):
"""What items are needed for the community center bundles?
Vanilla: Standard bundles from the vanilla game
Thematic: Every bundle will require random items compatible with their original theme
Remixed: Picks bundles at random from thematic, vanilla remixed and new custom ones
Shuffled: Every bundle will require random items and follow no particular structure"""
internal_name = "bundle_randomization"
display_name = "Bundle Randomization"
default = 1
default = 2
option_vanilla = 0
option_thematic = 1
option_shuffled = 2
option_remixed = 2
option_shuffled = 3
class BundlePrice(Choice):
"""How many items are needed for the community center bundles?
Minimum: Every bundle will require only one item
Very Cheap: Every bundle will require 2 items fewer than usual
Cheap: Every bundle will require 1 item fewer than usual
Normal: Every bundle will require the vanilla number of items
Expensive: Every bundle will require 1 extra item when applicable"""
Expensive: Every bundle will require 1 extra item
Very Expensive: Every bundle will require 2 extra items
Maximum: Every bundle will require many extra items"""
internal_name = "bundle_price"
display_name = "Bundle Price"
default = 2
option_very_cheap = 0
option_cheap = 1
option_normal = 2
option_expensive = 3
default = 0
option_minimum = -8
option_very_cheap = -2
option_cheap = -1
option_normal = 0
option_expensive = 1
option_very_expensive = 2
option_maximum = 8
class EntranceRandomization(Choice):
@ -189,12 +224,18 @@ class BackpackProgression(Choice):
class ToolProgression(Choice):
"""Shuffle the tool upgrades?
Vanilla: Clint will upgrade your tools with metal bars.
Progressive: You will randomly find Progressive Tool upgrades."""
Progressive: You will randomly find Progressive Tool upgrades.
Cheap: Tool Upgrades will cost 2/5th as much
Very Cheap: Tool Upgrades will cost 1/5th as much"""
internal_name = "tool_progression"
display_name = "Tool Progression"
default = 1
option_vanilla = 0
option_progressive = 1
option_vanilla = 0b000 # 0
option_progressive = 0b001 # 1
option_vanilla_cheap = 0b010 # 2
option_vanilla_very_cheap = 0b100 # 4
option_progressive_cheap = 0b011 # 3
option_progressive_very_cheap = 0b101 # 5
class ElevatorProgression(Choice):
@ -228,13 +269,18 @@ class BuildingProgression(Choice):
Progressive: You will receive the buildings and will be able to build the first one of each type for free,
once it is received. If you want more of the same building, it will cost the vanilla price.
Progressive early shipping bin: Same as Progressive, but the shipping bin will be placed early in the multiworld.
Cheap: Buildings will cost half as much
Very Cheap: Buildings will cost 1/5th as much
"""
internal_name = "building_progression"
display_name = "Building Progression"
default = 2
option_vanilla = 0
option_progressive = 1
option_progressive_early_shipping_bin = 2
default = 3
option_vanilla = 0b000 # 0
option_vanilla_cheap = 0b010 # 2
option_vanilla_very_cheap = 0b100 # 4
option_progressive = 0b001 # 1
option_progressive_cheap = 0b011 # 3
option_progressive_very_cheap = 0b101 # 5
class FestivalLocations(Choice):
@ -283,19 +329,23 @@ class SpecialOrderLocations(Choice):
option_board_qi = 2
class HelpWantedLocations(NamedRange):
"""Include location checks for Help Wanted quests
Out of every 7 quests, 4 will be item deliveries, and then 1 of each for: Fishing, Gathering and Slaying Monsters.
Choosing a multiple of 7 is recommended."""
internal_name = "help_wanted_locations"
class QuestLocations(NamedRange):
"""Include location checks for quests
None: No quests are checks
Story: Only story quests are checks
Number: Story quests and help wanted quests are checks up to the specified amount. Multiple of 7 recommended
Out of every 7 help wanted quests, 4 will be item deliveries, and then 1 of each for: Fishing, Gathering and Slaying Monsters.
Extra Help wanted quests might be added if current settings don't have enough locations"""
internal_name = "quest_locations"
default = 7
range_start = 0
range_end = 56
# step = 7
display_name = "Number of Help Wanted locations"
display_name = "Quest Locations"
special_range_names = {
"none": 0,
"none": -1,
"story": 0,
"minimum": 7,
"normal": 14,
"lots": 28,
@ -344,6 +394,115 @@ class Museumsanity(Choice):
option_all = 3
class Monstersanity(Choice):
"""Locations for slaying monsters?
None: There are no checks for slaying monsters
One per category: Every category visible at the adventure guild gives one check
One per Monster: Every unique monster gives one check
Monster Eradication Goals: The Monster Eradication Goals each contain one check
Short Monster Eradication Goals: The Monster Eradication Goals each contain one check, but are reduced by 60%
Very Short Monster Eradication Goals: The Monster Eradication Goals each contain one check, but are reduced by 90%
Progressive Eradication Goals: The Monster Eradication Goals each contain 5 checks, each 20% of the way
Split Eradication Goals: The Monster Eradication Goals are split by monsters, each monster has one check
"""
internal_name = "monstersanity"
display_name = "Monstersanity"
default = 1
option_none = 0
option_one_per_category = 1
option_one_per_monster = 2
option_goals = 3
option_short_goals = 4
option_very_short_goals = 5
option_progressive_goals = 6
option_split_goals = 7
class Shipsanity(Choice):
"""Locations for shipping items?
None: There are no checks for shipping items
Crops: Every crop and forageable being shipped is a check
Fish: Every fish being shipped is a check except legendaries
Full Shipment: Every item in the Collections page is a check
Full Shipment With Fish: Every item in the Collections page and every fish is a check
Everything: Every item in the game that can be shipped is a check
"""
internal_name = "shipsanity"
display_name = "Shipsanity"
default = 0
option_none = 0
option_crops = 1
# option_quality_crops = 2
option_fish = 3
# option_quality_fish = 4
option_full_shipment = 5
# option_quality_full_shipment = 6
option_full_shipment_with_fish = 7
# option_quality_full_shipment_with_fish = 8
option_everything = 9
# option_quality_everything = 10
class Cooksanity(Choice):
"""Locations for cooking food?
None: There are no checks for cooking
Queen of Sauce: Every Queen of Sauce Recipe can be cooked for a check
All: Every cooking recipe can be cooked for a check
"""
internal_name = "cooksanity"
display_name = "Cooksanity"
default = 0
option_none = 0
option_queen_of_sauce = 1
option_all = 2
class Chefsanity(NamedRange):
"""Locations for leaning cooking recipes?
Vanilla: All cooking recipes are learned normally
Queen of Sauce: Every Queen of sauce episode is a check, all queen of sauce recipes are items
Purchases: Every purchasable recipe is a check
Friendship: Recipes obtained from friendship are checks
Skills: Recipes obtained from skills are checks
All: Learning every cooking recipe is a check
"""
internal_name = "chefsanity"
display_name = "Chefsanity"
default = 0
range_start = 0
range_end = 15
option_none = 0b0000 # 0
option_queen_of_sauce = 0b0001 # 1
option_purchases = 0b0010 # 2
option_qos_and_purchases = 0b0011 # 3
option_skills = 0b0100 # 4
option_friendship = 0b1000 # 8
option_all = 0b1111 # 15
special_range_names = {
"none": 0b0000, # 0
"queen_of_sauce": 0b0001, # 1
"purchases": 0b0010, # 2
"qos_and_purchases": 0b0011, # 3
"skills": 0b0100, # 4
"friendship": 0b1000, # 8
"all": 0b1111, # 15
}
class Craftsanity(Choice):
"""Checks for crafting items?
If enabled, all recipes purchased in shops will be checks as well.
Recipes obtained from other sources will depend on related archipelago settings
"""
internal_name = "craftsanity"
display_name = "Craftsanity"
default = 0
option_none = 0
option_all = 1
class Friendsanity(Choice):
"""Shuffle Friendships?
None: Friendship hearts are earned normally
@ -530,13 +689,15 @@ class Mods(OptionSet):
ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna,
ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene,
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator, ModNames.sve, ModNames.distant_lands,
ModNames.alecto, ModNames.lacey, ModNames.boarding_house
}
@dataclass
class StardewValleyOptions(PerGameCommonOptions):
goal: Goal
farm_type: FarmType
starting_money: StartingMoney
profit_margin: ProfitMargin
bundle_randomization: BundleRandomization
@ -552,9 +713,14 @@ class StardewValleyOptions(PerGameCommonOptions):
elevator_progression: ElevatorProgression
arcade_machine_locations: ArcadeMachineLocations
special_order_locations: SpecialOrderLocations
help_wanted_locations: HelpWantedLocations
quest_locations: QuestLocations
fishsanity: Fishsanity
museumsanity: Museumsanity
monstersanity: Monstersanity
shipsanity: Shipsanity
cooksanity: Cooksanity
chefsanity: Chefsanity
craftsanity: Craftsanity
friendsanity: Friendsanity
friendsanity_heart_size: FriendsanityHeartSize
movement_buff_number: NumberOfMovementBuffs

View File

@ -3,14 +3,15 @@ from typing import Any, Dict
from Options import Accessibility, ProgressionBalancing, DeathLink
from .options import Goal, StartingMoney, ProfitMargin, BundleRandomization, BundlePrice, EntranceRandomization, SeasonRandomization, Cropsanity, \
BackpackProgression, ToolProgression, ElevatorProgression, SkillProgression, BuildingProgression, FestivalLocations, ArcadeMachineLocations, \
SpecialOrderLocations, HelpWantedLocations, Fishsanity, Museumsanity, Friendsanity, FriendsanityHeartSize, NumberOfMovementBuffs, NumberOfLuckBuffs, \
SpecialOrderLocations, QuestLocations, Fishsanity, Museumsanity, Friendsanity, FriendsanityHeartSize, NumberOfMovementBuffs, NumberOfLuckBuffs, \
ExcludeGingerIsland, TrapItems, MultipleDaySleepEnabled, MultipleDaySleepCost, ExperienceMultiplier, FriendshipMultiplier, DebrisMultiplier, QuickStart, \
Gifting
Gifting, FarmType, Monstersanity, Shipsanity, Cooksanity, Chefsanity, Craftsanity
all_random_settings = {
"progression_balancing": "random",
"accessibility": "random",
Goal.internal_name: "random",
FarmType.internal_name: "random",
StartingMoney.internal_name: "random",
ProfitMargin.internal_name: "random",
BundleRandomization.internal_name: "random",
@ -26,9 +27,14 @@ all_random_settings = {
FestivalLocations.internal_name: "random",
ArcadeMachineLocations.internal_name: "random",
SpecialOrderLocations.internal_name: "random",
HelpWantedLocations.internal_name: "random",
QuestLocations.internal_name: "random",
Fishsanity.internal_name: "random",
Museumsanity.internal_name: "random",
Monstersanity.internal_name: "random",
Shipsanity.internal_name: "random",
Cooksanity.internal_name: "random",
Chefsanity.internal_name: "random",
Craftsanity.internal_name: "random",
Friendsanity.internal_name: "random",
FriendsanityHeartSize.internal_name: "random",
NumberOfMovementBuffs.internal_name: "random",
@ -49,6 +55,7 @@ easy_settings = {
"progression_balancing": ProgressionBalancing.default,
"accessibility": Accessibility.option_items,
Goal.internal_name: Goal.option_community_center,
FarmType.internal_name: "random",
StartingMoney.internal_name: "very rich",
ProfitMargin.internal_name: "double",
BundleRandomization.internal_name: BundleRandomization.option_thematic,
@ -60,13 +67,18 @@ easy_settings = {
ToolProgression.internal_name: ToolProgression.option_progressive,
ElevatorProgression.internal_name: ElevatorProgression.option_progressive,
SkillProgression.internal_name: SkillProgression.option_progressive,
BuildingProgression.internal_name: BuildingProgression.option_progressive_early_shipping_bin,
BuildingProgression.internal_name: BuildingProgression.option_progressive_very_cheap,
FestivalLocations.internal_name: FestivalLocations.option_easy,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_disabled,
HelpWantedLocations.internal_name: "minimum",
QuestLocations.internal_name: "minimum",
Fishsanity.internal_name: Fishsanity.option_only_easy_fish,
Museumsanity.internal_name: Museumsanity.option_milestones,
Monstersanity.internal_name: Monstersanity.option_one_per_category,
Shipsanity.internal_name: Shipsanity.option_none,
Cooksanity.internal_name: Cooksanity.option_none,
Chefsanity.internal_name: Chefsanity.option_none,
Craftsanity.internal_name: Craftsanity.option_none,
Friendsanity.internal_name: Friendsanity.option_none,
FriendsanityHeartSize.internal_name: 4,
NumberOfMovementBuffs.internal_name: 8,
@ -87,6 +99,7 @@ medium_settings = {
"progression_balancing": 25,
"accessibility": Accessibility.option_locations,
Goal.internal_name: Goal.option_community_center,
FarmType.internal_name: "random",
StartingMoney.internal_name: "rich",
ProfitMargin.internal_name: 150,
BundleRandomization.internal_name: BundleRandomization.option_thematic,
@ -98,13 +111,18 @@ medium_settings = {
ToolProgression.internal_name: ToolProgression.option_progressive,
ElevatorProgression.internal_name: ElevatorProgression.option_progressive_from_previous_floor,
SkillProgression.internal_name: SkillProgression.option_progressive,
BuildingProgression.internal_name: BuildingProgression.option_progressive_early_shipping_bin,
BuildingProgression.internal_name: BuildingProgression.option_progressive_cheap,
FestivalLocations.internal_name: FestivalLocations.option_hard,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_victories_easy,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_only,
HelpWantedLocations.internal_name: "normal",
QuestLocations.internal_name: "normal",
Fishsanity.internal_name: Fishsanity.option_exclude_legendaries,
Museumsanity.internal_name: Museumsanity.option_milestones,
Monstersanity.internal_name: Monstersanity.option_one_per_monster,
Shipsanity.internal_name: Shipsanity.option_none,
Cooksanity.internal_name: Cooksanity.option_none,
Chefsanity.internal_name: Chefsanity.option_queen_of_sauce,
Craftsanity.internal_name: Craftsanity.option_none,
Friendsanity.internal_name: Friendsanity.option_starting_npcs,
FriendsanityHeartSize.internal_name: 4,
NumberOfMovementBuffs.internal_name: 6,
@ -125,6 +143,7 @@ hard_settings = {
"progression_balancing": 0,
"accessibility": Accessibility.option_locations,
Goal.internal_name: Goal.option_grandpa_evaluation,
FarmType.internal_name: "random",
StartingMoney.internal_name: "extra",
ProfitMargin.internal_name: "normal",
BundleRandomization.internal_name: BundleRandomization.option_thematic,
@ -140,9 +159,14 @@ hard_settings = {
FestivalLocations.internal_name: FestivalLocations.option_hard,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_full_shuffling,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
HelpWantedLocations.internal_name: "lots",
QuestLocations.internal_name: "lots",
Fishsanity.internal_name: Fishsanity.option_all,
Museumsanity.internal_name: Museumsanity.option_all,
Monstersanity.internal_name: Monstersanity.option_progressive_goals,
Shipsanity.internal_name: Shipsanity.option_crops,
Cooksanity.internal_name: Cooksanity.option_queen_of_sauce,
Chefsanity.internal_name: Chefsanity.option_qos_and_purchases,
Craftsanity.internal_name: Craftsanity.option_none,
Friendsanity.internal_name: Friendsanity.option_all,
FriendsanityHeartSize.internal_name: 4,
NumberOfMovementBuffs.internal_name: 4,
@ -163,6 +187,7 @@ nightmare_settings = {
"progression_balancing": 0,
"accessibility": Accessibility.option_locations,
Goal.internal_name: Goal.option_community_center,
FarmType.internal_name: "random",
StartingMoney.internal_name: "vanilla",
ProfitMargin.internal_name: "half",
BundleRandomization.internal_name: BundleRandomization.option_shuffled,
@ -178,9 +203,14 @@ nightmare_settings = {
FestivalLocations.internal_name: FestivalLocations.option_hard,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_full_shuffling,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
HelpWantedLocations.internal_name: "maximum",
QuestLocations.internal_name: "maximum",
Fishsanity.internal_name: Fishsanity.option_special,
Museumsanity.internal_name: Museumsanity.option_all,
Monstersanity.internal_name: Monstersanity.option_split_goals,
Shipsanity.internal_name: Shipsanity.option_full_shipment_with_fish,
Cooksanity.internal_name: Cooksanity.option_queen_of_sauce,
Chefsanity.internal_name: Chefsanity.option_qos_and_purchases,
Craftsanity.internal_name: Craftsanity.option_none,
Friendsanity.internal_name: Friendsanity.option_all_with_marriage,
FriendsanityHeartSize.internal_name: 4,
NumberOfMovementBuffs.internal_name: 2,
@ -201,6 +231,7 @@ short_settings = {
"progression_balancing": ProgressionBalancing.default,
"accessibility": Accessibility.option_items,
Goal.internal_name: Goal.option_bottom_of_the_mines,
FarmType.internal_name: "random",
StartingMoney.internal_name: "filthy rich",
ProfitMargin.internal_name: "quadruple",
BundleRandomization.internal_name: BundleRandomization.option_thematic,
@ -212,13 +243,18 @@ short_settings = {
ToolProgression.internal_name: ToolProgression.option_progressive,
ElevatorProgression.internal_name: ElevatorProgression.option_progressive_from_previous_floor,
SkillProgression.internal_name: SkillProgression.option_progressive,
BuildingProgression.internal_name: BuildingProgression.option_progressive_early_shipping_bin,
BuildingProgression.internal_name: BuildingProgression.option_progressive_very_cheap,
FestivalLocations.internal_name: FestivalLocations.option_disabled,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_disabled,
HelpWantedLocations.internal_name: "none",
QuestLocations.internal_name: "none",
Fishsanity.internal_name: Fishsanity.option_none,
Museumsanity.internal_name: Museumsanity.option_none,
Monstersanity.internal_name: Monstersanity.option_none,
Shipsanity.internal_name: Shipsanity.option_none,
Cooksanity.internal_name: Cooksanity.option_none,
Chefsanity.internal_name: Chefsanity.option_none,
Craftsanity.internal_name: Craftsanity.option_none,
Friendsanity.internal_name: Friendsanity.option_none,
FriendsanityHeartSize.internal_name: 4,
NumberOfMovementBuffs.internal_name: 10,
@ -235,10 +271,11 @@ short_settings = {
"death_link": "false",
}
lowsanity_settings = {
minsanity_settings = {
"progression_balancing": ProgressionBalancing.default,
"accessibility": Accessibility.option_minimal,
Goal.internal_name: Goal.default,
FarmType.internal_name: "random",
StartingMoney.internal_name: StartingMoney.default,
ProfitMargin.internal_name: ProfitMargin.default,
BundleRandomization.internal_name: BundleRandomization.default,
@ -254,9 +291,14 @@ lowsanity_settings = {
FestivalLocations.internal_name: FestivalLocations.option_disabled,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_disabled,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_disabled,
HelpWantedLocations.internal_name: "none",
QuestLocations.internal_name: "none",
Fishsanity.internal_name: Fishsanity.option_none,
Museumsanity.internal_name: Museumsanity.option_none,
Monstersanity.internal_name: Monstersanity.option_none,
Shipsanity.internal_name: Shipsanity.option_none,
Cooksanity.internal_name: Cooksanity.option_none,
Chefsanity.internal_name: Chefsanity.option_none,
Craftsanity.internal_name: Craftsanity.option_none,
Friendsanity.internal_name: Friendsanity.option_none,
FriendsanityHeartSize.internal_name: FriendsanityHeartSize.default,
NumberOfMovementBuffs.internal_name: NumberOfMovementBuffs.default,
@ -277,6 +319,7 @@ allsanity_settings = {
"progression_balancing": ProgressionBalancing.default,
"accessibility": Accessibility.option_locations,
Goal.internal_name: Goal.default,
FarmType.internal_name: "random",
StartingMoney.internal_name: StartingMoney.default,
ProfitMargin.internal_name: ProfitMargin.default,
BundleRandomization.internal_name: BundleRandomization.default,
@ -288,13 +331,18 @@ allsanity_settings = {
ToolProgression.internal_name: ToolProgression.option_progressive,
ElevatorProgression.internal_name: ElevatorProgression.option_progressive,
SkillProgression.internal_name: SkillProgression.option_progressive,
BuildingProgression.internal_name: BuildingProgression.option_progressive_early_shipping_bin,
BuildingProgression.internal_name: BuildingProgression.option_progressive,
FestivalLocations.internal_name: FestivalLocations.option_hard,
ArcadeMachineLocations.internal_name: ArcadeMachineLocations.option_full_shuffling,
SpecialOrderLocations.internal_name: SpecialOrderLocations.option_board_qi,
HelpWantedLocations.internal_name: "maximum",
QuestLocations.internal_name: "maximum",
Fishsanity.internal_name: Fishsanity.option_all,
Museumsanity.internal_name: Museumsanity.option_all,
Monstersanity.internal_name: Monstersanity.option_progressive_goals,
Shipsanity.internal_name: Shipsanity.option_everything,
Cooksanity.internal_name: Cooksanity.option_all,
Chefsanity.internal_name: Chefsanity.option_all,
Craftsanity.internal_name: Craftsanity.option_all,
Friendsanity.internal_name: Friendsanity.option_all,
FriendsanityHeartSize.internal_name: 1,
NumberOfMovementBuffs.internal_name: 12,
@ -318,6 +366,6 @@ sv_options_presets: Dict[str, Dict[str, Any]] = {
"Hard": hard_settings,
"Nightmare": nightmare_settings,
"Short": short_settings,
"Lowsanity": lowsanity_settings,
"Minsanity": minsanity_settings,
"Allsanity": allsanity_settings,
}

View File

@ -5,6 +5,10 @@ from dataclasses import dataclass, field
connector_keyword = " to "
class ModificationFlag(IntFlag):
NOT_MODIFIED = 0
MODIFIED = 1
class RandomizationFlag(IntFlag):
NOT_RANDOMIZED = 0b0
PELICAN_TOWN = 0b11111
@ -20,6 +24,7 @@ class RandomizationFlag(IntFlag):
class RegionData:
name: str
exits: List[str] = field(default_factory=list)
flag: ModificationFlag = ModificationFlag.NOT_MODIFIED
def get_merged_with(self, exits: List[str]):
merged_exits = []
@ -29,6 +34,10 @@ class RegionData:
merged_exits = list(set(merged_exits))
return RegionData(self.name, merged_exits)
def get_without_exit(self, exit_to_remove: str):
exits = [exit for exit in self.exits if exit != exit_to_remove]
return RegionData(self.name, exits)
def get_clone(self):
return self.get_merged_with(None)

View File

@ -2,11 +2,11 @@ from random import Random
from typing import Iterable, Dict, Protocol, List, Tuple, Set
from BaseClasses import Region, Entrance
from .options import EntranceRandomization, ExcludeGingerIsland, Museumsanity
from .options import EntranceRandomization, ExcludeGingerIsland, Museumsanity, StardewValleyOptions
from .strings.entrance_names import Entrance
from .strings.region_names import Region
from .region_classes import RegionData, ConnectionData, RandomizationFlag
from .mods.mod_regions import ModDataList
from .region_classes import RegionData, ConnectionData, RandomizationFlag, ModificationFlag
from .mods.mod_regions import ModDataList, vanilla_connections_to_remove_by_mod
class RegionFactory(Protocol):
@ -17,12 +17,18 @@ class RegionFactory(Protocol):
vanilla_regions = [
RegionData(Region.menu, [Entrance.to_stardew_valley]),
RegionData(Region.stardew_valley, [Entrance.to_farmhouse]),
RegionData(Region.farm_house, [Entrance.farmhouse_to_farm, Entrance.downstairs_to_cellar]),
RegionData(Region.farm_house, [Entrance.farmhouse_to_farm, Entrance.downstairs_to_cellar, Entrance.farmhouse_cooking, Entrance.watch_queen_of_sauce]),
RegionData(Region.cellar),
RegionData(Region.kitchen),
RegionData(Region.queen_of_sauce),
RegionData(Region.farm,
[Entrance.farm_to_backwoods, Entrance.farm_to_bus_stop, Entrance.farm_to_forest,
Entrance.farm_to_farmcave, Entrance.enter_greenhouse,
Entrance.use_desert_obelisk, Entrance.use_island_obelisk]),
Entrance.enter_coop, Entrance.enter_barn,
Entrance.enter_shed, Entrance.enter_slime_hutch,
Entrance.farming, Entrance.shipping]),
RegionData(Region.farming),
RegionData(Region.shipping),
RegionData(Region.backwoods, [Entrance.backwoods_to_mountain]),
RegionData(Region.bus_stop,
[Entrance.bus_stop_to_town, Entrance.take_bus_to_desert, Entrance.bus_stop_to_tunnel_entrance]),
@ -30,8 +36,22 @@ vanilla_regions = [
[Entrance.forest_to_town, Entrance.enter_secret_woods, Entrance.forest_to_wizard_tower,
Entrance.forest_to_marnie_ranch,
Entrance.forest_to_leah_cottage, Entrance.forest_to_sewer,
Entrance.buy_from_traveling_merchant]),
RegionData(Region.traveling_cart),
Entrance.buy_from_traveling_merchant,
Entrance.attend_flower_dance, Entrance.attend_festival_of_ice]),
RegionData(Region.traveling_cart, [Entrance.buy_from_traveling_merchant_sunday,
Entrance.buy_from_traveling_merchant_monday,
Entrance.buy_from_traveling_merchant_tuesday,
Entrance.buy_from_traveling_merchant_wednesday,
Entrance.buy_from_traveling_merchant_thursday,
Entrance.buy_from_traveling_merchant_friday,
Entrance.buy_from_traveling_merchant_saturday]),
RegionData(Region.traveling_cart_sunday),
RegionData(Region.traveling_cart_monday),
RegionData(Region.traveling_cart_tuesday),
RegionData(Region.traveling_cart_wednesday),
RegionData(Region.traveling_cart_thursday),
RegionData(Region.traveling_cart_friday),
RegionData(Region.traveling_cart_saturday),
RegionData(Region.farm_cave),
RegionData(Region.greenhouse),
RegionData(Region.mountain,
@ -51,15 +71,19 @@ vanilla_regions = [
Entrance.town_to_sam_house, Entrance.town_to_haley_house, Entrance.town_to_sewer,
Entrance.town_to_clint_blacksmith,
Entrance.town_to_museum,
Entrance.town_to_jojamart]),
RegionData(Region.beach,
[Entrance.beach_to_willy_fish_shop, Entrance.enter_elliott_house, Entrance.enter_tide_pools]),
Entrance.town_to_jojamart, Entrance.purchase_movie_ticket,
Entrance.attend_egg_festival, Entrance.attend_fair, Entrance.attend_spirit_eve, Entrance.attend_winter_star]),
RegionData(Region.beach, [Entrance.beach_to_willy_fish_shop, Entrance.enter_elliott_house, Entrance.enter_tide_pools,
Entrance.fishing,
Entrance.attend_luau, Entrance.attend_moonlight_jellies, Entrance.attend_night_market]),
RegionData(Region.fishing),
RegionData(Region.railroad, [Entrance.enter_bathhouse_entrance, Entrance.enter_witch_warp_cave]),
RegionData(Region.ranch),
RegionData(Region.leah_house),
RegionData(Region.sewer, [Entrance.enter_mutant_bug_lair]),
RegionData(Region.mutant_bug_lair),
RegionData(Region.wizard_tower, [Entrance.enter_wizard_basement]),
RegionData(Region.wizard_tower, [Entrance.enter_wizard_basement,
Entrance.use_desert_obelisk, Entrance.use_island_obelisk]),
RegionData(Region.wizard_basement),
RegionData(Region.tent),
RegionData(Region.carpenter, [Entrance.enter_sebastian_room]),
@ -79,14 +103,27 @@ vanilla_regions = [
RegionData(Region.pierre_store, [Entrance.enter_sunroom]),
RegionData(Region.sunroom),
RegionData(Region.saloon, [Entrance.play_journey_of_the_prairie_king, Entrance.play_junimo_kart]),
RegionData(Region.jotpk_world_1, [Entrance.reach_jotpk_world_2]),
RegionData(Region.jotpk_world_2, [Entrance.reach_jotpk_world_3]),
RegionData(Region.jotpk_world_3),
RegionData(Region.junimo_kart_1, [Entrance.reach_junimo_kart_2]),
RegionData(Region.junimo_kart_2, [Entrance.reach_junimo_kart_3]),
RegionData(Region.junimo_kart_3),
RegionData(Region.alex_house),
RegionData(Region.trailer),
RegionData(Region.mayor_house),
RegionData(Region.sam_house),
RegionData(Region.haley_house),
RegionData(Region.blacksmith),
RegionData(Region.blacksmith, [Entrance.blacksmith_copper]),
RegionData(Region.blacksmith_copper, [Entrance.blacksmith_iron]),
RegionData(Region.blacksmith_iron, [Entrance.blacksmith_gold]),
RegionData(Region.blacksmith_gold, [Entrance.blacksmith_iridium]),
RegionData(Region.blacksmith_iridium),
RegionData(Region.museum),
RegionData(Region.jojamart),
RegionData(Region.jojamart, [Entrance.enter_abandoned_jojamart]),
RegionData(Region.abandoned_jojamart, [Entrance.enter_movie_theater]),
RegionData(Region.movie_ticket_stand),
RegionData(Region.movie_theater),
RegionData(Region.fish_shop, [Entrance.fish_shop_to_boat_tunnel]),
RegionData(Region.boat_tunnel, [Entrance.boat_to_ginger_island]),
RegionData(Region.elliott_house),
@ -105,18 +142,16 @@ vanilla_regions = [
RegionData(Region.oasis, [Entrance.enter_casino]),
RegionData(Region.casino),
RegionData(Region.skull_cavern_entrance, [Entrance.enter_skull_cavern]),
RegionData(Region.skull_cavern, [Entrance.mine_to_skull_cavern_floor_25, Entrance.mine_to_skull_cavern_floor_50,
Entrance.mine_to_skull_cavern_floor_75, Entrance.mine_to_skull_cavern_floor_100,
Entrance.mine_to_skull_cavern_floor_125, Entrance.mine_to_skull_cavern_floor_150,
Entrance.mine_to_skull_cavern_floor_175, Entrance.mine_to_skull_cavern_floor_200]),
RegionData(Region.skull_cavern_25),
RegionData(Region.skull_cavern_50),
RegionData(Region.skull_cavern_75),
RegionData(Region.skull_cavern_100),
RegionData(Region.skull_cavern_125),
RegionData(Region.skull_cavern_150),
RegionData(Region.skull_cavern_175),
RegionData(Region.skull_cavern_200),
RegionData(Region.skull_cavern, [Entrance.mine_to_skull_cavern_floor_25]),
RegionData(Region.skull_cavern_25, [Entrance.mine_to_skull_cavern_floor_50]),
RegionData(Region.skull_cavern_50, [Entrance.mine_to_skull_cavern_floor_75]),
RegionData(Region.skull_cavern_75, [Entrance.mine_to_skull_cavern_floor_100]),
RegionData(Region.skull_cavern_100, [Entrance.mine_to_skull_cavern_floor_125]),
RegionData(Region.skull_cavern_125, [Entrance.mine_to_skull_cavern_floor_150]),
RegionData(Region.skull_cavern_150, [Entrance.mine_to_skull_cavern_floor_175]),
RegionData(Region.skull_cavern_175, [Entrance.mine_to_skull_cavern_floor_200]),
RegionData(Region.skull_cavern_200, [Entrance.enter_dangerous_skull_cavern]),
RegionData(Region.dangerous_skull_cavern),
RegionData(Region.island_south, [Entrance.island_south_to_west, Entrance.island_south_to_north,
Entrance.island_south_to_east, Entrance.island_south_to_southeast,
Entrance.use_island_resort,
@ -144,7 +179,7 @@ vanilla_regions = [
RegionData(Region.volcano_dwarf_shop),
RegionData(Region.volcano_floor_10),
RegionData(Region.island_trader),
RegionData(Region.island_farmhouse),
RegionData(Region.island_farmhouse, [Entrance.island_cooking]),
RegionData(Region.gourmand_frog_cave),
RegionData(Region.colored_crystals_cave),
RegionData(Region.shipwreck),
@ -156,50 +191,49 @@ vanilla_regions = [
[Entrance.dig_site_to_professor_snail_cave, Entrance.parrot_express_dig_site_to_volcano,
Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_dig_site_to_jungle]),
RegionData(Region.professor_snail_cave),
RegionData(Region.jotpk_world_1, [Entrance.reach_jotpk_world_2]),
RegionData(Region.jotpk_world_2, [Entrance.reach_jotpk_world_3]),
RegionData(Region.jotpk_world_3),
RegionData(Region.junimo_kart_1, [Entrance.reach_junimo_kart_2]),
RegionData(Region.junimo_kart_2, [Entrance.reach_junimo_kart_3]),
RegionData(Region.junimo_kart_3),
RegionData(Region.mines, [Entrance.talk_to_mines_dwarf,
Entrance.dig_to_mines_floor_5, Entrance.dig_to_mines_floor_10,
Entrance.dig_to_mines_floor_15, Entrance.dig_to_mines_floor_20,
Entrance.dig_to_mines_floor_25, Entrance.dig_to_mines_floor_30,
Entrance.dig_to_mines_floor_35, Entrance.dig_to_mines_floor_40,
Entrance.dig_to_mines_floor_45, Entrance.dig_to_mines_floor_50,
Entrance.dig_to_mines_floor_55, Entrance.dig_to_mines_floor_60,
Entrance.dig_to_mines_floor_65, Entrance.dig_to_mines_floor_70,
Entrance.dig_to_mines_floor_75, Entrance.dig_to_mines_floor_80,
Entrance.dig_to_mines_floor_85, Entrance.dig_to_mines_floor_90,
Entrance.dig_to_mines_floor_95, Entrance.dig_to_mines_floor_100,
Entrance.dig_to_mines_floor_105, Entrance.dig_to_mines_floor_110,
Entrance.dig_to_mines_floor_115, Entrance.dig_to_mines_floor_120]),
Entrance.dig_to_mines_floor_5]),
RegionData(Region.mines_dwarf_shop),
RegionData(Region.mines_floor_5),
RegionData(Region.mines_floor_10),
RegionData(Region.mines_floor_15),
RegionData(Region.mines_floor_20),
RegionData(Region.mines_floor_25),
RegionData(Region.mines_floor_30),
RegionData(Region.mines_floor_35),
RegionData(Region.mines_floor_40),
RegionData(Region.mines_floor_45),
RegionData(Region.mines_floor_50),
RegionData(Region.mines_floor_55),
RegionData(Region.mines_floor_60),
RegionData(Region.mines_floor_65),
RegionData(Region.mines_floor_70),
RegionData(Region.mines_floor_75),
RegionData(Region.mines_floor_80),
RegionData(Region.mines_floor_85),
RegionData(Region.mines_floor_90),
RegionData(Region.mines_floor_95),
RegionData(Region.mines_floor_100),
RegionData(Region.mines_floor_105),
RegionData(Region.mines_floor_110),
RegionData(Region.mines_floor_115),
RegionData(Region.mines_floor_120),
RegionData(Region.mines_floor_5, [Entrance.dig_to_mines_floor_10]),
RegionData(Region.mines_floor_10, [Entrance.dig_to_mines_floor_15]),
RegionData(Region.mines_floor_15, [Entrance.dig_to_mines_floor_20]),
RegionData(Region.mines_floor_20, [Entrance.dig_to_mines_floor_25]),
RegionData(Region.mines_floor_25, [Entrance.dig_to_mines_floor_30]),
RegionData(Region.mines_floor_30, [Entrance.dig_to_mines_floor_35]),
RegionData(Region.mines_floor_35, [Entrance.dig_to_mines_floor_40]),
RegionData(Region.mines_floor_40, [Entrance.dig_to_mines_floor_45]),
RegionData(Region.mines_floor_45, [Entrance.dig_to_mines_floor_50]),
RegionData(Region.mines_floor_50, [Entrance.dig_to_mines_floor_55]),
RegionData(Region.mines_floor_55, [Entrance.dig_to_mines_floor_60]),
RegionData(Region.mines_floor_60, [Entrance.dig_to_mines_floor_65]),
RegionData(Region.mines_floor_65, [Entrance.dig_to_mines_floor_70]),
RegionData(Region.mines_floor_70, [Entrance.dig_to_mines_floor_75]),
RegionData(Region.mines_floor_75, [Entrance.dig_to_mines_floor_80]),
RegionData(Region.mines_floor_80, [Entrance.dig_to_mines_floor_85]),
RegionData(Region.mines_floor_85, [Entrance.dig_to_mines_floor_90]),
RegionData(Region.mines_floor_90, [Entrance.dig_to_mines_floor_95]),
RegionData(Region.mines_floor_95, [Entrance.dig_to_mines_floor_100]),
RegionData(Region.mines_floor_100, [Entrance.dig_to_mines_floor_105]),
RegionData(Region.mines_floor_105, [Entrance.dig_to_mines_floor_110]),
RegionData(Region.mines_floor_110, [Entrance.dig_to_mines_floor_115]),
RegionData(Region.mines_floor_115, [Entrance.dig_to_mines_floor_120]),
RegionData(Region.mines_floor_120, [Entrance.dig_to_dangerous_mines_20, Entrance.dig_to_dangerous_mines_60, Entrance.dig_to_dangerous_mines_100]),
RegionData(Region.dangerous_mines_20),
RegionData(Region.dangerous_mines_60),
RegionData(Region.dangerous_mines_100),
RegionData(Region.coop),
RegionData(Region.barn),
RegionData(Region.shed),
RegionData(Region.slime_hutch),
RegionData(Region.egg_festival),
RegionData(Region.flower_dance),
RegionData(Region.luau),
RegionData(Region.moonlight_jellies),
RegionData(Region.fair),
RegionData(Region.spirit_eve),
RegionData(Region.festival_of_ice),
RegionData(Region.night_market),
RegionData(Region.winter_star),
]
# Exists and where they lead
@ -208,13 +242,21 @@ vanilla_connections = [
ConnectionData(Entrance.to_farmhouse, Region.farm_house),
ConnectionData(Entrance.farmhouse_to_farm, Region.farm),
ConnectionData(Entrance.downstairs_to_cellar, Region.cellar),
ConnectionData(Entrance.farmhouse_cooking, Region.kitchen),
ConnectionData(Entrance.watch_queen_of_sauce, Region.queen_of_sauce),
ConnectionData(Entrance.farm_to_backwoods, Region.backwoods),
ConnectionData(Entrance.farm_to_bus_stop, Region.bus_stop),
ConnectionData(Entrance.farm_to_forest, Region.forest),
ConnectionData(Entrance.farm_to_farmcave, Region.farm_cave, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData(Entrance.farming, Region.farming),
ConnectionData(Entrance.enter_greenhouse, Region.greenhouse),
ConnectionData(Entrance.enter_coop, Region.coop),
ConnectionData(Entrance.enter_barn, Region.barn),
ConnectionData(Entrance.enter_shed, Region.shed),
ConnectionData(Entrance.enter_slime_hutch, Region.slime_hutch),
ConnectionData(Entrance.shipping, Region.shipping),
ConnectionData(Entrance.use_desert_obelisk, Region.desert),
ConnectionData(Entrance.use_island_obelisk, Region.island_south),
ConnectionData(Entrance.use_island_obelisk, Region.island_south, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.use_farm_obelisk, Region.farm),
ConnectionData(Entrance.backwoods_to_mountain, Region.mountain),
ConnectionData(Entrance.bus_stop_to_town, Region.town),
@ -232,6 +274,13 @@ vanilla_connections = [
ConnectionData(Entrance.enter_secret_woods, Region.secret_woods),
ConnectionData(Entrance.forest_to_sewer, Region.sewer, flag=RandomizationFlag.BUILDINGS),
ConnectionData(Entrance.buy_from_traveling_merchant, Region.traveling_cart),
ConnectionData(Entrance.buy_from_traveling_merchant_sunday, Region.traveling_cart_sunday),
ConnectionData(Entrance.buy_from_traveling_merchant_monday, Region.traveling_cart_monday),
ConnectionData(Entrance.buy_from_traveling_merchant_tuesday, Region.traveling_cart_tuesday),
ConnectionData(Entrance.buy_from_traveling_merchant_wednesday, Region.traveling_cart_wednesday),
ConnectionData(Entrance.buy_from_traveling_merchant_thursday, Region.traveling_cart_thursday),
ConnectionData(Entrance.buy_from_traveling_merchant_friday, Region.traveling_cart_friday),
ConnectionData(Entrance.buy_from_traveling_merchant_saturday, Region.traveling_cart_saturday),
ConnectionData(Entrance.town_to_sewer, Region.sewer, flag=RandomizationFlag.BUILDINGS),
ConnectionData(Entrance.enter_mutant_bug_lair, Region.mutant_bug_lair, flag=RandomizationFlag.BUILDINGS),
ConnectionData(Entrance.mountain_to_railroad, Region.railroad),
@ -267,6 +316,10 @@ vanilla_connections = [
ConnectionData(Entrance.enter_sunroom, Region.sunroom, flag=RandomizationFlag.BUILDINGS),
ConnectionData(Entrance.town_to_clint_blacksmith, Region.blacksmith,
flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.blacksmith_copper, Region.blacksmith_copper),
ConnectionData(Entrance.blacksmith_iron, Region.blacksmith_iron),
ConnectionData(Entrance.blacksmith_gold, Region.blacksmith_gold),
ConnectionData(Entrance.blacksmith_iridium, Region.blacksmith_iridium),
ConnectionData(Entrance.town_to_saloon, Region.saloon,
flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.play_journey_of_the_prairie_king, Region.jotpk_world_1),
@ -289,6 +342,9 @@ vanilla_connections = [
flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.town_to_jojamart, Region.jojamart,
flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.purchase_movie_ticket, Region.movie_ticket_stand),
ConnectionData(Entrance.enter_abandoned_jojamart, Region.abandoned_jojamart),
ConnectionData(Entrance.enter_movie_theater, Region.movie_theater),
ConnectionData(Entrance.town_to_beach, Region.beach),
ConnectionData(Entrance.enter_elliott_house, Region.elliott_house,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA),
@ -296,8 +352,9 @@ vanilla_connections = [
flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.fish_shop_to_boat_tunnel, Region.boat_tunnel,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.boat_to_ginger_island, Region.island_south),
ConnectionData(Entrance.boat_to_ginger_island, Region.island_south, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.enter_tide_pools, Region.tide_pools),
ConnectionData(Entrance.fishing, Region.fishing),
ConnectionData(Entrance.mountain_to_the_mines, Region.mines,
flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.talk_to_mines_dwarf, Region.mines_dwarf_shop),
@ -325,6 +382,9 @@ vanilla_connections = [
ConnectionData(Entrance.dig_to_mines_floor_110, Region.mines_floor_110),
ConnectionData(Entrance.dig_to_mines_floor_115, Region.mines_floor_115),
ConnectionData(Entrance.dig_to_mines_floor_120, Region.mines_floor_120),
ConnectionData(Entrance.dig_to_dangerous_mines_20, Region.dangerous_mines_20, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.dig_to_dangerous_mines_60, Region.dangerous_mines_60, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.dig_to_dangerous_mines_100, Region.dangerous_mines_100, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.enter_skull_cavern_entrance, Region.skull_cavern_entrance,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA),
ConnectionData(Entrance.enter_oasis, Region.oasis,
@ -339,6 +399,7 @@ vanilla_connections = [
ConnectionData(Entrance.mine_to_skull_cavern_floor_150, Region.skull_cavern_150),
ConnectionData(Entrance.mine_to_skull_cavern_floor_175, Region.skull_cavern_175),
ConnectionData(Entrance.mine_to_skull_cavern_floor_200, Region.skull_cavern_200),
ConnectionData(Entrance.enter_dangerous_skull_cavern, Region.dangerous_skull_cavern, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.enter_witch_warp_cave, Region.witch_warp_cave, flag=RandomizationFlag.BUILDINGS),
ConnectionData(Entrance.enter_witch_swamp, Region.witch_swamp, flag=RandomizationFlag.BUILDINGS),
ConnectionData(Entrance.enter_witch_hut, Region.witch_hut, flag=RandomizationFlag.BUILDINGS),
@ -352,17 +413,17 @@ vanilla_connections = [
ConnectionData(Entrance.island_south_to_east, Region.island_east, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_south_to_southeast, Region.island_south_east,
flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.use_island_resort, Region.island_resort),
ConnectionData(Entrance.use_island_resort, Region.island_resort, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_west_to_islandfarmhouse, Region.island_farmhouse,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_cooking, Region.kitchen),
ConnectionData(Entrance.island_west_to_gourmand_cave, Region.gourmand_frog_cave,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_west_to_crystals_cave, Region.colored_crystals_cave,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_west_to_shipwreck, Region.shipwreck,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_west_to_qi_walnut_room, Region.qi_walnut_room,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_west_to_qi_walnut_room, Region.qi_walnut_room, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_east_to_leo_hut, Region.leo_hut,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.island_east_to_island_shrine, Region.island_shrine,
@ -378,21 +439,30 @@ vanilla_connections = [
ConnectionData(Entrance.volcano_to_secret_beach, Region.volcano_secret_beach,
flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.talk_to_island_trader, Region.island_trader, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.climb_to_volcano_5, Region.volcano_floor_5),
ConnectionData(Entrance.talk_to_volcano_dwarf, Region.volcano_dwarf_shop),
ConnectionData(Entrance.climb_to_volcano_10, Region.volcano_floor_10),
ConnectionData(Entrance.parrot_express_jungle_to_docks, Region.island_south),
ConnectionData(Entrance.parrot_express_dig_site_to_docks, Region.island_south),
ConnectionData(Entrance.parrot_express_volcano_to_docks, Region.island_south),
ConnectionData(Entrance.parrot_express_volcano_to_jungle, Region.island_west),
ConnectionData(Entrance.parrot_express_docks_to_jungle, Region.island_west),
ConnectionData(Entrance.parrot_express_dig_site_to_jungle, Region.island_west),
ConnectionData(Entrance.parrot_express_docks_to_dig_site, Region.dig_site),
ConnectionData(Entrance.parrot_express_volcano_to_dig_site, Region.dig_site),
ConnectionData(Entrance.parrot_express_jungle_to_dig_site, Region.dig_site),
ConnectionData(Entrance.parrot_express_dig_site_to_volcano, Region.island_north),
ConnectionData(Entrance.parrot_express_docks_to_volcano, Region.island_north),
ConnectionData(Entrance.parrot_express_jungle_to_volcano, Region.island_north),
ConnectionData(Entrance.climb_to_volcano_5, Region.volcano_floor_5, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.talk_to_volcano_dwarf, Region.volcano_dwarf_shop, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.climb_to_volcano_10, Region.volcano_floor_10, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_jungle_to_docks, Region.island_south, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_dig_site_to_docks, Region.island_south, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_volcano_to_docks, Region.island_south, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_volcano_to_jungle, Region.island_west, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_docks_to_jungle, Region.island_west, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_dig_site_to_jungle, Region.island_west, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_docks_to_dig_site, Region.dig_site, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_volcano_to_dig_site, Region.dig_site, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_jungle_to_dig_site, Region.dig_site, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_dig_site_to_volcano, Region.island_north, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_docks_to_volcano, Region.island_north, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.parrot_express_jungle_to_volcano, Region.island_north, flag=RandomizationFlag.GINGER_ISLAND),
ConnectionData(Entrance.attend_egg_festival, Region.egg_festival),
ConnectionData(Entrance.attend_flower_dance, Region.flower_dance),
ConnectionData(Entrance.attend_luau, Region.luau),
ConnectionData(Entrance.attend_moonlight_jellies, Region.moonlight_jellies),
ConnectionData(Entrance.attend_fair, Region.fair),
ConnectionData(Entrance.attend_spirit_eve, Region.spirit_eve),
ConnectionData(Entrance.attend_festival_of_ice, Region.festival_of_ice),
ConnectionData(Entrance.attend_night_market, Region.night_market),
ConnectionData(Entrance.attend_winter_star, Region.winter_star),
]
@ -409,78 +479,105 @@ def create_final_regions(world_options) -> List[RegionData]:
(region for region in final_regions if region.name == mod_region.name), None)
if existing_region:
final_regions.remove(existing_region)
if ModificationFlag.MODIFIED in mod_region.flag:
mod_region = modify_vanilla_regions(existing_region, mod_region)
final_regions.append(existing_region.get_merged_with(mod_region.exits))
continue
final_regions.append(mod_region.get_clone())
return final_regions
def create_final_connections(world_options) -> List[ConnectionData]:
final_connections = []
final_connections.extend(vanilla_connections)
if world_options.mods is None:
return final_connections
for mod in world_options.mods.value:
def create_final_connections_and_regions(world_options) -> Tuple[Dict[str, ConnectionData], Dict[str, RegionData]]:
regions_data: Dict[str, RegionData] = {region.name: region for region in create_final_regions(world_options)}
connections = {connection.name: connection for connection in vanilla_connections}
connections = modify_connections_for_mods(connections, world_options.mods)
include_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_false
return remove_ginger_island_regions_and_connections(regions_data, connections, include_island)
def remove_ginger_island_regions_and_connections(regions_by_name: Dict[str, RegionData], connections: Dict[str, ConnectionData], include_island: bool):
if include_island:
return connections, regions_by_name
for connection_name in list(connections):
connection = connections[connection_name]
if connection.flag & RandomizationFlag.GINGER_ISLAND:
regions_by_name.pop(connection.destination, None)
connections.pop(connection_name)
regions_by_name = {name: regions_by_name[name].get_without_exit(connection_name) for name in regions_by_name}
return connections, regions_by_name
def modify_connections_for_mods(connections: Dict[str, ConnectionData], mods) -> Dict[str, ConnectionData]:
if mods is None:
return connections
for mod in mods.value:
if mod not in ModDataList:
continue
final_connections.extend(ModDataList[mod].connections)
return final_connections
if mod in vanilla_connections_to_remove_by_mod:
for connection_data in vanilla_connections_to_remove_by_mod[mod]:
connections.pop(connection_data.name)
connections.update({connection.name: connection for connection in ModDataList[mod].connections})
return connections
def create_regions(region_factory: RegionFactory, random: Random, world_options) -> Tuple[
Dict[str, Region], Dict[str, str]]:
final_regions = create_final_regions(world_options)
regions: Dict[str: Region] = {region.name: region_factory(region.name, region.exits) for region in
final_regions}
entrances: Dict[str: Entrance] = {entrance.name: entrance
for region in regions.values()
for entrance in region.exits}
def modify_vanilla_regions(existing_region: RegionData, modified_region: RegionData) -> RegionData:
regions_by_name: Dict[str, RegionData] = {region.name: region for region in final_regions}
connections, randomized_data = randomize_connections(random, world_options, regions_by_name)
updated_region = existing_region
region_exits = updated_region.exits
modified_exits = modified_region.exits
for exits in modified_exits:
region_exits.remove(exits)
return updated_region
def create_regions(region_factory: RegionFactory, random: Random, world_options: StardewValleyOptions) -> Tuple[
Dict[str, Region], Dict[str, Entrance], Dict[str, str]]:
entrances_data, regions_data = create_final_connections_and_regions(world_options)
regions_by_name: Dict[str: Region] = {region_name: region_factory(region_name, regions_data[region_name].exits) for region_name in regions_data}
entrances_by_name: Dict[str: Entrance] = {entrance.name: entrance for region in regions_by_name.values() for entrance in region.exits
if entrance.name in entrances_data}
connections, randomized_data = randomize_connections(random, world_options, regions_data, entrances_data)
for connection in connections:
if connection.name in entrances:
entrances[connection.name].connect(regions[connection.destination])
return regions, randomized_data
if connection.name in entrances_by_name:
entrances_by_name[connection.name].connect(regions_by_name[connection.destination])
return regions_by_name, entrances_by_name, randomized_data
def randomize_connections(random: Random, world_options, regions_by_name) -> Tuple[
List[ConnectionData], Dict[str, str]]:
connections_to_randomize = []
final_connections = create_final_connections(world_options)
connections_by_name: Dict[str, ConnectionData] = {connection.name: connection for connection in final_connections}
def randomize_connections(random: Random, world_options: StardewValleyOptions, regions_by_name: Dict[str, RegionData],
connections_by_name: Dict[str, ConnectionData]) -> Tuple[List[ConnectionData], Dict[str, str]]:
connections_to_randomize: List[ConnectionData] = []
if world_options.entrance_randomization == EntranceRandomization.option_pelican_town:
connections_to_randomize = [connection for connection in final_connections if
RandomizationFlag.PELICAN_TOWN in connection.flag]
connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if
RandomizationFlag.PELICAN_TOWN in connections_by_name[connection].flag]
elif world_options.entrance_randomization == EntranceRandomization.option_non_progression:
connections_to_randomize = [connection for connection in final_connections if
RandomizationFlag.NON_PROGRESSION in connection.flag]
connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if
RandomizationFlag.NON_PROGRESSION in connections_by_name[connection].flag]
elif world_options.entrance_randomization == EntranceRandomization.option_buildings:
connections_to_randomize = [connection for connection in final_connections if
RandomizationFlag.BUILDINGS in connection.flag]
connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if
RandomizationFlag.BUILDINGS in connections_by_name[connection].flag]
elif world_options.entrance_randomization == EntranceRandomization.option_chaos:
connections_to_randomize = [connection for connection in final_connections if
RandomizationFlag.BUILDINGS in connection.flag]
connections_to_randomize = exclude_island_if_necessary(connections_to_randomize, world_options)
connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if
RandomizationFlag.BUILDINGS in connections_by_name[connection].flag]
connections_to_randomize = remove_excluded_entrances(connections_to_randomize, world_options)
# On Chaos, we just add the connections to randomize, unshuffled, and the client does it every day
randomized_data_for_mod = {}
for connection in connections_to_randomize:
randomized_data_for_mod[connection.name] = connection.name
randomized_data_for_mod[connection.reverse] = connection.reverse
return final_connections, randomized_data_for_mod
return list(connections_by_name.values()), randomized_data_for_mod
connections_to_randomize = remove_excluded_entrances(connections_to_randomize, world_options)
random.shuffle(connections_to_randomize)
destination_pool = list(connections_to_randomize)
random.shuffle(destination_pool)
randomized_connections = randomize_chosen_connections(connections_to_randomize, destination_pool)
add_non_randomized_connections(final_connections, connections_to_randomize, randomized_connections)
add_non_randomized_connections(list(connections_by_name.values()), connections_to_randomize, randomized_connections)
swap_connections_until_valid(regions_by_name, connections_by_name, randomized_connections, connections_to_randomize, random)
randomized_connections_for_generation = create_connections_for_generation(randomized_connections)
@ -489,25 +586,14 @@ def randomize_connections(random: Random, world_options, regions_by_name) -> Tup
return randomized_connections_for_generation, randomized_data_for_mod
def remove_excluded_entrances(connections_to_randomize, world_options):
def remove_excluded_entrances(connections_to_randomize: List[ConnectionData], world_options: StardewValleyOptions) -> List[ConnectionData]:
exclude_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_true
exclude_sewers = world_options.museumsanity == Museumsanity.option_none
if exclude_island:
connections_to_randomize = [connection for connection in connections_to_randomize if RandomizationFlag.GINGER_ISLAND not in connection.flag]
if exclude_sewers:
connections_to_randomize = [connection for connection in connections_to_randomize if Region.sewer not in connection.name or Region.sewer not in connection.reverse]
return connections_to_randomize
def exclude_island_if_necessary(connections_to_randomize: List[ConnectionData], world_options) -> List[ConnectionData]:
exclude_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_true
if exclude_island:
connections_to_randomize = [connection for connection in connections_to_randomize if
RandomizationFlag.GINGER_ISLAND not in connection.flag]
return connections_to_randomize
def randomize_chosen_connections(connections_to_randomize: List[ConnectionData],
destination_pool: List[ConnectionData]) -> Dict[ConnectionData, ConnectionData]:
randomized_connections = {}
@ -517,8 +603,7 @@ def randomize_chosen_connections(connections_to_randomize: List[ConnectionData],
return randomized_connections
def create_connections_for_generation(randomized_connections: Dict[ConnectionData, ConnectionData]) -> List[
ConnectionData]:
def create_connections_for_generation(randomized_connections: Dict[ConnectionData, ConnectionData]) -> List[ConnectionData]:
connections = []
for connection in randomized_connections:
destination = randomized_connections[connection]
@ -536,37 +621,50 @@ def create_data_for_mod(randomized_connections: Dict[ConnectionData, ConnectionD
add_to_mod_data(connection, destination, randomized_data_for_mod)
return randomized_data_for_mod
def add_to_mod_data(connection: ConnectionData, destination: ConnectionData, randomized_data_for_mod: Dict[str, str]):
randomized_data_for_mod[connection.name] = destination.name
randomized_data_for_mod[destination.reverse] = connection.reverse
def add_non_randomized_connections(connections, connections_to_randomize: List[ConnectionData],
def add_non_randomized_connections(all_connections: List[ConnectionData], connections_to_randomize: List[ConnectionData],
randomized_connections: Dict[ConnectionData, ConnectionData]):
for connection in connections:
for connection in all_connections:
if connection in connections_to_randomize:
continue
randomized_connections[connection] = connection
def swap_connections_until_valid(regions_by_name, connections_by_name, randomized_connections: Dict[ConnectionData, ConnectionData],
def swap_connections_until_valid(regions_by_name, connections_by_name: Dict[str, ConnectionData], randomized_connections: Dict[ConnectionData, ConnectionData],
connections_to_randomize: List[ConnectionData], random: Random):
while True:
reachable_regions, unreachable_regions = find_reachable_regions(regions_by_name, connections_by_name, randomized_connections)
if not unreachable_regions:
return randomized_connections
swap_one_connection(regions_by_name, connections_by_name, randomized_connections, reachable_regions,
unreachable_regions, connections_to_randomize, random)
swap_one_random_connection(regions_by_name, connections_by_name, randomized_connections, reachable_regions,
unreachable_regions, connections_to_randomize, random)
def region_should_be_reachable(region_name: str, connections_in_slot: Iterable[ConnectionData]) -> bool:
if region_name == Region.menu:
return True
for connection in connections_in_slot:
if region_name == connection.destination:
return True
return False
def find_reachable_regions(regions_by_name, connections_by_name,
randomized_connections: Dict[ConnectionData, ConnectionData]):
reachable_regions = {Region.menu}
unreachable_regions = {region for region in regions_by_name.keys()}
# unreachable_regions = {region for region in regions_by_name.keys() if region_should_be_reachable(region, connections_by_name.values())}
unreachable_regions.remove(Region.menu)
exits_to_explore = list(regions_by_name[Region.menu].exits)
while exits_to_explore:
exit_name = exits_to_explore.pop()
# if exit_name not in connections_by_name:
# continue
exit_connection = connections_by_name[exit_name]
replaced_connection = randomized_connections[exit_connection]
target_region_name = replaced_connection.destination
@ -580,9 +678,9 @@ def find_reachable_regions(regions_by_name, connections_by_name,
return reachable_regions, unreachable_regions
def swap_one_connection(regions_by_name, connections_by_name,randomized_connections: Dict[ConnectionData, ConnectionData],
reachable_regions: Set[str], unreachable_regions: Set[str],
connections_to_randomize: List[ConnectionData], random: Random):
def swap_one_random_connection(regions_by_name, connections_by_name, randomized_connections: Dict[ConnectionData, ConnectionData],
reachable_regions: Set[str], unreachable_regions: Set[str],
connections_to_randomize: List[ConnectionData], random: Random):
randomized_connections_already_shuffled = {connection: randomized_connections[connection]
for connection in randomized_connections
if connection != randomized_connections[connection]}
@ -604,7 +702,11 @@ def swap_one_connection(regions_by_name, connections_by_name,randomized_connecti
chosen_reachable_entrance_name = random.choice(chosen_reachable_region.exits)
chosen_reachable_entrance = connections_by_name[chosen_reachable_entrance_name]
reachable_destination = randomized_connections[chosen_reachable_entrance]
unreachable_destination = randomized_connections[chosen_unreachable_entrance]
randomized_connections[chosen_reachable_entrance] = unreachable_destination
randomized_connections[chosen_unreachable_entrance] = reachable_destination
swap_two_connections(chosen_reachable_entrance, chosen_unreachable_entrance, randomized_connections)
def swap_two_connections(entrance_1, entrance_2, randomized_connections):
reachable_destination = randomized_connections[entrance_1]
unreachable_destination = randomized_connections[entrance_2]
randomized_connections[entrance_1] = unreachable_destination
randomized_connections[entrance_2] = reachable_destination

View File

@ -1 +1 @@
importlib_resources; python_version <= '3.8'
importlib_resources; python_version <= '3.8'

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ from typing import List
from worlds.stardew_valley import LocationData
from worlds.stardew_valley.items import load_item_csv, Group, ItemData
from worlds.stardew_valley.locations import load_location_csv
from worlds.stardew_valley.locations import load_location_csv, LocationTags
RESOURCE_PACK_CODE_OFFSET = 5000
script_folder = Path(__file__)
@ -34,14 +34,15 @@ def write_item_csv(items: List[ItemData]):
def write_location_csv(locations: List[LocationData]):
with open((script_folder.parent.parent / "data/locations.csv").resolve(), "w", newline="") as file:
write = csv.DictWriter(file, ["id", "region", "name", "tags"])
write = csv.DictWriter(file, ["id", "region", "name", "tags", "mod_name"])
write.writeheader()
for location in locations:
location_dict = {
"id": location.code_without_offset,
"name": location.name,
"region": location.region,
"tags": ",".join(sorted(group.name for group in location.tags))
"tags": ",".join(sorted(group.name for group in location.tags)),
"mod_name": location.mod_name
}
write.writerow(location_dict)
@ -76,12 +77,11 @@ if __name__ == "__main__":
location_counter = itertools.count(max(location.code_without_offset
for location in loaded_locations
if location.code_without_offset is not None) + 1)
locations_to_write = []
for location in loaded_locations:
if location.code_without_offset is None:
locations_to_write.append(
LocationData(next(location_counter), location.region, location.name, location.tags))
LocationData(next(location_counter), location.region, location.name, location.mod_name, location.tags))
continue
locations_to_write.append(location)

View File

@ -1,384 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable, Dict, List, Union, FrozenSet, Set
from BaseClasses import CollectionState, ItemClassification
from .items import item_table
MISSING_ITEM = "THIS ITEM IS MISSING"
class StardewRule:
def __call__(self, state: CollectionState) -> bool:
raise NotImplementedError
def __or__(self, other) -> StardewRule:
if type(other) is Or:
return Or(self, *other.rules)
return Or(self, other)
def __and__(self, other) -> StardewRule:
if type(other) is And:
return And(other.rules.union({self}))
return And(self, other)
def get_difficulty(self):
raise NotImplementedError
def simplify(self) -> StardewRule:
return self
class True_(StardewRule): # noqa
def __new__(cls, _cache=[]): # noqa
# Only one single instance will be ever created.
if not _cache:
_cache.append(super(True_, cls).__new__(cls))
return _cache[0]
def __call__(self, state: CollectionState) -> bool:
return True
def __or__(self, other) -> StardewRule:
return self
def __and__(self, other) -> StardewRule:
return other
def __repr__(self):
return "True"
def get_difficulty(self):
return 0
class False_(StardewRule): # noqa
def __new__(cls, _cache=[]): # noqa
# Only one single instance will be ever created.
if not _cache:
_cache.append(super(False_, cls).__new__(cls))
return _cache[0]
def __call__(self, state: CollectionState) -> bool:
return False
def __or__(self, other) -> StardewRule:
return other
def __and__(self, other) -> StardewRule:
return self
def __repr__(self):
return "False"
def get_difficulty(self):
return 999999999
false_ = False_()
true_ = True_()
assert false_ is False_()
assert true_ is True_()
class Or(StardewRule):
rules: FrozenSet[StardewRule]
_simplified: bool
def __init__(self, rule: Union[StardewRule, Iterable[StardewRule]], *rules: StardewRule):
rules_list: Set[StardewRule]
if isinstance(rule, Iterable):
rules_list = {*rule}
else:
rules_list = {rule}
if rules is not None:
rules_list.update(rules)
assert rules_list, "Can't create a Or conditions without rules"
if any(type(rule) is Or for rule in rules_list):
new_rules: Set[StardewRule] = set()
for rule in rules_list:
if type(rule) is Or:
new_rules.update(rule.rules)
else:
new_rules.add(rule)
rules_list = new_rules
self.rules = frozenset(rules_list)
self._simplified = False
def __call__(self, state: CollectionState) -> bool:
return any(rule(state) for rule in self.rules)
def __repr__(self):
return f"({' | '.join(repr(rule) for rule in self.rules)})"
def __or__(self, other):
if other is true_:
return other
if other is false_:
return self
if type(other) is Or:
return Or(self.rules.union(other.rules))
return Or(self.rules.union({other}))
def __eq__(self, other):
return isinstance(other, self.__class__) and other.rules == self.rules
def __hash__(self):
return hash(self.rules)
def get_difficulty(self):
return min(rule.get_difficulty() for rule in self.rules)
def simplify(self) -> StardewRule:
if self._simplified:
return self
if true_ in self.rules:
return true_
simplified_rules = [simplified for simplified in {rule.simplify() for rule in self.rules}
if simplified is not false_]
if not simplified_rules:
return false_
if len(simplified_rules) == 1:
return simplified_rules[0]
self.rules = frozenset(simplified_rules)
self._simplified = True
return self
class And(StardewRule):
rules: FrozenSet[StardewRule]
_simplified: bool
def __init__(self, rule: Union[StardewRule, Iterable[StardewRule]], *rules: StardewRule):
rules_list: Set[StardewRule]
if isinstance(rule, Iterable):
rules_list = {*rule}
else:
rules_list = {rule}
if rules is not None:
rules_list.update(rules)
if not rules_list:
rules_list.add(true_)
elif any(type(rule) is And for rule in rules_list):
new_rules: Set[StardewRule] = set()
for rule in rules_list:
if type(rule) is And:
new_rules.update(rule.rules)
else:
new_rules.add(rule)
rules_list = new_rules
self.rules = frozenset(rules_list)
self._simplified = False
def __call__(self, state: CollectionState) -> bool:
return all(rule(state) for rule in self.rules)
def __repr__(self):
return f"({' & '.join(repr(rule) for rule in self.rules)})"
def __and__(self, other):
if other is true_:
return self
if other is false_:
return other
if type(other) is And:
return And(self.rules.union(other.rules))
return And(self.rules.union({other}))
def __eq__(self, other):
return isinstance(other, self.__class__) and other.rules == self.rules
def __hash__(self):
return hash(self.rules)
def get_difficulty(self):
return max(rule.get_difficulty() for rule in self.rules)
def simplify(self) -> StardewRule:
if self._simplified:
return self
if false_ in self.rules:
return false_
simplified_rules = [simplified for simplified in {rule.simplify() for rule in self.rules}
if simplified is not true_]
if not simplified_rules:
return true_
if len(simplified_rules) == 1:
return simplified_rules[0]
self.rules = frozenset(simplified_rules)
self._simplified = True
return self
class Count(StardewRule):
count: int
rules: List[StardewRule]
def __init__(self, count: int, rule: Union[StardewRule, Iterable[StardewRule]], *rules: StardewRule):
rules_list: List[StardewRule]
if isinstance(rule, Iterable):
rules_list = [*rule]
else:
rules_list = [rule]
if rules is not None:
rules_list.extend(rules)
assert rules_list, "Can't create a Count conditions without rules"
assert len(rules_list) >= count, "Count need at least as many rules at the count"
self.rules = rules_list
self.count = count
def __call__(self, state: CollectionState) -> bool:
c = 0
for r in self.rules:
if r(state):
c += 1
if c >= self.count:
return True
return False
def __repr__(self):
return f"Received {self.count} {repr(self.rules)}"
def get_difficulty(self):
rules_sorted_by_difficulty = sorted(self.rules, key=lambda x: x.get_difficulty())
easiest_n_rules = rules_sorted_by_difficulty[0:self.count]
return max(rule.get_difficulty() for rule in easiest_n_rules)
def simplify(self):
return Count(self.count, [rule.simplify() for rule in self.rules])
class TotalReceived(StardewRule):
count: int
items: Iterable[str]
player: int
def __init__(self, count: int, items: Union[str, Iterable[str]], player: int):
items_list: List[str]
if isinstance(items, Iterable):
items_list = [*items]
else:
items_list = [items]
assert items_list, "Can't create a Total Received conditions without items"
for item in items_list:
assert item_table[item].classification & ItemClassification.progression, \
"Item has to be progression to be used in logic"
self.player = player
self.items = items_list
self.count = count
def __call__(self, state: CollectionState) -> bool:
c = 0
for item in self.items:
c += state.count(item, self.player)
if c >= self.count:
return True
return False
def __repr__(self):
return f"Received {self.count} {self.items}"
def get_difficulty(self):
return self.count
@dataclass(frozen=True)
class Received(StardewRule):
item: str
player: int
count: int
def __post_init__(self):
assert item_table[self.item].classification & ItemClassification.progression, \
f"Item [{item_table[self.item].name}] has to be progression to be used in logic"
def __call__(self, state: CollectionState) -> bool:
return state.has(self.item, self.player, self.count)
def __repr__(self):
if self.count == 1:
return f"Received {self.item}"
return f"Received {self.count} {self.item}"
def get_difficulty(self):
if self.item == "Spring":
return 0
if self.item == "Summer":
return 1
if self.item == "Fall":
return 2
if self.item == "Winter":
return 3
return self.count
@dataclass(frozen=True)
class Reach(StardewRule):
spot: str
resolution_hint: str
player: int
def __call__(self, state: CollectionState) -> bool:
return state.can_reach(self.spot, self.resolution_hint, self.player)
def __repr__(self):
return f"Reach {self.resolution_hint} {self.spot}"
def get_difficulty(self):
return 1
@dataclass(frozen=True)
class Has(StardewRule):
item: str
# For sure there is a better way than just passing all the rules everytime
other_rules: Dict[str, StardewRule]
def __call__(self, state: CollectionState) -> bool:
if isinstance(self.item, str):
return self.other_rules[self.item](state)
def __repr__(self):
if not self.item in self.other_rules:
return f"Has {self.item} -> {MISSING_ITEM}"
return f"Has {self.item} -> {repr(self.other_rules[self.item])}"
def get_difficulty(self):
return self.other_rules[self.item].get_difficulty() + 1
def __hash__(self):
return hash(self.item)
def simplify(self) -> StardewRule:
return self.other_rules[self.item].simplify()

View File

@ -0,0 +1,4 @@
from .base import *
from .literal import *
from .protocol import *
from .state import *

View File

@ -0,0 +1,448 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from collections import deque
from functools import cached_property
from itertools import chain
from threading import Lock
from typing import Iterable, Dict, List, Union, Sized, Hashable, Callable, Tuple, Set, Optional
from BaseClasses import CollectionState
from .literal import true_, false_, LiteralStardewRule
from .protocol import StardewRule
MISSING_ITEM = "THIS ITEM IS MISSING"
class BaseStardewRule(StardewRule, ABC):
def __or__(self, other) -> StardewRule:
if other is true_ or other is false_ or type(other) is Or:
return other | self
return Or(self, other)
def __and__(self, other) -> StardewRule:
if other is true_ or other is false_ or type(other) is And:
return other & self
return And(self, other)
class CombinableStardewRule(BaseStardewRule, ABC):
@property
@abstractmethod
def combination_key(self) -> Hashable:
raise NotImplementedError
@property
@abstractmethod
def value(self):
raise NotImplementedError
def is_same_rule(self, other: CombinableStardewRule):
return self.combination_key == other.combination_key
def add_into(self, rules: Dict[Hashable, CombinableStardewRule], reducer: Callable[[CombinableStardewRule, CombinableStardewRule], CombinableStardewRule]) \
-> Dict[Hashable, CombinableStardewRule]:
rules = dict(rules)
if self.combination_key in rules:
rules[self.combination_key] = reducer(self, rules[self.combination_key])
else:
rules[self.combination_key] = self
return rules
def __and__(self, other):
if isinstance(other, CombinableStardewRule) and self.is_same_rule(other):
return And.combine(self, other)
return super().__and__(other)
def __or__(self, other):
if isinstance(other, CombinableStardewRule) and self.is_same_rule(other):
return Or.combine(self, other)
return super().__or__(other)
class _SimplificationState:
original_simplifiable_rules: Tuple[StardewRule, ...]
rules_to_simplify: deque[StardewRule]
simplified_rules: Set[StardewRule]
lock: Lock
def __init__(self, simplifiable_rules: Tuple[StardewRule, ...], rules_to_simplify: Optional[deque[StardewRule]] = None,
simplified_rules: Optional[Set[StardewRule]] = None):
if simplified_rules is None:
simplified_rules = set()
self.original_simplifiable_rules = simplifiable_rules
self.rules_to_simplify = rules_to_simplify
self.simplified_rules = simplified_rules
self.locked = False
@property
def is_simplified(self):
return self.rules_to_simplify is not None and not self.rules_to_simplify
def short_circuit(self, complement: LiteralStardewRule):
self.rules_to_simplify = deque()
self.simplified_rules = {complement}
def try_popleft(self):
try:
self.rules_to_simplify.popleft()
except IndexError:
pass
def acquire_copy(self):
state = _SimplificationState(self.original_simplifiable_rules, self.rules_to_simplify.copy(), self.simplified_rules.copy())
state.acquire()
return state
def merge(self, other: _SimplificationState):
return _SimplificationState(self.original_simplifiable_rules + other.original_simplifiable_rules)
def add(self, rule: StardewRule):
return _SimplificationState(self.original_simplifiable_rules + (rule,))
def acquire(self):
"""
This just set a boolean to True and is absolutely not thread safe. It just works because AP is single-threaded.
"""
if self.locked is True:
return False
self.locked = True
return True
def release(self):
assert self.locked
self.locked = False
class AggregatingStardewRule(BaseStardewRule, ABC):
"""
Logic for both "And" and "Or" rules.
"""
identity: LiteralStardewRule
complement: LiteralStardewRule
symbol: str
combinable_rules: Dict[Hashable, CombinableStardewRule]
simplification_state: _SimplificationState
_last_short_circuiting_rule: Optional[StardewRule] = None
def __init__(self, *rules: StardewRule, _combinable_rules=None, _simplification_state=None):
if _combinable_rules is None:
assert rules, f"Can't create an aggregating condition without rules"
rules, _combinable_rules = self.split_rules(rules)
_simplification_state = _SimplificationState(rules)
self.combinable_rules = _combinable_rules
self.simplification_state = _simplification_state
@property
def original_rules(self):
return RepeatableChain(self.combinable_rules.values(), self.simplification_state.original_simplifiable_rules)
@property
def current_rules(self):
if self.simplification_state.rules_to_simplify is None:
return self.original_rules
return RepeatableChain(self.combinable_rules.values(), self.simplification_state.simplified_rules, self.simplification_state.rules_to_simplify)
@classmethod
def split_rules(cls, rules: Union[Iterable[StardewRule]]) -> Tuple[Tuple[StardewRule, ...], Dict[Hashable, CombinableStardewRule]]:
other_rules = []
reduced_rules = {}
for rule in rules:
if isinstance(rule, CombinableStardewRule):
key = rule.combination_key
if key not in reduced_rules:
reduced_rules[key] = rule
continue
reduced_rules[key] = cls.combine(reduced_rules[key], rule)
continue
if type(rule) is cls:
other_rules.extend(rule.simplification_state.original_simplifiable_rules) # noqa
reduced_rules = cls.merge(reduced_rules, rule.combinable_rules) # noqa
continue
other_rules.append(rule)
return tuple(other_rules), reduced_rules
@classmethod
def merge(cls, left: Dict[Hashable, CombinableStardewRule], right: Dict[Hashable, CombinableStardewRule]) -> Dict[Hashable, CombinableStardewRule]:
reduced_rules = dict(left)
for key, rule in right.items():
if key not in reduced_rules:
reduced_rules[key] = rule
continue
reduced_rules[key] = cls.combine(reduced_rules[key], rule)
return reduced_rules
@staticmethod
@abstractmethod
def combine(left: CombinableStardewRule, right: CombinableStardewRule) -> CombinableStardewRule:
raise NotImplementedError
def short_circuit_simplification(self):
self.simplification_state.short_circuit(self.complement)
self.combinable_rules = {}
return self.complement, self.complement.value
def short_circuit_evaluation(self, rule):
self._last_short_circuiting_rule = rule
return self, self.complement.value
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
"""
The global idea here is the same as short-circuiting operators, applied to evaluation and rule simplification.
"""
# Directly checking last rule that short-circuited, in case state has not changed.
if self._last_short_circuiting_rule:
if self._last_short_circuiting_rule(state) is self.complement.value:
return self.short_circuit_evaluation(self._last_short_circuiting_rule)
self._last_short_circuiting_rule = None
# Combinable rules are considered already simplified, so we evaluate them right away to go faster.
for rule in self.combinable_rules.values():
if rule(state) is self.complement.value:
return self.short_circuit_evaluation(rule)
if self.simplification_state.is_simplified:
# The rule is fully simplified, so now we can only evaluate.
for rule in self.simplification_state.simplified_rules:
if rule(state) is self.complement.value:
return self.short_circuit_evaluation(rule)
return self, self.identity.value
return self.evaluate_while_simplifying_stateful(state)
def evaluate_while_simplifying_stateful(self, state):
local_state = self.simplification_state
try:
# Creating a new copy, so we don't modify the rules while we're already evaluating it. This can happen if a rule is used for an entrance and a
# location. When evaluating a given rule what requires access to a region, the region cache can get an update. If it does, we could enter this rule
# again. Since the simplification is stateful, the set of simplified rules can be modified while it's being iterated on, and cause a crash.
#
# After investigation, for millions of call to this method, copy were acquired 425 times.
# Merging simplification state in parent call was deemed useless.
if not local_state.acquire():
local_state = local_state.acquire_copy()
self.simplification_state = local_state
# Evaluating what has already been simplified. First it will be faster than simplifying "new" rules, but we also assume that if we reach this point
# and there are already are simplified rule, one of these rules has short-circuited, and might again, so we can leave early.
for rule in local_state.simplified_rules:
if rule(state) is self.complement.value:
return self.short_circuit_evaluation(rule)
# If the queue is None, it means we have not start simplifying. Otherwise, we will continue simplification where we left.
if local_state.rules_to_simplify is None:
rules_to_simplify = frozenset(local_state.original_simplifiable_rules)
if self.complement in rules_to_simplify:
return self.short_circuit_simplification()
local_state.rules_to_simplify = deque(rules_to_simplify)
# Start simplification where we left.
while local_state.rules_to_simplify:
result = self.evaluate_rule_while_simplifying_stateful(local_state, state)
local_state.try_popleft()
if result is not None:
return result
# The whole rule has been simplified and evaluated without short-circuit.
return self, self.identity.value
finally:
local_state.release()
def evaluate_rule_while_simplifying_stateful(self, local_state, state):
simplified, value = local_state.rules_to_simplify[0].evaluate_while_simplifying(state)
# Identity is removed from the resulting simplification since it does not affect the result.
if simplified is self.identity:
return
# If we find a complement here, we know the rule will always short-circuit, what ever the state.
if simplified is self.complement:
return self.short_circuit_simplification()
# Keep the simplified rule to be reevaluated later.
local_state.simplified_rules.add(simplified)
# Now we use the value to short-circuit if it is the complement.
if value is self.complement.value:
return self.short_circuit_evaluation(simplified)
def __str__(self):
return f"({self.symbol.join(str(rule) for rule in self.original_rules)})"
def __repr__(self):
return f"({self.symbol.join(repr(rule) for rule in self.original_rules)})"
def __eq__(self, other):
return (isinstance(other, type(self)) and self.combinable_rules == other.combinable_rules and
self.simplification_state.original_simplifiable_rules == self.simplification_state.original_simplifiable_rules)
def __hash__(self):
return hash((id(self.combinable_rules), self.simplification_state.original_simplifiable_rules))
class Or(AggregatingStardewRule):
identity = false_
complement = true_
symbol = " | "
def __call__(self, state: CollectionState) -> bool:
return self.evaluate_while_simplifying(state)[1]
def __or__(self, other):
if other is true_ or other is false_:
return other | self
if isinstance(other, CombinableStardewRule):
return Or(_combinable_rules=other.add_into(self.combinable_rules, self.combine), _simplification_state=self.simplification_state)
if type(other) is Or:
return Or(_combinable_rules=self.merge(self.combinable_rules, other.combinable_rules),
_simplification_state=self.simplification_state.merge(other.simplification_state))
return Or(_combinable_rules=self.combinable_rules, _simplification_state=self.simplification_state.add(other))
@staticmethod
def combine(left: CombinableStardewRule, right: CombinableStardewRule) -> CombinableStardewRule:
return min(left, right, key=lambda x: x.value)
def get_difficulty(self):
return min(rule.get_difficulty() for rule in self.original_rules)
class And(AggregatingStardewRule):
identity = true_
complement = false_
symbol = " & "
def __call__(self, state: CollectionState) -> bool:
return self.evaluate_while_simplifying(state)[1]
def __and__(self, other):
if other is true_ or other is false_:
return other & self
if isinstance(other, CombinableStardewRule):
return And(_combinable_rules=other.add_into(self.combinable_rules, self.combine), _simplification_state=self.simplification_state)
if type(other) is And:
return And(_combinable_rules=self.merge(self.combinable_rules, other.combinable_rules),
_simplification_state=self.simplification_state.merge(other.simplification_state))
return And(_combinable_rules=self.combinable_rules, _simplification_state=self.simplification_state.add(other))
@staticmethod
def combine(left: CombinableStardewRule, right: CombinableStardewRule) -> CombinableStardewRule:
return max(left, right, key=lambda x: x.value)
def get_difficulty(self):
return max(rule.get_difficulty() for rule in self.original_rules)
class Count(BaseStardewRule):
count: int
rules: List[StardewRule]
def __init__(self, rules: List[StardewRule], count: int):
self.rules = rules
self.count = count
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
c = 0
for i in range(self.rules_count):
self.rules[i], value = self.rules[i].evaluate_while_simplifying(state)
if value:
c += 1
if c >= self.count:
return self, True
if c + self.rules_count - i < self.count:
break
return self, False
def __call__(self, state: CollectionState) -> bool:
return self.evaluate_while_simplifying(state)[1]
@cached_property
def rules_count(self):
return len(self.rules)
def get_difficulty(self):
self.rules = sorted(self.rules, key=lambda x: x.get_difficulty())
# In an optimal situation, all the simplest rules will be true. Since the rules are sorted, we know that the most difficult rule we might have to do
# is the one at the "self.count".
return self.rules[self.count - 1].get_difficulty()
def __repr__(self):
return f"Received {self.count} {repr(self.rules)}"
class Has(BaseStardewRule):
item: str
# For sure there is a better way than just passing all the rules everytime
other_rules: Dict[str, StardewRule]
def __init__(self, item: str, other_rules: Dict[str, StardewRule]):
self.item = item
self.other_rules = other_rules
def __call__(self, state: CollectionState) -> bool:
return self.evaluate_while_simplifying(state)[1]
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
return self.other_rules[self.item].evaluate_while_simplifying(state)
def get_difficulty(self):
return self.other_rules[self.item].get_difficulty() + 1
def __str__(self):
if self.item not in self.other_rules:
return f"Has {self.item} -> {MISSING_ITEM}"
return f"Has {self.item}"
def __repr__(self):
if self.item not in self.other_rules:
return f"Has {self.item} -> {MISSING_ITEM}"
return f"Has {self.item} -> {repr(self.other_rules[self.item])}"
def __hash__(self):
return hash(self.item)
class RepeatableChain(Iterable, Sized):
"""
Essentially a copy of what's in the core, with proper type hinting
"""
def __init__(self, *iterable: Union[Iterable, Sized]):
self.iterables = iterable
def __iter__(self):
return chain.from_iterable(self.iterables)
def __bool__(self):
return any(sub_iterable for sub_iterable in self.iterables)
def __len__(self):
return sum(len(iterable) for iterable in self.iterables)
def __contains__(self, item):
return any(item in it for it in self.iterables)

View File

@ -0,0 +1,39 @@
from functools import singledispatch
from typing import Set
from . import StardewRule, Reach, Count, AggregatingStardewRule, Has
def look_for_indirect_connection(rule: StardewRule) -> Set[str]:
required_regions = set()
_find(rule, required_regions)
return required_regions
@singledispatch
def _find(rule: StardewRule, regions: Set[str]):
...
@_find.register
def _(rule: AggregatingStardewRule, regions: Set[str]):
for r in rule.original_rules:
_find(r, regions)
@_find.register
def _(rule: Count, regions: Set[str]):
for r in rule.rules:
_find(r, regions)
@_find.register
def _(rule: Has, regions: Set[str]):
r = rule.other_rules[rule.item]
_find(r, regions)
@_find.register
def _(rule: Reach, regions: Set[str]):
if rule.resolution_hint == "Region":
regions.add(rule.spot)

View File

@ -0,0 +1,62 @@
from abc import ABC
from typing import Tuple
from BaseClasses import CollectionState
from .protocol import StardewRule
class LiteralStardewRule(StardewRule, ABC):
value: bool
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
return self, self.value
def __call__(self, state: CollectionState) -> bool:
return self.value
def __repr__(self):
return str(self.value)
class True_(LiteralStardewRule): # noqa
value = True
def __new__(cls, _cache=[]): # noqa
# Only one single instance will be ever created.
if not _cache:
_cache.append(super(True_, cls).__new__(cls))
return _cache[0]
def __or__(self, other) -> StardewRule:
return self
def __and__(self, other) -> StardewRule:
return other
def get_difficulty(self):
return 0
class False_(LiteralStardewRule): # noqa
value = False
def __new__(cls, _cache=[]): # noqa
# Only one single instance will be ever created.
if not _cache:
_cache.append(super(False_, cls).__new__(cls))
return _cache[0]
def __or__(self, other) -> StardewRule:
return other
def __and__(self, other) -> StardewRule:
return self
def get_difficulty(self):
return 999999999
false_ = False_()
true_ = True_()
assert false_
assert true_

View File

@ -0,0 +1,30 @@
from __future__ import annotations
from abc import abstractmethod
from typing import Protocol, Tuple, runtime_checkable
from BaseClasses import CollectionState
@runtime_checkable
class StardewRule(Protocol):
@abstractmethod
def __call__(self, state: CollectionState) -> bool:
...
@abstractmethod
def __and__(self, other: StardewRule):
...
@abstractmethod
def __or__(self, other: StardewRule):
...
@abstractmethod
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
...
@abstractmethod
def get_difficulty(self):
...

View File

@ -0,0 +1,140 @@
from dataclasses import dataclass
from typing import Iterable, Union, List, Tuple, Hashable
from BaseClasses import ItemClassification, CollectionState
from .base import BaseStardewRule, CombinableStardewRule
from .protocol import StardewRule
from ..items import item_table
class TotalReceived(BaseStardewRule):
count: int
items: Iterable[str]
player: int
def __init__(self, count: int, items: Union[str, Iterable[str]], player: int):
items_list: List[str]
if isinstance(items, Iterable):
items_list = [*items]
else:
items_list = [items]
assert items_list, "Can't create a Total Received conditions without items"
for item in items_list:
assert item_table[item].classification & ItemClassification.progression, \
f"Item [{item_table[item].name}] has to be progression to be used in logic"
self.player = player
self.items = items_list
self.count = count
def __call__(self, state: CollectionState) -> bool:
c = 0
for item in self.items:
c += state.count(item, self.player)
if c >= self.count:
return True
return False
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
return self, self(state)
def get_difficulty(self):
return self.count
def __repr__(self):
return f"Received {self.count} {self.items}"
@dataclass(frozen=True)
class Received(CombinableStardewRule):
item: str
player: int
count: int
def __post_init__(self):
assert item_table[self.item].classification & ItemClassification.progression, \
f"Item [{item_table[self.item].name}] has to be progression to be used in logic"
@property
def combination_key(self) -> Hashable:
return self.item
@property
def value(self):
return self.count
def __call__(self, state: CollectionState) -> bool:
return state.has(self.item, self.player, self.count)
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
return self, self(state)
def __repr__(self):
if self.count == 1:
return f"Received {self.item}"
return f"Received {self.count} {self.item}"
def get_difficulty(self):
return self.count
@dataclass(frozen=True)
class Reach(BaseStardewRule):
spot: str
resolution_hint: str
player: int
def __call__(self, state: CollectionState) -> bool:
if self.resolution_hint == 'Region' and self.spot not in state.multiworld.regions.region_cache[self.player]:
return False
return state.can_reach(self.spot, self.resolution_hint, self.player)
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
return self, self(state)
def __repr__(self):
return f"Reach {self.resolution_hint} {self.spot}"
def get_difficulty(self):
return 1
@dataclass(frozen=True)
class HasProgressionPercent(CombinableStardewRule):
player: int
percent: int
def __post_init__(self):
assert self.percent > 0, "HasProgressionPercent rule must be above 0%"
assert self.percent <= 100, "HasProgressionPercent rule can't require more than 100% of items"
@property
def combination_key(self) -> Hashable:
return HasProgressionPercent.__name__
@property
def value(self):
return self.percent
def __call__(self, state: CollectionState) -> bool:
stardew_world = state.multiworld.worlds[self.player]
total_count = stardew_world.total_progression_items
needed_count = (total_count * self.percent) // 100
total_count = 0
for item in state.prog_items[self.player]:
item_count = state.prog_items[self.player][item]
total_count += item_count
if total_count >= needed_count:
return True
return False
def evaluate_while_simplifying(self, state: CollectionState) -> Tuple[StardewRule, bool]:
return self, self(state)
def __repr__(self):
return f"HasProgressionPercent {self.percent}"
def get_difficulty(self):
return self.percent

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