176 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			176 lines
		
	
	
		
			7.2 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 .fish_data import legendary_fish, special_fish, all_fish_items
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@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", "Summer"),
							 | 
						||
| 
								 | 
							
								    LocationData(None, "Stardew Valley", "Fall"),
							 | 
						||
| 
								 | 
							
								    LocationData(None, "Stardew Valley", "Winter"),
							 | 
						||
| 
								 | 
							
								    LocationData(None, "Stardew Valley", "Year Two"),
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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_random_selection:
							 | 
						||
| 
								 | 
							
								        randomized_locations.extend(location_table[f"{prefix}{fish.name}"]
							 | 
						||
| 
								 | 
							
								                                    for fish in all_fish_items 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_items)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for location_data in randomized_locations:
							 | 
						||
| 
								 | 
							
								        location_collector(location_data.name, location_data.code, location_data.region)
							 |