Archipelago/worlds/stardew_valley/regions.py

292 lines
16 KiB
Python

from dataclasses import dataclass, field
from enum import IntFlag
from random import Random
from typing import Iterable, Dict, Protocol, Optional, List, Tuple
from BaseClasses import Region, Entrance
from . import options
from .options import StardewOptions
class RegionFactory(Protocol):
def __call__(self, name: str, regions: Iterable[str]) -> Region:
raise NotImplementedError
class RandomizationFlag(IntFlag):
NOT_RANDOMIZED = 0b0
PELICAN_TOWN = 0b11111
NON_PROGRESSION = 0b11110
BUILDINGS = 0b11100
EVERYTHING = 0b11000
CHAOS = 0b10000
@dataclass(frozen=True)
class RegionData:
name: str
exits: List[str] = field(default_factory=list)
@dataclass(frozen=True)
class ConnectionData:
name: str
destination: str
reverse: Optional[str] = None
flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED
def __post_init__(self):
if self.reverse is None and " to " in self.name:
origin, destination = self.name.split(" to ")
super().__setattr__("reverse", f"{destination} to {origin}")
stardew_valley_regions = [
RegionData("Menu", ["To Stardew Valley"]),
RegionData("Stardew Valley", ["To Farmhouse"]),
RegionData("Farmhouse", ["Outside to Farm", "Downstairs to Cellar"]),
RegionData("Cellar"),
RegionData("Farm", ["Farm to Backwoods", "Farm to Bus Stop", "Farm to Forest", "Farm to Farmcave", "Enter Greenhouse",
"Use Desert Obelisk", "Use Island Obelisk"]),
RegionData("Backwoods", ["Backwoods to Mountain"]),
RegionData("Bus Stop", ["Bus Stop to Town", "Take Bus to Desert", "Bus Stop to Tunnel Entrance"]),
RegionData("Forest", ["Forest to Town", "Enter Secret Woods", "Forest to Wizard Tower", "Forest to Marnie's Ranch",
"Forest to Leah's Cottage", "Forest to Sewers"]),
RegionData("Farmcave"),
RegionData("Greenhouse"),
RegionData("Mountain", ["Mountain to Railroad", "Mountain to Tent", "Mountain to Carpenter Shop", "Mountain to The Mines",
"Enter Quarry", "Mountain to Adventurer's Guild", "Mountain to Town"]),
RegionData("Tunnel Entrance", ["Enter Tunnel"]),
RegionData("Tunnel"),
RegionData("Town", ["Town to Community Center", "Town to Beach", "Town to Hospital",
"Town to Pierre's General Store", "Town to Saloon", "Town to Alex's House", "Town to Trailer", "Town to Mayor's Manor",
"Town to Sam's House", "Town to Haley's House", "Town to Sewers", "Town to Clint's Blacksmith", "Town to Museum",
"Town to JojaMart"]),
RegionData("Beach", ["Beach to Willy's Fish Shop", "Enter Elliott's House", "Enter Tide Pools"]),
RegionData("Railroad", ["Enter Bathhouse Entrance", "Enter Witch Warp Cave"]), # "Enter Perfection Cutscene Area"
RegionData("Marnie's Ranch"),
RegionData("Leah's Cottage"),
RegionData("Sewers", ["Enter Mutant Bug Lair"]),
RegionData("Mutant Bug Lair"),
RegionData("Wizard Tower", ["Enter Wizard Basement"]),
RegionData("Wizard Basement"),
RegionData("Tent"),
RegionData("Carpenter Shop", ["Enter Sebastian's Room"]),
RegionData("Sebastian's Room"),
RegionData("Adventurer's Guild"),
RegionData("Community Center",
["Access Crafts Room", "Access Pantry", "Access Fish Tank", "Access Boiler Room", "Access Bulletin Board",
"Access Vault"]),
RegionData("Crafts Room"),
RegionData("Pantry"),
RegionData("Fish Tank"),
RegionData("Boiler Room"),
RegionData("Bulletin Board"),
RegionData("Vault"),
RegionData("Hospital", ["Enter Harvey's Room"]),
RegionData("Harvey's Room"),
RegionData("Pierre's General Store", ["Enter Sunroom"]),
RegionData("Sunroom"),
RegionData("Saloon", ["Play Journey of the Prairie King", "Play Junimo Kart"]),
RegionData("Alex's House"),
RegionData("Trailer"),
RegionData("Mayor's Manor"),
RegionData("Sam's House"),
RegionData("Haley's House"),
RegionData("Clint's Blacksmith"),
RegionData("Museum"),
RegionData("JojaMart"),
RegionData("Willy's Fish Shop"),
RegionData("Elliott's House"),
RegionData("Tide Pools"),
RegionData("Bathhouse Entrance", ["Enter Locker Room"]),
RegionData("Locker Room", ["Enter Public Bath"]),
RegionData("Public Bath"),
RegionData("Witch Warp Cave", ["Enter Witch's Swamp"]),
RegionData("Witch's Swamp"),
RegionData("Quarry", ["Enter Quarry Mine Entrance"]),
RegionData("Quarry Mine Entrance", ["Enter Quarry Mine"]),
RegionData("Quarry Mine"),
RegionData("Secret Woods"),
RegionData("The Desert", ["Enter Skull Cavern Entrance"]),
RegionData("Skull Cavern Entrance", ["Enter Skull Cavern"]),
RegionData("Skull Cavern"),
RegionData("Ginger Island"),
RegionData("JotPK World 1", ["Reach JotPK World 2"]),
RegionData("JotPK World 2", ["Reach JotPK World 3"]),
RegionData("JotPK World 3"),
RegionData("Junimo Kart 1", ["Reach Junimo Kart 2"]),
RegionData("Junimo Kart 2", ["Reach Junimo Kart 3"]),
RegionData("Junimo Kart 3"),
RegionData("The Mines", ["Dig to The Mines - Floor 5", "Dig to The Mines - Floor 10", "Dig to The Mines - Floor 15",
"Dig to The Mines - Floor 20", "Dig to The Mines - Floor 25", "Dig to The Mines - Floor 30",
"Dig to The Mines - Floor 35", "Dig to The Mines - Floor 40", "Dig to The Mines - Floor 45",
"Dig to The Mines - Floor 50", "Dig to The Mines - Floor 55", "Dig to The Mines - Floor 60",
"Dig to The Mines - Floor 65", "Dig to The Mines - Floor 70", "Dig to The Mines - Floor 75",
"Dig to The Mines - Floor 80", "Dig to The Mines - Floor 85", "Dig to The Mines - Floor 90",
"Dig to The Mines - Floor 95", "Dig to The Mines - Floor 100", "Dig to The Mines - Floor 105",
"Dig to The Mines - Floor 110", "Dig to The Mines - Floor 115", "Dig to The Mines - Floor 120"]),
RegionData("The Mines - Floor 5"),
RegionData("The Mines - Floor 10"),
RegionData("The Mines - Floor 15"),
RegionData("The Mines - Floor 20"),
RegionData("The Mines - Floor 25"),
RegionData("The Mines - Floor 30"),
RegionData("The Mines - Floor 35"),
RegionData("The Mines - Floor 40"),
RegionData("The Mines - Floor 45"),
RegionData("The Mines - Floor 50"),
RegionData("The Mines - Floor 55"),
RegionData("The Mines - Floor 60"),
RegionData("The Mines - Floor 65"),
RegionData("The Mines - Floor 70"),
RegionData("The Mines - Floor 75"),
RegionData("The Mines - Floor 80"),
RegionData("The Mines - Floor 85"),
RegionData("The Mines - Floor 90"),
RegionData("The Mines - Floor 95"),
RegionData("The Mines - Floor 100"),
RegionData("The Mines - Floor 105"),
RegionData("The Mines - Floor 110"),
RegionData("The Mines - Floor 115"),
RegionData("The Mines - Floor 120"),
]
# Exists and where they lead
mandatory_connections = [
ConnectionData("To Stardew Valley", "Stardew Valley"),
ConnectionData("To Farmhouse", "Farmhouse"),
ConnectionData("Outside to Farm", "Farm"),
ConnectionData("Downstairs to Cellar", "Cellar"),
ConnectionData("Farm to Backwoods", "Backwoods"),
ConnectionData("Farm to Bus Stop", "Bus Stop"),
ConnectionData("Farm to Forest", "Forest"),
ConnectionData("Farm to Farmcave", "Farmcave", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Greenhouse", "Greenhouse"),
ConnectionData("Use Desert Obelisk", "The Desert"),
ConnectionData("Use Island Obelisk", "Ginger Island"),
ConnectionData("Backwoods to Mountain", "Mountain"),
ConnectionData("Bus Stop to Town", "Town"),
ConnectionData("Bus Stop to Tunnel Entrance", "Tunnel Entrance"),
ConnectionData("Take Bus to Desert", "The Desert"),
ConnectionData("Enter Tunnel", "Tunnel"),
ConnectionData("Forest to Town", "Town"),
ConnectionData("Forest to Wizard Tower", "Wizard Tower", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Wizard Basement", "Wizard Basement"),
ConnectionData("Forest to Marnie's Ranch", "Marnie's Ranch", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Forest to Leah's Cottage", "Leah's Cottage"),
ConnectionData("Enter Secret Woods", "Secret Woods"),
ConnectionData("Forest to Sewers", "Sewers"),
ConnectionData("Town to Sewers", "Sewers"),
ConnectionData("Enter Mutant Bug Lair", "Mutant Bug Lair"),
ConnectionData("Mountain to Railroad", "Railroad"),
ConnectionData("Mountain to Tent", "Tent", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Mountain to Carpenter Shop", "Carpenter Shop", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Sebastian's Room", "Sebastian's Room"),
ConnectionData("Mountain to Adventurer's Guild", "Adventurer's Guild"),
ConnectionData("Enter Quarry", "Quarry"),
ConnectionData("Enter Quarry Mine Entrance", "Quarry Mine Entrance"),
ConnectionData("Enter Quarry Mine", "Quarry Mine"),
ConnectionData("Mountain to Town", "Town"),
ConnectionData("Town to Community Center", "Community Center", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Access Crafts Room", "Crafts Room"),
ConnectionData("Access Pantry", "Pantry"),
ConnectionData("Access Fish Tank", "Fish Tank"),
ConnectionData("Access Boiler Room", "Boiler Room"),
ConnectionData("Access Bulletin Board", "Bulletin Board"),
ConnectionData("Access Vault", "Vault"),
ConnectionData("Town to Hospital", "Hospital", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Enter Harvey's Room", "Harvey's Room"),
ConnectionData("Town to Pierre's General Store", "Pierre's General Store", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Enter Sunroom", "Sunroom"),
ConnectionData("Town to Clint's Blacksmith", "Clint's Blacksmith", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Saloon", "Saloon", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Play Journey of the Prairie King", "JotPK World 1"),
ConnectionData("Reach JotPK World 2", "JotPK World 2"),
ConnectionData("Reach JotPK World 3", "JotPK World 3"),
ConnectionData("Play Junimo Kart", "Junimo Kart 1"),
ConnectionData("Reach Junimo Kart 2", "Junimo Kart 2"),
ConnectionData("Reach Junimo Kart 3", "Junimo Kart 3"),
ConnectionData("Town to Sam's House", "Sam's House", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Haley's House", "Haley's House", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Mayor's Manor", "Mayor's Manor", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Alex's House", "Alex's House", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Trailer", "Trailer", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Museum", "Museum", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to JojaMart", "JojaMart", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Beach", "Beach"),
ConnectionData("Enter Elliott's House", "Elliott's House"),
ConnectionData("Beach to Willy's Fish Shop", "Willy's Fish Shop", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Tide Pools", "Tide Pools"),
ConnectionData("Mountain to The Mines", "The Mines", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Dig to The Mines - Floor 5", "The Mines - Floor 5"),
ConnectionData("Dig to The Mines - Floor 10", "The Mines - Floor 10"),
ConnectionData("Dig to The Mines - Floor 15", "The Mines - Floor 15"),
ConnectionData("Dig to The Mines - Floor 20", "The Mines - Floor 20"),
ConnectionData("Dig to The Mines - Floor 25", "The Mines - Floor 25"),
ConnectionData("Dig to The Mines - Floor 30", "The Mines - Floor 30"),
ConnectionData("Dig to The Mines - Floor 35", "The Mines - Floor 35"),
ConnectionData("Dig to The Mines - Floor 40", "The Mines - Floor 40"),
ConnectionData("Dig to The Mines - Floor 45", "The Mines - Floor 45"),
ConnectionData("Dig to The Mines - Floor 50", "The Mines - Floor 50"),
ConnectionData("Dig to The Mines - Floor 55", "The Mines - Floor 55"),
ConnectionData("Dig to The Mines - Floor 60", "The Mines - Floor 60"),
ConnectionData("Dig to The Mines - Floor 65", "The Mines - Floor 65"),
ConnectionData("Dig to The Mines - Floor 70", "The Mines - Floor 70"),
ConnectionData("Dig to The Mines - Floor 75", "The Mines - Floor 75"),
ConnectionData("Dig to The Mines - Floor 80", "The Mines - Floor 80"),
ConnectionData("Dig to The Mines - Floor 85", "The Mines - Floor 85"),
ConnectionData("Dig to The Mines - Floor 90", "The Mines - Floor 90"),
ConnectionData("Dig to The Mines - Floor 95", "The Mines - Floor 95"),
ConnectionData("Dig to The Mines - Floor 100", "The Mines - Floor 100"),
ConnectionData("Dig to The Mines - Floor 105", "The Mines - Floor 105"),
ConnectionData("Dig to The Mines - Floor 110", "The Mines - Floor 110"),
ConnectionData("Dig to The Mines - Floor 115", "The Mines - Floor 115"),
ConnectionData("Dig to The Mines - Floor 120", "The Mines - Floor 120"),
ConnectionData("Enter Skull Cavern Entrance", "Skull Cavern Entrance"),
ConnectionData("Enter Skull Cavern", "Skull Cavern"),
ConnectionData("Enter Witch Warp Cave", "Witch Warp Cave"),
ConnectionData("Enter Witch's Swamp", "Witch's Swamp"),
ConnectionData("Enter Bathhouse Entrance", "Bathhouse Entrance"),
ConnectionData("Enter Locker Room", "Locker Room"),
ConnectionData("Enter Public Bath", "Public Bath"),
]
def create_regions(region_factory: RegionFactory, random: Random, world_options: StardewOptions) -> Tuple[Iterable[Region], Dict[str, str]]:
regions: Dict[str: Region] = {region.name: region_factory(region.name, region.exits) for region in stardew_valley_regions}
entrances: Dict[str: Entrance] = {entrance.name: entrance
for region in regions.values()
for entrance in region.exits}
connections, randomized_data = randomize_connections(random, world_options)
for connection in connections:
if connection.name not in entrances:
continue
entrances[connection.name].connect(regions[connection.destination])
return regions.values(), randomized_data
def randomize_connections(random: Random, world_options: StardewOptions) -> Tuple[List[ConnectionData], Dict[str, str]]:
connections_to_randomize = []
if world_options[options.EntranceRandomization] == options.EntranceRandomization.option_pelican_town:
connections_to_randomize = [connection for connection in mandatory_connections if RandomizationFlag.PELICAN_TOWN in connection.flag]
elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_non_progression:
connections_to_randomize = [connection for connection in mandatory_connections if RandomizationFlag.NON_PROGRESSION in connection.flag]
random.shuffle(connections_to_randomize)
destination_pool = list(connections_to_randomize)
random.shuffle(destination_pool)
randomized_connections = []
randomized_data = {}
for connection in connections_to_randomize:
destination = destination_pool.pop()
randomized_connections.append(ConnectionData(connection.name, destination.destination, destination.reverse))
randomized_data[connection.name] = destination.name
randomized_data[destination.reverse] = connection.reverse
return mandatory_connections, randomized_data