This is a major update for Stardew Valley, for version 3.x.x. Changes include a large number of new features, including Seasons Randomizer, SeedShuffle, Museumsanity, Friendsanity, Complete Collection Goal, Full House Goal, friendship multiplier Co-authored-by: Jouramie <jouramie@hotmail.com>
		
			
				
	
	
		
			216 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			216 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import csv
 | 
						|
import enum
 | 
						|
from dataclasses import dataclass
 | 
						|
from random import Random
 | 
						|
from typing import Optional, Dict, Protocol, List, FrozenSet
 | 
						|
 | 
						|
from . import options, data
 | 
						|
from .data.fish_data import legendary_fish, special_fish, all_fish
 | 
						|
from .data.museum_data import all_museum_items
 | 
						|
from .data.villagers_data import all_villagers
 | 
						|
 | 
						|
LOCATION_CODE_OFFSET = 717000
 | 
						|
 | 
						|
 | 
						|
class LocationTags(enum.Enum):
 | 
						|
    MANDATORY = enum.auto()
 | 
						|
    BUNDLE = enum.auto()
 | 
						|
    COMMUNITY_CENTER_BUNDLE = enum.auto()
 | 
						|
    CRAFTS_ROOM_BUNDLE = enum.auto()
 | 
						|
    PANTRY_BUNDLE = enum.auto()
 | 
						|
    FISH_TANK_BUNDLE = enum.auto()
 | 
						|
    BOILER_ROOM_BUNDLE = enum.auto()
 | 
						|
    BULLETIN_BOARD_BUNDLE = enum.auto()
 | 
						|
    VAULT_BUNDLE = enum.auto()
 | 
						|
    COMMUNITY_CENTER_ROOM = enum.auto()
 | 
						|
    BACKPACK = enum.auto()
 | 
						|
    TOOL_UPGRADE = enum.auto()
 | 
						|
    HOE_UPGRADE = enum.auto()
 | 
						|
    PICKAXE_UPGRADE = enum.auto()
 | 
						|
    AXE_UPGRADE = enum.auto()
 | 
						|
    WATERING_CAN_UPGRADE = enum.auto()
 | 
						|
    TRASH_CAN_UPGRADE = enum.auto()
 | 
						|
    FISHING_ROD_UPGRADE = enum.auto()
 | 
						|
    THE_MINES_TREASURE = enum.auto()
 | 
						|
    THE_MINES_ELEVATOR = enum.auto()
 | 
						|
    SKILL_LEVEL = enum.auto()
 | 
						|
    FARMING_LEVEL = enum.auto()
 | 
						|
    FISHING_LEVEL = enum.auto()
 | 
						|
    FORAGING_LEVEL = enum.auto()
 | 
						|
    COMBAT_LEVEL = enum.auto()
 | 
						|
    MINING_LEVEL = enum.auto()
 | 
						|
    BUILDING_BLUEPRINT = enum.auto()
 | 
						|
    QUEST = enum.auto()
 | 
						|
    ARCADE_MACHINE = enum.auto()
 | 
						|
    ARCADE_MACHINE_VICTORY = enum.auto()
 | 
						|
    JOTPK = enum.auto()
 | 
						|
    JUNIMO_KART = enum.auto()
 | 
						|
    HELP_WANTED = enum.auto()
 | 
						|
    TRAVELING_MERCHANT = enum.auto()
 | 
						|
    FISHSANITY = enum.auto()
 | 
						|
    MUSEUM_MILESTONES = enum.auto()
 | 
						|
    MUSEUM_DONATIONS = enum.auto()
 | 
						|
    FRIENDSANITY = enum.auto()
 | 
						|
 | 
						|
 | 
						|
@dataclass(frozen=True)
 | 
						|
class LocationData:
 | 
						|
    code_without_offset: Optional[int]
 | 
						|
    region: str
 | 
						|
    name: str
 | 
						|
    tags: FrozenSet[LocationTags] = frozenset()
 | 
						|
 | 
						|
    @property
 | 
						|
    def code(self) -> Optional[int]:
 | 
						|
        return LOCATION_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None
 | 
						|
 | 
						|
 | 
						|
