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