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:
parent
f7da833572
commit
52e65e208e
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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])
|
|
@ -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
|
@ -1,9 +0,0 @@
|
|||
fishing_chest = "Fishing Chest"
|
||||
secret_note = "Secret Note"
|
||||
|
||||
quality_dict = {
|
||||
0: "",
|
||||
1: "Silver",
|
||||
2: "Gold",
|
||||
3: "Iridium"
|
||||
}
|
|
@ -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}
|
|
@ -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,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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
|
@ -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}"
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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"))
|
|
@ -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))
|
|
@ -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))
|
|
@ -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))
|
|
@ -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
|
|
@ -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_()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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`.
|
|
@ -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)
|
|
@ -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_()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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))
|
|
@ -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)
|
|
@ -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_()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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))
|
|
@ -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)
|
|
@ -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_()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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()))
|
|
@ -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
|
|
@ -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_()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
importlib_resources; python_version <= '3.8'
|
||||
importlib_resources; python_version <= '3.8'
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -0,0 +1,4 @@
|
|||
from .base import *
|
||||
from .literal import *
|
||||
from .protocol import *
|
||||
from .state import *
|
|
@ -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)
|
|
@ -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)
|
|
@ -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_
|
|
@ -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):
|
||||
...
|
|
@ -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
Loading…
Reference in New Issue