527 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			527 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
import csv
 | 
						|
import enum
 | 
						|
import logging
 | 
						|
from dataclasses import dataclass, field
 | 
						|
from pathlib import Path
 | 
						|
from random import Random
 | 
						|
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 .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 .strings.ap_names.buff_names import Buff
 | 
						|
 | 
						|
ITEM_CODE_OFFSET = 717000
 | 
						|
 | 
						|
logger = logging.getLogger(__name__)
 | 
						|
world_folder = Path(__file__).parent
 | 
						|
 | 
						|
 | 
						|
class Group(enum.Enum):
 | 
						|
    RESOURCE_PACK = enum.auto()
 | 
						|
    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()
 | 
						|
    PROGRESSIVE_TOOLS = enum.auto()
 | 
						|
    SKILL_LEVEL_UP = enum.auto()
 | 
						|
    ARCADE_MACHINE_BUFFS = enum.auto()
 | 
						|
    GALAXY_WEAPONS = enum.auto()
 | 
						|
    BASE_RESOURCE = enum.auto()
 | 
						|
    WARP_TOTEM = enum.auto()
 | 
						|
    GEODE = enum.auto()
 | 
						|
    ORE = enum.auto()
 | 
						|
    FERTILIZER = enum.auto()
 | 
						|
    SEED = enum.auto()
 | 
						|
    CROPSANITY = enum.auto()
 | 
						|
    FISHING_RESOURCE = enum.auto()
 | 
						|
    SEASON = enum.auto()
 | 
						|
    TRAVELING_MERCHANT_DAY = enum.auto()
 | 
						|
    MUSEUM = enum.auto()
 | 
						|
    FRIENDSANITY = enum.auto()
 | 
						|
    FESTIVAL = enum.auto()
 | 
						|
    RARECROW = enum.auto()
 | 
						|
    TRAP = enum.auto()
 | 
						|
    MAXIMUM_ONE = enum.auto()
 | 
						|
    EXACTLY_TWO = enum.auto()
 | 
						|
    DEPRECATED = enum.auto()
 | 
						|
    RESOURCE_PACK_USEFUL = enum.auto()
 | 
						|
    SPECIAL_ORDER_BOARD = enum.auto()
 | 
						|
    SPECIAL_ORDER_QI = enum.auto()
 | 
						|
    BABY = enum.auto()
 | 
						|
    GINGER_ISLAND = enum.auto()
 | 
						|
    WALNUT_PURCHASE = enum.auto()
 | 
						|
    TV_CHANNEL = enum.auto()
 | 
						|
    MAGIC_SPELL = enum.auto()
 | 
						|
 | 
						|
 | 
						|
@dataclass(frozen=True)
 | 
						|
class ItemData:
 | 
						|
    code_without_offset: Optional[int]
 | 
						|
    name: str
 | 
						|
    classification: ItemClassification
 | 
						|
    mod_name: Optional[str] = None
 | 
						|
    groups: Set[Group] = field(default_factory=frozenset)
 | 
						|
 | 
						|
    def __post_init__(self):
 | 
						|
        if not isinstance(self.groups, frozenset):
 | 
						|
            super().__setattr__("groups", frozenset(self.groups))
 | 
						|
 | 
						|
    @property
 | 
						|
    def code(self):
 | 
						|
        return ITEM_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None
 | 
						|
 | 
						|
    def has_any_group(self, *group: Group) -> bool:
 | 
						|
        groups = set(group)
 | 
						|
        return bool(groups.intersection(self.groups))
 | 
						|
 | 
						|
 | 
						|
class StardewItemFactory(Protocol):
 | 
						|
    def __call__(self, name: Union[str, ItemData]) -> Item:
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
 | 
						|
def load_item_csv():
 | 
						|
    try:
 | 
						|
        from importlib.resources import files
 | 
						|
    except ImportError:
 | 
						|
        from importlib_resources import files  # noqa
 | 
						|
 | 
						|
    items = []
 | 
						|
    with files(data).joinpath("items.csv").open() as file:
 | 
						|
        item_reader = csv.DictReader(file)
 | 
						|
        for item in item_reader:
 | 
						|
            id = int(item["id"]) if item["id"] else None
 | 
						|
            classification = ItemClassification[item["classification"]]
 | 
						|
            groups = {Group[group] for group in item["groups"].split(",") if group}
 | 
						|
            mod_name = str(item["mod_name"]) if item["mod_name"] else None
 | 
						|
            items.append(ItemData(id, item["name"], classification, mod_name, groups))
 | 
						|
    return items
 | 
						|
 | 
						|
 | 
						|
events = [
 | 
						|
    ItemData(None, "Victory", ItemClassification.progression),
 | 
						|
    ItemData(None, "Month End", ItemClassification.progression),
 | 
						|
]
 | 
						|
 | 
						|
all_items: List[ItemData] = load_item_csv() + events
 | 
						|
item_table: Dict[str, ItemData] = {}
 | 
						|
