Archipelago/worlds/stardew_valley/locations.py

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)