forked from mirror/Archipelago
Major Content update for Stardew Valley ### Features - New BundleRandomization Value: Meme Bundles - Over 100 custom bundles, designed to be jokes, references, trolls, etc - New Setting: Bundles Per Room modifier - New Setting: Backpack Size - New Setting: Secretsanity - Checks for triggering easter eggs and secrets - New Setting: Moviesanity - Checks for watching movies and sharing snacks with Villagers - New Setting: Eatsanity - Checks for eating items - New Setting: Hatsanity - Checks for wearing Hats - New Setting: Start Without - Allows you to select any combination of various "starting" items, that you will actually not start with. Notably, tools, backpack slots, Day5 unlocks, etc. - New Setting: Allowed Filler Items - Allows you to customize the filler items you'll get - New Setting: Endgame Locations - Checks for various expensive endgame tasks and purchases - New Shipsanity value: Crops and Fish - New Settings: Jojapocalypse and settings to customize it - Bundle Plando: Replaced with BundleWhitelist and BundleBlacklist, for more customization freedom - Added a couple of Host.yaml settings to help hosts allow or ban specific difficult settings that could cause problems if the people don't know what they are signing up for. Plus a truckload of improvements on the mod side, not seen in this PR. ### Removed features - Integration for Stardew Valley Expanded. It is simply disabled, the code is all still there, but I'm extremely tired of providing tech support for it, plus Stardew Valley 1.7 was announced and that will break it again, so I'm done. When a maintainer steps up, it can be re-enabled.
680 lines
35 KiB
Python
680 lines
35 KiB
Python
import logging
|
|
from random import Random
|
|
from typing import List
|
|
|
|
from BaseClasses import Item, ItemClassification
|
|
from .fillers import generate_resource_packs_and_traps, generate_unique_filler_items
|
|
from .filters import remove_excluded
|
|
from .item_data import StardewItemFactory, items_by_group, Group, item_table, ItemData
|
|
from ..content.feature import friendsanity
|
|
from ..content.game_content import StardewContent
|
|
from ..content.vanilla.ginger_island import ginger_island_content_pack
|
|
from ..content.vanilla.qi_board import qi_board_content_pack
|
|
from ..data.game_item import ItemTag
|
|
from ..mods.mod_data import ModNames
|
|
from ..options import StardewValleyOptions, FestivalLocations, SpecialOrderLocations, SeasonRandomization, Museumsanity, \
|
|
ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \
|
|
Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, Moviesanity
|
|
from ..options.options import IncludeEndgameLocations, Friendsanity
|
|
from ..strings.ap_names.ap_option_names import WalnutsanityOptionName, SecretsanityOptionName, EatsanityOptionName, ChefsanityOptionName, StartWithoutOptionName
|
|
from ..strings.ap_names.ap_weapon_names import APWeapon
|
|
from ..strings.ap_names.buff_names import Buff
|
|
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade, Bookseller
|
|
from ..strings.ap_names.mods.mod_items import SVEQuestItem
|
|
from ..strings.backpack_tiers import Backpack
|
|
from ..strings.building_names import Building
|
|
from ..strings.currency_names import Currency
|
|
from ..strings.tool_names import Tool
|
|
from ..strings.wallet_item_names import Wallet
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item],
|
|
options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
|
|
items = []
|
|
unique_items = create_unique_items(item_factory, options, content, random)
|
|
|
|
remove_items(items_to_exclude, unique_items)
|
|
|
|
remove_items_if_no_room_for_them(unique_items, locations_count, random)
|
|
|
|
items += unique_items
|
|
logger.debug(f"Created {len(unique_items)} unique items")
|
|
|
|
unique_filler_items = generate_unique_filler_items(item_factory, content, options, random, locations_count - len(items))
|
|
items += unique_filler_items
|
|
logger.debug(f"Created {len(unique_filler_items)} unique filler items")
|
|
|
|
resource_pack_items = generate_resource_packs_and_traps(item_factory, options, content, random, items + items_to_exclude, locations_count - len(items))
|
|
items += resource_pack_items
|
|
logger.debug(f"Created {len(resource_pack_items)} resource packs")
|
|
|
|
return items
|
|
|
|
|
|
def remove_items(items_to_remove: List[Item], items: List[Item]):
|
|
for item_to_remove in items_to_remove:
|
|
for i, item_candidate in enumerate(items):
|
|
if item_to_remove != item_candidate:
|
|
continue
|
|
if item_to_remove.classification == item_candidate.classification and item_to_remove.advancement == item_candidate.advancement:
|
|
items.pop(i)
|
|
break
|
|
|
|
|
|
def remove_items_if_no_room_for_them(unique_items: List[Item], locations_count: int, random: Random):
|
|
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")
|
|
count = len(unique_items)
|
|
assert len(removable_items) >= number_of_items_to_remove, \
|
|
f"There should be at least as many locations [{locations_count}] as there are mandatory items [{count - len(removable_items)}]"
|
|
items_to_remove = random.sample(removable_items, number_of_items_to_remove)
|
|
remove_items(items_to_remove, unique_items)
|
|
|
|
|
|
def create_unique_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, random: Random) -> List[Item]:
|
|
items = []
|
|
|
|
items.extend(item_factory(item) for item in items_by_group[Group.COMMUNITY_REWARD])
|
|
items.append(item_factory(CommunityUpgrade.movie_theater)) # It is a community reward, but we need two of them
|
|
create_raccoons(item_factory, options, items)
|
|
items.append(item_factory(Wallet.metal_detector)) # Always offer at least one metal detector
|
|
|
|
create_backpack_items(item_factory, options, content, items)
|
|
create_weapons(item_factory, options, content, items)
|
|
items.append(item_factory("Skull Key"))
|
|
create_elevators(item_factory, options, content, items)
|
|
create_tools(item_factory, content, items)
|
|
create_skills(item_factory, content, items)
|
|
create_wizard_buildings(item_factory, options, content, items)
|
|
create_carpenter_buildings(item_factory, options, content, items)
|
|
items.append(item_factory("Railroad Boulder Removed"))
|
|
items.append(item_factory(CommunityUpgrade.fruit_bats))
|
|
items.append(item_factory(CommunityUpgrade.mushroom_boxes))
|
|
items.append(item_factory("Beach Bridge"))
|
|
create_tv_channels(item_factory, options, items)
|
|
create_quest_rewards(item_factory, options, content, items)
|
|
create_stardrops(item_factory, options, content, items)
|
|
create_museum_items(item_factory, options, items)
|
|
create_arcade_machine_items(item_factory, options, items)
|
|
create_movement_buffs(item_factory, options, items)
|
|
create_traveling_merchant_items(item_factory, items)
|
|
items.append(item_factory("Return Scepter"))
|
|
create_seasons(item_factory, options, items)
|
|
create_seeds(item_factory, content, items)
|
|
create_friendsanity_items(item_factory, options, content, items, random)
|
|
create_festival_rewards(item_factory, options, items)
|
|
create_special_order_board_rewards(item_factory, options, items)
|
|
create_special_order_qi_rewards(item_factory, options, content, items)
|
|
create_walnuts(item_factory, options, content, items)
|
|
create_walnut_purchase_rewards(item_factory, content, items)
|
|
create_crafting_recipes(item_factory, options, content, items)
|
|
create_cooking_recipes(item_factory, options, content, items)
|
|
create_shipsanity_items(item_factory, options, items)
|
|
create_booksanity_items(item_factory, options, content, items)
|
|
create_movie_items(item_factory, options, items)
|
|
create_secrets_items(item_factory, content, options, items)
|
|
create_eatsanity_enzyme_items(item_factory, options, items)
|
|
create_endgame_locations_items(item_factory, options, items)
|
|
|
|
create_goal_items(item_factory, options, items)
|
|
items.append(item_factory("Golden Egg"))
|
|
items.append(item_factory(CommunityUpgrade.mr_qi_plane_ride))
|
|
|
|
items.append(item_factory(Wallet.mens_locker_key))
|
|
items.append(item_factory(Wallet.womens_locker_key))
|
|
|
|
create_sve_special_items(item_factory, content, items)
|
|
create_magic_mod_spells(item_factory, content, items)
|
|
create_deepwoods_pendants(item_factory, content, items)
|
|
create_archaeology_items(item_factory, content, items)
|
|
|
|
return items
|
|
|
|
|
|
def create_raccoons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
number_progressive_raccoons = 9
|
|
if options.bundle_per_room.value < 0:
|
|
number_progressive_raccoons -= options.bundle_per_room.value
|
|
if options.quest_locations.has_no_story_quests():
|
|
number_progressive_raccoons = number_progressive_raccoons - 1
|
|
|
|
items.extend(item_factory(item) for item in [CommunityUpgrade.raccoon] * number_progressive_raccoons)
|
|
|
|
|
|
def create_backpack_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
if options.backpack_progression == BackpackProgression.option_vanilla:
|
|
return
|
|
num_per_tier = options.backpack_size.count_per_tier()
|
|
backpack_tier_names = Backpack.get_purchasable_tiers(ModNames.big_backpack in content.registered_packs, StartWithoutOptionName.backpack in options.start_without)
|
|
num_backpacks = len(backpack_tier_names) * num_per_tier
|
|
|
|
items.extend(item_factory(item) for item in ["Progressive Backpack"] * num_backpacks)
|
|
|
|
|
|
def create_footwear(item_factory: StardewItemFactory, number: int) -> List[Item]:
|
|
return [item_factory(APWeapon.footwear) for _ in range(number)]
|
|
|
|
|
|
def create_weapons(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
weapons = weapons_count(content)
|
|
items.extend(item_factory(item) for item in [APWeapon.slingshot] * 2)
|
|
monstersanity = options.monstersanity
|
|
|
|
ring_classification = ItemClassification.progression if options.bundle_randomization == BundleRandomization.option_meme else ItemClassification.useful
|
|
rings_items = [item for item in items_by_group[Group.FILLER_RING] if item.classification is not ItemClassification.filler]
|
|
|
|
if monstersanity == Monstersanity.option_none: # Without monstersanity, might not be enough checks to split the weapons
|
|
items.extend(item_factory(item) for item in [APWeapon.weapon] * weapons)
|
|
items.extend(create_footwear(item_factory, 3)) # 1-2 | 3-4 | 6-7-8
|
|
rings_items = [item for item in rings_items if item.classification is ItemClassification.progression]
|
|
items.extend(item_factory(item, classification_pre_fill=ring_classification) for item in rings_items)
|
|
return
|
|
|
|
items.extend(item_factory(item) for item in [APWeapon.sword] * weapons)
|
|
items.extend(item_factory(item) for item in [APWeapon.club] * weapons)
|
|
items.extend(item_factory(item) for item in [APWeapon.dagger] * weapons)
|
|
items.extend(create_footwear(item_factory, 4)) # 1-2 | 3-4 | 6-7-8 | 11-13
|
|
|
|
items.extend(item_factory(item, classification_pre_fill=ring_classification) for item in rings_items)
|
|
if monstersanity == Monstersanity.option_goals or monstersanity == Monstersanity.option_one_per_category or \
|
|
monstersanity == Monstersanity.option_short_goals or monstersanity == Monstersanity.option_very_short_goals:
|
|
return
|
|
|
|
|
|
def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
if options.elevator_progression == ElevatorProgression.option_vanilla:
|
|
return
|
|
|
|
items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24])
|
|
if ModNames.deepwoods in content.registered_packs:
|
|
items.extend([item_factory(item) for item in ["Progressive Woods Obelisk Sigils"] * 10])
|
|
if ModNames.skull_cavern_elevator in content.registered_packs:
|
|
items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8])
|
|
|
|
|
|
def create_tools(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
tool_progression = content.features.tool_progression
|
|
for tool, count in tool_progression.tool_distribution.items():
|
|
item = item_table[tool_progression.to_progressive_item_name(tool)]
|
|
|
|
# Trash can is only used in tool upgrade logic, so the last trash can is not progression because it basically does not unlock anything.
|
|
if tool == Tool.trash_can:
|
|
count -= 1
|
|
items.append(item_factory(item,
|
|
classification_pre_fill=ItemClassification.useful,
|
|
classification_post_fill=ItemClassification.progression_skip_balancing))
|
|
|
|
items.extend([item_factory(item) for _ in range(count)])
|
|
|
|
|
|
def create_skills(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
skill_progression = content.features.skill_progression
|
|
if not skill_progression.is_progressive:
|
|
return
|
|
|
|
for skill in content.skills.values():
|
|
items.extend(item_factory(skill.level_name) for _ in skill_progression.get_randomized_level_names_by_level(skill))
|
|
|
|
if skill_progression.is_mastery_randomized(skill):
|
|
items.append(item_factory(skill.mastery_name))
|
|
|
|
if skill_progression.are_masteries_shuffled:
|
|
items.append(item_factory(Wallet.mastery_of_the_five_ways))
|
|
|
|
|
|
def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
useful_buildings_classification = ItemClassification.progression_skip_balancing if goal_is_perfection(options) else ItemClassification.useful
|
|
items.append(item_factory("Earth Obelisk", classification_pre_fill=useful_buildings_classification))
|
|
items.append(item_factory("Water Obelisk", classification_pre_fill=useful_buildings_classification))
|
|
items.append(item_factory("Desert Obelisk"))
|
|
items.append(item_factory("Junimo Hut"))
|
|
items.append(item_factory("Gold Clock", classification_pre_fill=useful_buildings_classification))
|
|
if content.is_enabled(ginger_island_content_pack):
|
|
items.append(item_factory("Island Obelisk"))
|
|
if content.is_enabled(ModNames.deepwoods):
|
|
items.append(item_factory("Woods Obelisk"))
|
|
|
|
|
|
def create_carpenter_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
building_progression = content.features.building_progression
|
|
if not building_progression.is_progressive:
|
|
return
|
|
|
|
for building in content.farm_buildings.values():
|
|
item_name, _ = building_progression.to_progressive_item(building.name)
|
|
if item_name in [Building.stable, Building.well] and options.bundle_randomization != BundleRandomization.option_meme:
|
|
items.append(item_factory(item_name, classification_pre_fill=ItemClassification.useful))
|
|
else:
|
|
items.append(item_factory(item_name))
|
|
|
|
|
|
def create_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
create_special_quest_rewards(item_factory, options, content, items)
|
|
create_help_wanted_quest_rewards(item_factory, options, items)
|
|
|
|
create_quest_rewards_sve(item_factory, options, content, items)
|
|
|
|
|
|
def create_special_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
if options.quest_locations.has_no_story_quests():
|
|
return
|
|
# items.append(item_factory("Adventurer's Guild")) # Now unlocked always!
|
|
items.append(item_factory(Wallet.club_card))
|
|
items.append(item_factory(Wallet.magnifying_glass))
|
|
items.append(item_factory(Wallet.magic_ink))
|
|
items.append(item_factory(Wallet.iridium_snake_milk))
|
|
if ModNames.sve in content.registered_packs:
|
|
items.append(item_factory(Wallet.bears_knowledge))
|
|
else:
|
|
items.append(item_factory(Wallet.bears_knowledge, classification_pre_fill=ItemClassification.useful)) # Not necessary outside of SVE
|
|
items.append(item_factory("Dark Talisman"))
|
|
if content.is_enabled(ginger_island_content_pack):
|
|
items.append(item_factory("Fairy Dust Recipe"))
|
|
|
|
|
|
def create_help_wanted_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
if options.quest_locations <= 0:
|
|
return
|
|
|
|
number_help_wanted = options.quest_locations.value
|
|
quest_per_prize_ticket = 3
|
|
number_prize_tickets = number_help_wanted // quest_per_prize_ticket
|
|
items.extend(item_factory(item) for item in [Currency.prize_ticket] * number_prize_tickets)
|
|
|
|
|
|
def create_stardrops(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
stardrops_classification = get_stardrop_classification(options)
|
|
items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # The Mines level 100
|
|
items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Krobus Stardrop
|
|
if content.features.fishsanity.is_enabled:
|
|
items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Master Angler Stardrop
|
|
if ModNames.deepwoods in content.registered_packs:
|
|
items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Petting the Unicorn
|
|
if content.features.friendsanity.is_enabled:
|
|
items.append(item_factory("Stardrop", classification_pre_fill=stardrops_classification)) # Spouse Stardrop
|
|
if SecretsanityOptionName.easy in options.secretsanity:
|
|
# Always Progression as a different secret requires a stardrop
|
|
items.append(item_factory("Stardrop", classification_pre_fill=ItemClassification.progression)) # Old Master Cannoli.
|
|
|
|
|
|
def create_museum_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
items.append(item_factory(Wallet.rusty_key))
|
|
items.append(item_factory(Wallet.dwarvish_translation_guide))
|
|
items.append(item_factory("Ancient Seeds Recipe"))
|
|
items.append(item_factory("Stardrop", classification_pre_fill=get_stardrop_classification(options)))
|
|
if options.museumsanity == Museumsanity.option_none:
|
|
return
|
|
items.extend(item_factory(item) for item in ["Magic Rock Candy"] * 10)
|
|
items.extend(item_factory(item) for item in ["Ancient Seeds"] * 5)
|
|
items.append(item_factory(Wallet.metal_detector))
|
|
|
|
|
|
def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item], random: Random):
|
|
if not content.features.friendsanity.is_enabled:
|
|
return
|
|
|
|
create_babies(item_factory, items, random)
|
|
|
|
for villager in content.villagers.values():
|
|
item_name = friendsanity.to_item_name(villager.name)
|
|
|
|
for _ in content.features.friendsanity.get_randomized_hearts(villager):
|
|
items.append(item_factory(item_name))
|
|
|
|
for _ in content.features.friendsanity.get_pet_randomized_hearts():
|
|
items.append(item_factory(friendsanity.pet_heart_item_name, classification_pre_fill=ItemClassification.progression_skip_balancing))
|
|
|
|
|
|
def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random):
|
|
baby_items = [item for item in items_by_group[Group.BABY]]
|
|
for i in range(2):
|
|
chosen_baby = random.choice(baby_items)
|
|
items.append(item_factory(chosen_baby))
|
|
|
|
|
|
def create_arcade_machine_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
if options.arcade_machine_locations == ArcadeMachineLocations.option_full_shuffling:
|
|
items.append(item_factory("JotPK: Progressive Boots"))
|
|
items.append(item_factory("JotPK: Progressive Boots"))
|
|
items.append(item_factory("JotPK: Progressive Gun"))
|
|
items.append(item_factory("JotPK: Progressive Gun"))
|
|
items.append(item_factory("JotPK: Progressive Gun"))
|
|
items.append(item_factory("JotPK: Progressive Gun"))
|
|
items.append(item_factory("JotPK: Progressive Ammo"))
|
|
items.append(item_factory("JotPK: Progressive Ammo"))
|
|
items.append(item_factory("JotPK: Progressive Ammo"))
|
|
items.append(item_factory("JotPK: Extra Life"))
|
|
items.append(item_factory("JotPK: Extra Life"))
|
|
items.append(item_factory("JotPK: Increased Drop Rate"))
|
|
items.extend(item_factory(item) for item in ["Junimo Kart: Extra Life"] * 8)
|
|
|
|
|
|
def create_movement_buffs(item_factory, options: StardewValleyOptions, items: List[Item]):
|
|
movement_buffs: int = options.movement_buff_number.value
|
|
items.extend(item_factory(item) for item in [Buff.movement] * movement_buffs)
|
|
|
|
|
|
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
|
|
items.extend([*(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]),
|
|
*(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6)])
|
|
|
|
|
|
def create_seasons(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
if options.season_randomization == SeasonRandomization.option_disabled:
|
|
return
|
|
|
|
if options.season_randomization == SeasonRandomization.option_progressive:
|
|
items.extend([item_factory(item) for item in ["Progressive Season"] * 3])
|
|
return
|
|
|
|
items.extend([item_factory(item) for item in items_by_group[Group.SEASON]])
|
|
|
|
|
|
def create_seeds(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
if not content.features.cropsanity.is_enabled:
|
|
return
|
|
|
|
items.extend(item_factory(item_table[seed.name]) for seed in content.find_tagged_items(ItemTag.CROPSANITY_SEED))
|
|
|
|
|
|
def create_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
items.append(item_factory("Deluxe Scarecrow Recipe"))
|
|
if options.festival_locations == FestivalLocations.option_disabled:
|
|
return
|
|
|
|
festival_rewards = [item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler]
|
|
items.extend([*festival_rewards, item_factory("Stardrop", classification_pre_fill=get_stardrop_classification(options))])
|
|
|
|
|
|
def create_walnuts(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
walnutsanity = options.walnutsanity
|
|
if not content.is_enabled(ginger_island_content_pack) or walnutsanity == Walnutsanity.preset_none:
|
|
return
|
|
|
|
# Give baseline walnuts just to be nice
|
|
num_single_walnuts = 0
|
|
num_triple_walnuts = 2
|
|
num_penta_walnuts = 1
|
|
# https://stardewvalleywiki.com/Golden_Walnut
|
|
# Totals should be accurate, but distribution is slightly offset to make room for baseline walnuts
|
|
if WalnutsanityOptionName.puzzles in walnutsanity: # 61
|
|
num_single_walnuts += 6 # 6
|
|
num_triple_walnuts += 5 # 15
|
|
num_penta_walnuts += 8 # 40
|
|
if WalnutsanityOptionName.bushes in walnutsanity: # 25
|
|
num_single_walnuts += 16 # 16
|
|
num_triple_walnuts += 3 # 9
|
|
if WalnutsanityOptionName.dig_spots in walnutsanity: # 18
|
|
num_single_walnuts += 18 # 18
|
|
if WalnutsanityOptionName.repeatables in walnutsanity: # 33
|
|
num_single_walnuts += 30 # 30
|
|
num_triple_walnuts += 1 # 3
|
|
|
|
items.extend([item_factory(item) for item in ["Golden Walnut"] * num_single_walnuts])
|
|
items.extend([item_factory(item) for item in ["3 Golden Walnuts"] * num_triple_walnuts])
|
|
items.extend([item_factory(item) for item in ["5 Golden Walnuts"] * num_penta_walnuts])
|
|
|
|
|
|
def create_walnut_purchase_rewards(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
if not content.is_enabled(ginger_island_content_pack):
|
|
return
|
|
|
|
items.extend([item_factory("Boat Repair"),
|
|
item_factory("Open Professor Snail Cave"),
|
|
item_factory("Ostrich Incubator Recipe"),
|
|
item_factory("Treehouse"),
|
|
*[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_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, content: StardewContent, items: List[Item]):
|
|
if not content.is_enabled(ginger_island_content_pack):
|
|
return
|
|
qi_gem_rewards = []
|
|
if options.bundle_randomization >= BundleRandomization.option_remixed:
|
|
qi_gem_rewards.append("15 Qi Gems")
|
|
qi_gem_rewards.append("15 Qi Gems")
|
|
|
|
if content.is_enabled(qi_board_content_pack):
|
|
qi_gem_rewards.extend(["100 Qi Gems", "10 Qi Gems", "40 Qi Gems", "25 Qi Gems", "25 Qi Gems",
|
|
"40 Qi Gems", "20 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems"])
|
|
|
|
qi_gem_items = [item_factory(reward) for reward in qi_gem_rewards]
|
|
items.extend(qi_gem_items)
|
|
|
|
|
|
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, content: StardewContent, items: List[Item]):
|
|
has_craftsanity = options.craftsanity == Craftsanity.option_all
|
|
crafting_recipes = []
|
|
crafting_recipes.extend([recipe for recipe in items_by_group[Group.QI_CRAFTING_RECIPE]])
|
|
if has_craftsanity:
|
|
crafting_recipes.extend([recipe for recipe in items_by_group[Group.CRAFTSANITY]])
|
|
crafting_recipes = remove_excluded(crafting_recipes, content, options)
|
|
items.extend([item_factory(item) for item in crafting_recipes])
|
|
|
|
|
|
def create_cooking_recipes(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
chefsanity = options.chefsanity
|
|
if chefsanity == Chefsanity.preset_none:
|
|
return
|
|
|
|
chefsanity_recipes_by_name = {recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_STARTER]} # Dictionary to not make duplicates
|
|
|
|
if ChefsanityOptionName.queen_of_sauce in chefsanity:
|
|
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_QOS]})
|
|
if ChefsanityOptionName.purchases in chefsanity:
|
|
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_PURCHASE]})
|
|
if ChefsanityOptionName.friendship in chefsanity:
|
|
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_FRIENDSHIP]})
|
|
if ChefsanityOptionName.skills in chefsanity:
|
|
chefsanity_recipes_by_name.update({recipe.name: recipe for recipe in items_by_group[Group.CHEFSANITY_SKILL]})
|
|
|
|
filtered_chefsanity_recipes = remove_excluded(list(chefsanity_recipes_by_name.values()), content, 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_booksanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
create_bookseller_items(item_factory, options, content, items)
|
|
booksanity = content.features.booksanity
|
|
if not booksanity.is_enabled:
|
|
return
|
|
|
|
items.extend(item_factory(item_table[booksanity.to_item_name(book.name)]) for book in content.find_tagged_items(ItemTag.BOOK_POWER))
|
|
progressive_lost_book = item_table[booksanity.progressive_lost_book]
|
|
# We do -1 here because the first lost book spawns freely in the museum
|
|
num_lost_books = len([book for book in content.features.booksanity.get_randomized_lost_books()]) - 1
|
|
items.extend(item_factory(progressive_lost_book) for _ in range(num_lost_books))
|
|
|
|
|
|
def create_bookseller_items(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
needs_books = options.shipsanity == Shipsanity.option_everything or content.features.booksanity.is_enabled or content.features.hatsanity.is_enabled
|
|
book_items = []
|
|
book_items.extend(item_factory(item_table[Bookseller.days]) for _ in range(4 if needs_books else 1))
|
|
if not needs_books:
|
|
book_items.extend(item_factory(item_table[Bookseller.days], classification_pre_fill=ItemClassification.filler) for _ in range(3))
|
|
book_items.extend(item_factory(item_table[Bookseller.stock_rare_books]) for _ in range(2 if needs_books else 1))
|
|
book_items.append(item_factory(item_table[Bookseller.stock_permanent_books]))
|
|
book_items.append(item_factory(item_table[Bookseller.stock_experience_books]))
|
|
if needs_books:
|
|
book_items.extend(item_factory(item_table[Bookseller.stock_experience_books], classification_pre_fill=ItemClassification.filler) for _ in range(2))
|
|
|
|
items.extend(book_items)
|
|
|
|
|
|
def create_movie_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
if options.moviesanity.value < Moviesanity.option_all_movies:
|
|
return
|
|
|
|
items.extend(item_factory(item) for item in items_by_group[Group.MOVIESANITY])
|
|
|
|
|
|
def create_secrets_items(item_factory: StardewItemFactory, content: StardewContent, options: StardewValleyOptions, items: List[Item]):
|
|
if not options.secretsanity:
|
|
return
|
|
secret_items = []
|
|
if SecretsanityOptionName.easy in options.secretsanity:
|
|
secret_items.extend(items_by_group[Group.EASY_SECRET])
|
|
# if SecretsanityOptionName.fishing in options.secretsanity:
|
|
# secret_items.extend(items_by_group[Group.FISHING_SECRET]) # There are no longer any of these items, they are now part of FILLER_DECORATION
|
|
# if SecretsanityOptionName.difficult in options.secretsanity:
|
|
# items.extend(item_factory(item) for item in items_by_group[Group.DIFFICULT_SECRET])
|
|
if SecretsanityOptionName.secret_notes in options.secretsanity:
|
|
secret_items.extend(items_by_group[Group.SECRET_NOTES_SECRET])
|
|
if options.quest_locations.has_no_story_quests():
|
|
secret_items.append(item_table[Wallet.club_card])
|
|
secret_items.append(item_table[Wallet.iridium_snake_milk])
|
|
filtered_secret_items = remove_excluded(list(secret_items), content, options)
|
|
items.extend([item_factory(item) for item in filtered_secret_items])
|
|
|
|
|
|
def create_eatsanity_enzyme_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
if EatsanityOptionName.lock_effects not in options.eatsanity:
|
|
return
|
|
|
|
# These items unlock progressively stronger ability to digest food items that give the associated buff
|
|
# Upon receiving the enzyme, you also get a temporary buff of whatever the effect is
|
|
# Stamina and Health items can go beyond their original max value, but the buffs cannot.
|
|
items.extend(item_factory(item) for item in ["Stamina Enzyme"]*10)
|
|
items.extend(item_factory(item) for item in ["Health Enzyme"]*10)
|
|
items.extend(item_factory(item) for item in ["Speed Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Luck Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Farming Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Foraging Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Fishing Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Mining Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Magnetism Enzyme"]*2)
|
|
items.extend(item_factory(item) for item in ["Defense Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Attack Enzyme"]*5)
|
|
items.extend(item_factory(item) for item in ["Max Stamina Enzyme"]*3)
|
|
items.extend(item_factory(item) for item in ["Squid Ink Enzyme"])
|
|
items.extend(item_factory(item) for item in ["Monster Musk Enzyme"])
|
|
items.extend(item_factory(item) for item in ["Oil Of Garlic Enzyme"])
|
|
items.extend(item_factory(item) for item in ["Tipsy Enzyme"])
|
|
|
|
|
|
def create_endgame_locations_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
if options.include_endgame_locations == IncludeEndgameLocations.option_false:
|
|
return
|
|
|
|
items_to_add = []
|
|
items_to_add.extend(items_by_group[Group.ENDGAME_LOCATION_ITEMS])
|
|
if options.friendsanity != Friendsanity.option_all_with_marriage:
|
|
for portrait in items_by_group[Group.REQUIRES_FRIENDSANITY_MARRIAGE]:
|
|
items_to_add.remove(portrait)
|
|
items.extend(item_factory(item) for item in items_to_add)
|
|
|
|
|
|
def create_goal_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
|
|
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, content: StardewContent, items: List[Item]):
|
|
if ModNames.archaeology not in content.registered_packs:
|
|
return
|
|
|
|
items.append(item_factory(Wallet.metal_detector))
|
|
|
|
|
|
def create_magic_mod_spells(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
if ModNames.magic not in content.registered_packs:
|
|
return
|
|
items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]])
|
|
|
|
|
|
def create_deepwoods_pendants(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
if ModNames.deepwoods not in content.registered_packs:
|
|
return
|
|
items.extend([item_factory(item) for item in ["Pendant of Elders", "Pendant of Community", "Pendant of Depths"]])
|
|
|
|
|
|
def create_sve_special_items(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
|
|
if ModNames.sve not in content.registered_packs:
|
|
return
|
|
|
|
items.extend([item_factory(item) for item in items_by_group[Group.MOD_WARP] if ModNames.sve in item.content_packs])
|
|
|
|
|
|
def create_quest_rewards_sve(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
|
|
if not content.is_enabled(ModNames.sve):
|
|
return
|
|
|
|
ginger_island_included = content.is_enabled(ginger_island_content_pack)
|
|
items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items])
|
|
if ginger_island_included:
|
|
items.extend([item_factory(item) for item in SVEQuestItem.sve_always_quest_items_ginger_island])
|
|
|
|
if options.quest_locations.has_no_story_quests():
|
|
return
|
|
|
|
items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items])
|
|
if not ginger_island_included:
|
|
return
|
|
items.extend([item_factory(item) for item in SVEQuestItem.sve_quest_items_ginger_island])
|
|
|
|
|
|
def weapons_count(content: StardewContent):
|
|
weapon_count = 5
|
|
if ModNames.sve in content.registered_packs:
|
|
weapon_count += 1
|
|
return weapon_count
|
|
|
|
|
|
def get_stardrop_classification(options) -> ItemClassification:
|
|
return ItemClassification.progression_skip_balancing \
|
|
if goal_is_perfection(options) or goal_is_stardrops(options) or EatsanityOptionName.shop in options.eatsanity \
|
|
else ItemClassification.useful
|
|
|
|
|
|
def goal_is_perfection(options) -> bool:
|
|
return options.goal == Goal.option_perfection
|
|
|
|
|
|
def goal_is_stardrops(options) -> bool:
|
|
return options.goal == Goal.option_mystery_of_the_stardrops
|