items_by_group: Dict[Group, List[ItemData]] = {}
 | 
						|
 | 
						|
 | 
						|
def initialize_groups():
 | 
						|
    for item in all_items:
 | 
						|
        for group in item.groups:
 | 
						|
            item_group = items_by_group.get(group, list())
 | 
						|
            item_group.append(item)
 | 
						|
            items_by_group[group] = item_group
 | 
						|
 | 
						|
 | 
						|
def initialize_item_table():
 | 
						|
    item_table.update({item.name: item for item in all_items})
 | 
						|
 | 
						|
 | 
						|
initialize_item_table()
 | 
						|
initialize_groups()
 | 
						|
 | 
						|
 | 
						|
def create_items(item_factory: StardewItemFactory, 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)
 | 
						|
 | 
						|
    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")
 | 
						|
 | 
						|
    unique_filler_items = create_unique_filler_items(item_factory, options, random, locations_count - len(items))
 | 
						|
    items += unique_filler_items
 | 
						|
    logger.debug(f"Created {len(unique_filler_items)} unique filler items")
 | 
						|
 | 
						|
    resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items, locations_count)
 | 
						|
    items += resource_pack_items
 | 
						|
    logger.debug(f"Created {len(resource_pack_items)} resource packs")
 | 
						|
 | 
						|
    return 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])
 | 
						|
 | 
						|
    create_backpack_items(item_factory, options, items)
 | 
						|
    create_mine_rewards(item_factory, items, random)
 | 
						|
    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("Beach Bridge"))
 | 
						|
    items.append(item_factory("Dark Talisman"))
 | 
						|
    create_tv_channels(item_factory, items)
 | 
						|
    create_special_quest_rewards(item_factory, 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_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_magic_mod_spells(item_factory, options, items)
 | 
						|
 | 
						|
    return items
 | 
						|
 | 
						|
 | 
						|
def create_backpack_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    if (options.backpack_progression == BackpackProgression.option_progressive or
 | 
						|
            options.backpack_progression == BackpackProgression.option_early_progressive):
 | 
						|
        items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2)
 | 
						|
        if ModNames.big_backpack in options.mods:
 | 
						|
            items.append(item_factory("Progressive Backpack"))
 | 
						|
 | 
						|
 | 
						|
def create_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_elevators(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    if options.elevator_progression == ElevatorProgression.option_vanilla:
 | 
						|
        return
 | 
						|
 | 
						|
    items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24])
 | 
						|
    if ModNames.deepwoods in options.mods:
 | 
						|
        items.extend([item_factory(item) for item in ["Progressive Woods Obelisk Sigils"] * 10])
 | 
						|
    if ModNames.skull_cavern_elevator in options.mods:
 | 
						|
        items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8])
 | 
						|
 | 
						|
 | 
						|
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)
 | 
						|
    items.append(item_factory("Golden Scythe"))
 | 
						|
 | 
						|
 | 
						|
def create_skills(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    if options.skill_progression == SkillProgression.option_progressive:
 | 
						|
        for item in items_by_group[Group.SKILL_LEVEL_UP]:
 | 
						|
            if item.mod_name not in options.mods and item.mod_name is not None:
 | 
						|
                continue
 | 
						|
            items.extend(item_factory(item) for item in [item.name] * 10)
 | 
						|
 | 
						|
 | 
						|
def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    items.append(item_factory("Earth Obelisk"))
 | 
						|
    items.append(item_factory("Water Obelisk"))
 | 
						|
    items.append(item_factory("Desert Obelisk"))
 | 
						|
    items.append(item_factory("Junimo Hut"))
 | 
						|
    items.append(item_factory("Gold Clock"))
 | 
						|
    if options.exclude_ginger_island == ExcludeGingerIsland.option_false:
 | 
						|
        items.append(item_factory("Island Obelisk"))
 | 
						|
    if ModNames.deepwoods in options.mods:
 | 
						|
        items.append(item_factory("Woods Obelisk"))
 | 
						|
 | 
						|
 | 
						|
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"))
 | 
						|
 | 
						|
 | 
						|
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_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
 | 
						|
    if options.fishsanity != Fishsanity.option_none:
 | 
						|
        items.append(item_factory("Stardrop"))  #Master Angler Stardrop
 | 
						|
    if ModNames.deepwoods in options.mods:
 | 
						|
        items.append(item_factory("Stardrop"))  # Petting the Unicorn
 | 
						|
 | 
						|
 | 
						|
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("Ancient Seeds Recipe"))
 | 
						|
    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"))
 | 
						|
 | 
						|
 | 
						|
