 5eadbc9840
			
		
	
	5eadbc9840
	
	
	
		
			
			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)
 |