class StardewLocationCollector(Protocol):
 | 
						|
    def __call__(self, name: str, code: Optional[int], region: str) -> None:
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
 | 
						|
def load_location_csv() -> List[LocationData]:
 | 
						|
    try:
 | 
						|
        from importlib.resources import files
 | 
						|
    except ImportError:
 | 
						|
        from importlib_resources import files
 | 
						|
 | 
						|
    with files(data).joinpath("locations.csv").open() as file:
 | 
						|
        reader = csv.DictReader(file)
 | 
						|
        return [LocationData(int(location["id"]) if location["id"] else None,
 | 
						|
                             location["region"],
 | 
						|
                             location["name"],
 | 
						|
                             frozenset(LocationTags[group]
 | 
						|
                                       for group in location["tags"].split(",")
 | 
						|
                                       if group))
 | 
						|
                for location in reader]
 | 
						|
 | 
						|
 | 
						|
events_locations = [
 | 
						|
    LocationData(None, "Stardew Valley", "Succeed Grandpa's Evaluation"),
 | 
						|
    LocationData(None, "Community Center", "Complete Community Center"),
 | 
						|
    LocationData(None, "The Mines - Floor 120", "Reach the Bottom of The Mines"),
 | 
						|
    LocationData(None, "Skull Cavern", "Complete Quest Cryptic Note"),
 | 
						|
    LocationData(None, "Stardew Valley", "Catch Every Fish"),
 | 
						|
    LocationData(None, "Stardew Valley", "Complete the Museum Collection"),
 | 
						|
    LocationData(None, "Stardew Valley", "Full House"),
 | 
						|
]
 | 
						|
 | 
						|
all_locations = load_location_csv() + events_locations
 | 
						|
location_table: Dict[str, LocationData] = {location.name: location for location in all_locations}
 | 
						|
locations_by_tag: Dict[LocationTags, List[LocationData]] = {}
 | 
						|
 | 
						|
 | 
						|
def initialize_groups():
 | 
						|
    for location in all_locations:
 | 
						|
        for tag in location.tags:
 | 
						|
            location_group = locations_by_tag.get(tag, list())
 | 
						|
            location_group.append(location)
 | 
						|
            locations_by_tag[tag] = location_group
 | 
						|
 | 
						|
 | 
						|
initialize_groups()
 | 
						|
 | 
						|
 | 
						|
def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_number_of_quests: int):
 | 
						|
    for i in range(0, desired_number_of_quests):
 | 
						|
        batch = i // 7
 | 
						|
        index_this_batch = i % 7
 | 
						|
        if index_this_batch < 4:
 | 
						|
            randomized_locations.append(
 | 
						|
                location_table[f"Help Wanted: Item Delivery {(batch * 4) + index_this_batch + 1}"])
 | 
						|
        elif index_this_batch == 4:
 | 
						|
            randomized_locations.append(location_table[f"Help Wanted: Fishing {batch + 1}"])
 | 
						|
        elif index_this_batch == 5:
 | 
						|
            randomized_locations.append(location_table[f"Help Wanted: Slay Monsters {batch + 1}"])
 | 
						|
        elif index_this_batch == 6:
 | 
						|
            randomized_locations.append(location_table[f"Help Wanted: Gathering {batch + 1}"])
 | 
						|
 | 
						|
 | 
						|
def extend_fishsanity_locations(randomized_locations: List[LocationData], fishsanity: int, random: Random):
 | 
						|
    prefix = "Fishsanity: "
 | 
						|
    if fishsanity == options.Fishsanity.option_none:
 | 
						|
        return
 | 
						|
    elif fishsanity == options.Fishsanity.option_legendaries:
 | 
						|
        randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish)
 | 
						|
    elif fishsanity == options.Fishsanity.option_special:
 | 
						|
        randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish)
 | 
						|
    elif fishsanity == options.Fishsanity.option_randomized:
 | 
						|
        randomized_locations.extend(location_table[f"{prefix}{fish.name}"]
 | 
						|
                                    for fish in all_fish if random.random() < 0.4)
 | 
						|
    elif fishsanity == options.Fishsanity.option_all:
 | 
						|
        randomized_locations.extend(location_table[f"{prefix}{fish.name}"] for fish in all_fish)
 | 
						|
 | 
						|
 | 
						|