def create_friendsanity_items(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    if options.friendsanity == Friendsanity.option_none:
 | 
						|
        return
 | 
						|
    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
 | 
						|
    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
 | 
						|
        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:
 | 
						|
            continue
 | 
						|
        heart_cap = 8 if villager.bachelor else 10
 | 
						|
        if include_post_marriage_hearts and villager.bachelor:
 | 
						|
            heart_cap = 14
 | 
						|
        for heart in range(1, 15):
 | 
						|
            if heart > heart_cap:
 | 
						|
                break
 | 
						|
            if heart % heart_size == 0 or heart == heart_cap:
 | 
						|
                items.append(item_factory(f"{villager.name} <3"))
 | 
						|
    if not exclude_non_bachelors:
 | 
						|
        for heart in range(1, 6):
 | 
						|
            if heart % heart_size == 0 or heart == 5:
 | 
						|
                items.append(item_factory(f"Pet <3"))
 | 
						|
 | 
						|
 | 
						|
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_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)
 | 
						|
 | 
						|
 | 
						|
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
 | 
						|
    items.extend([*(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]),
 | 
						|
                  *(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6),
 | 
						|
                  *(item_factory(item) for item in ["Traveling Merchant Discount"] * 8)])
 | 
						|
 | 
						|
 | 
						|
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, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    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]
 | 
						|
    items.extend(seed_items)
 | 
						|
 | 
						|
 | 
						|
def create_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    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")])
 | 
						|
 | 
						|
 | 
						|
def create_walnut_purchase_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
 | 
						|
        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_disabled:
 | 
						|
        return
 | 
						|
 | 
						|
    items.extend([item_factory(item) for item in items_by_group[Group.SPECIAL_ORDER_BOARD]])
 | 
						|
 | 
						|
 | 
						|
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):
 | 
						|
        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_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_filler_festival_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions) -> List[Item]:
 | 
						|
    if options.festival_locations == FestivalLocations.option_disabled:
 | 
						|
        return []
 | 
						|
 | 
						|
    return [item_factory(item) for item in items_by_group[Group.FESTIVAL] if
 | 
						|
            item.classification == ItemClassification.filler]
 | 
						|
 | 
						|
 | 
						|
def create_magic_mod_spells(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]):
 | 
						|
    if ModNames.magic not in options.mods:
 | 
						|
        return []
 | 
						|
    items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]])
 | 
						|
 | 
						|
 | 
						|
def create_unique_filler_items(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
 | 
						|
                               available_item_slots: int) -> List[Item]:
 | 
						|
    items = []
 | 
						|
 | 
						|
    items.extend(create_filler_festival_rewards(item_factory, options))
 | 
						|
 | 
						|
    if len(items) > available_item_slots:
 | 
						|
        items = random.sample(items, available_item_slots)
 | 
						|
    return items
 | 
						|
 | 
						|
 | 
						|
def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random,
 | 
						|
                                       items_already_added: List[Item],
 | 
						|
                                       number_locations: int) -> List[Item]:
 | 
						|
    include_traps = options.trap_items != TrapItems.option_no_traps
 | 
						|
    all_filler_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK]]
 | 
						|
    all_filler_packs.extend(items_by_group[Group.TRASH])
 | 
						|
    if include_traps:
 | 
						|
        all_filler_packs.extend(items_by_group[Group.TRAP])
 | 
						|
    items_already_added_names = [item.name for item in items_already_added]
 | 
						|
    useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL]
 | 
						|
                             if pack.name not in items_already_added_names]
 | 
						|
    trap_items = [pack for pack in items_by_group[Group.TRAP]
 | 
						|
                  if pack.name not in items_already_added_names and
 | 
						|
                  (pack.mod_name is None or pack.mod_name in options.mods)]
 | 
						|
 | 
						|
    priority_filler_items = []
 | 
						|
    priority_filler_items.extend(useful_resource_packs)
 | 
						|
    if include_traps:
 | 
						|
        priority_filler_items.extend(trap_items)
 | 
						|
 | 
						|
    all_filler_packs = remove_excluded_packs(all_filler_packs, options)
 | 
						|
    priority_filler_items = remove_excluded_packs(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)]
 | 
						|
        return chosen_priority_items
 | 
						|
 | 
						|
    items = []
 | 
						|
    chosen_priority_items = [item_factory(resource_pack) 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]]
 | 
						|
 | 
						|
    while required_resource_pack > 0:
 | 
						|
        resource_pack = random.choice(all_filler_packs)
 | 
						|
        exactly_2 = Group.EXACTLY_TWO in resource_pack.groups
 | 
						|
        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))
 | 
						|
        required_resource_pack -= 1
 | 
						|
        if exactly_2:
 | 
						|
            items.append(item_factory(resource_pack))
 | 
						|
            required_resource_pack -= 1
 | 
						|
        if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups:
 | 
						|
            all_filler_packs.remove(resource_pack)
 | 
						|
 | 
						|
    return items
 | 
						|
 | 
						|
 | 
						|
def remove_excluded_packs(packs, options: StardewValleyOptions):
 | 
						|
    included_packs = [pack for pack in packs if Group.DEPRECATED not in pack.groups]
 | 
						|
    if options.exclude_ginger_island == ExcludeGingerIsland.option_true:
 | 
						|
        included_packs = [pack for pack in included_packs if Group.GINGER_ISLAND not in pack.groups]
 | 
						|
    return included_packs
 |