def extend_museumsanity_locations(randomized_locations: List[LocationData], museumsanity: int, random: Random):
 | 
						|
    prefix = "Museumsanity: "
 | 
						|
    if museumsanity == options.Museumsanity.option_none:
 | 
						|
        return
 | 
						|
    elif museumsanity == options.Museumsanity.option_milestones:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.MUSEUM_MILESTONES])
 | 
						|
    elif museumsanity == options.Museumsanity.option_randomized:
 | 
						|
        randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"]
 | 
						|
                                    for museum_item in all_museum_items if random.random() < 0.4)
 | 
						|
    elif museumsanity == options.Museumsanity.option_all:
 | 
						|
        randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"] for museum_item in all_museum_items)
 | 
						|
 | 
						|
 | 
						|
def extend_friendsanity_locations(randomized_locations: List[LocationData], friendsanity: int):
 | 
						|
    if friendsanity == options.Friendsanity.option_none:
 | 
						|
        return
 | 
						|
    exclude_non_bachelors = friendsanity == options.Friendsanity.option_bachelors
 | 
						|
    exclude_locked_villagers = friendsanity == options.Friendsanity.option_starting_npcs or \
 | 
						|
                               friendsanity == options.Friendsanity.option_bachelors
 | 
						|
    exclude_post_marriage_hearts = friendsanity != options.Friendsanity.option_all_with_marriage
 | 
						|
    for villager in all_villagers:
 | 
						|
        if not villager.available and exclude_locked_villagers:
 | 
						|
            continue
 | 
						|
        if not villager.bachelor and exclude_non_bachelors:
 | 
						|
            continue
 | 
						|
        for heart in range(1, 15):
 | 
						|
            if villager.bachelor and exclude_post_marriage_hearts and heart > 8:
 | 
						|
                continue
 | 
						|
            if villager.bachelor or heart < 11:
 | 
						|
                randomized_locations.append(location_table[f"Friendsanity: {villager.name} {heart} <3"])
 | 
						|
    if not exclude_non_bachelors:
 | 
						|
        for heart in range(1, 6):
 | 
						|
            randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"])
 | 
						|
 | 
						|
 | 
						|
def create_locations(location_collector: StardewLocationCollector,
 | 
						|
                     world_options: options.StardewOptions,
 | 
						|
                     random: Random):
 | 
						|
    randomized_locations = []
 | 
						|
 | 
						|
    randomized_locations.extend(locations_by_tag[LocationTags.MANDATORY])
 | 
						|
 | 
						|
    if not world_options[options.BackpackProgression] == options.BackpackProgression.option_vanilla:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.BACKPACK])
 | 
						|
 | 
						|
    if not world_options[options.ToolProgression] == options.ToolProgression.option_vanilla:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE])
 | 
						|
 | 
						|
    if not world_options[options.TheMinesElevatorsProgression] == options.TheMinesElevatorsProgression.option_vanilla:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.THE_MINES_ELEVATOR])
 | 
						|
 | 
						|
    if not world_options[options.SkillProgression] == options.SkillProgression.option_vanilla:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.SKILL_LEVEL])
 | 
						|
 | 
						|
    if not world_options[options.BuildingProgression] == options.BuildingProgression.option_vanilla:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.BUILDING_BLUEPRINT])
 | 
						|
 | 
						|
    if not world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_disabled:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE_VICTORY])
 | 
						|
 | 
						|
    if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
 | 
						|
        randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE])
 | 
						|
 | 
						|
    extend_help_wanted_quests(randomized_locations, world_options[options.HelpWantedLocations])
 | 
						|
    extend_fishsanity_locations(randomized_locations, world_options[options.Fishsanity], random)
 | 
						|
    extend_museumsanity_locations(randomized_locations, world_options[options.Museumsanity], random)
 | 
						|
    extend_friendsanity_locations(randomized_locations, world_options[options.Friendsanity])
 | 
						|
 | 
						|
    for location_data in randomized_locations:
 | 
						|
        location_collector(location_data.name, location_data.code, location_data.region)
 |