Archipelago/worlds/stardew_valley/regions.py

313 lines
17 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 .data.region_data import SVRegion
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(SVRegion.menu, ["To Stardew Valley"]),
RegionData(SVRegion.stardew_valley, ["To Farmhouse"]),
RegionData(SVRegion.farm_house, ["Outside to Farm", "Downstairs to Cellar"]),
RegionData(SVRegion.cellar),
RegionData(SVRegion.farm,
["Farm to Backwoods", "Farm to Bus Stop", "Farm to Forest", "Farm to Farmcave", "Enter Greenhouse",
"Use Desert Obelisk", "Use Island Obelisk"]),
RegionData(SVRegion.backwoods, ["Backwoods to Mountain"]),
RegionData(SVRegion.bus_stop, ["Bus Stop to Town", "Take Bus to Desert", "Bus Stop to Tunnel Entrance"]),
RegionData(SVRegion.forest, ["Forest to Town", "Enter Secret Woods", "Forest to Wizard Tower", "Forest to Marnie's Ranch",
"Forest to Leah's Cottage", "Forest to Sewers", "Talk to Traveling Merchant"]),
RegionData(SVRegion.traveling_cart),
RegionData(SVRegion.farm_cave),
RegionData(SVRegion.greenhouse),
RegionData(SVRegion.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(SVRegion.tunnel_entrance, ["Enter Tunnel"]),
RegionData(SVRegion.tunnel),
RegionData(SVRegion.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(SVRegion.beach, ["Beach to Willy's Fish Shop", "Enter Elliott's House", "Enter Tide Pools"]),
RegionData(SVRegion.railroad, ["Enter Bathhouse Entrance", "Enter Witch Warp Cave"]), # "Enter Perfection Cutscene Area"
RegionData(SVRegion.ranch),
RegionData(SVRegion.leah_house),
RegionData(SVRegion.sewers, ["Enter Mutant Bug Lair"]),
RegionData(SVRegion.mutant_bug_lair),
RegionData(SVRegion.wizard_tower, ["Enter Wizard Basement"]),
RegionData(SVRegion.wizard_basement),
RegionData(SVRegion.tent),
RegionData(SVRegion.carpenter, ["Enter Sebastian's Room"]),
RegionData(SVRegion.sebastian_room),
RegionData(SVRegion.adventurer_guild),
RegionData(SVRegion.community_center,
["Access Crafts Room", "Access Pantry", "Access Fish Tank", "Access Boiler Room",
"Access Bulletin Board",
"Access Vault"]),
RegionData(SVRegion.crafts_room),
RegionData(SVRegion.pantry),
RegionData(SVRegion.fish_tank),
RegionData(SVRegion.boiler_room),
RegionData(SVRegion.bulletin_board),
RegionData(SVRegion.vault),
RegionData(SVRegion.hospital, ["Enter Harvey's Room"]),
RegionData(SVRegion.harvey_room),
RegionData(SVRegion.pierre_store, ["Enter Sunroom"]),
RegionData(SVRegion.sunroom),
RegionData(SVRegion.saloon, ["Play Journey of the Prairie King", "Play Junimo Kart"]),
RegionData(SVRegion.alex_house),
RegionData(SVRegion.trailer),
RegionData(SVRegion.mayor_house),
RegionData(SVRegion.sam_house),
RegionData(SVRegion.haley_house),
RegionData(SVRegion.blacksmith),
RegionData(SVRegion.museum),
RegionData(SVRegion.jojamart),
RegionData(SVRegion.fish_shop),
RegionData(SVRegion.elliott_house),
RegionData(SVRegion.tide_pools),
RegionData(SVRegion.bathhouse_entrance, ["Enter Locker Room"]),
RegionData(SVRegion.locker_room, ["Enter Public Bath"]),
RegionData(SVRegion.public_bath),
RegionData(SVRegion.witch_warp_cave, ["Enter Witch's Swamp"]),
RegionData(SVRegion.witch_swamp),
RegionData(SVRegion.quarry, ["Enter Quarry Mine Entrance"]),
RegionData(SVRegion.quarry_mine_entrance, ["Enter Quarry Mine"]),
RegionData(SVRegion.quarry_mine),
RegionData(SVRegion.secret_woods),
RegionData(SVRegion.desert, ["Enter Skull Cavern Entrance"]),
RegionData(SVRegion.skull_cavern_entrance, ["Enter Skull Cavern"]),
RegionData(SVRegion.skull_cavern, ["Mine to Skull Cavern Floor 100"]),
RegionData(SVRegion.perfect_skull_cavern),
RegionData(SVRegion.ginger_island),
RegionData(SVRegion.jotpk_world_1, ["Reach JotPK World 2"]),
RegionData(SVRegion.jotpk_world_2, ["Reach JotPK World 3"]),
RegionData(SVRegion.jotpk_world_3),
RegionData(SVRegion.junimo_kart_1, ["Reach Junimo Kart 2"]),
RegionData(SVRegion.junimo_kart_2, ["Reach Junimo Kart 3"]),
RegionData(SVRegion.junimo_kart_3),
RegionData(SVRegion.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(SVRegion.mines_floor_5),
RegionData(SVRegion.mines_floor_10),
RegionData(SVRegion.mines_floor_15),
RegionData(SVRegion.mines_floor_20),
RegionData(SVRegion.mines_floor_25),
RegionData(SVRegion.mines_floor_30),
RegionData(SVRegion.mines_floor_35),
RegionData(SVRegion.mines_floor_40),
RegionData(SVRegion.mines_floor_45),
RegionData(SVRegion.mines_floor_50),
RegionData(SVRegion.mines_floor_55),
RegionData(SVRegion.mines_floor_60),
RegionData(SVRegion.mines_floor_65),
RegionData(SVRegion.mines_floor_70),
RegionData(SVRegion.mines_floor_75),
RegionData(SVRegion.mines_floor_80),
RegionData(SVRegion.mines_floor_85),
RegionData(SVRegion.mines_floor_90),
RegionData(SVRegion.mines_floor_95),
RegionData(SVRegion.mines_floor_100),
RegionData(SVRegion.mines_floor_105),
RegionData(SVRegion.mines_floor_110),
RegionData(SVRegion.mines_floor_115),
RegionData(SVRegion.mines_floor_120),
]
# Exists and where they lead
mandatory_connections = [
ConnectionData("To Stardew Valley", SVRegion.stardew_valley),
ConnectionData("To Farmhouse", SVRegion.farm_house),
ConnectionData("Outside to Farm", SVRegion.farm),
ConnectionData("Downstairs to Cellar", SVRegion.cellar),
ConnectionData("Farm to Backwoods", SVRegion.backwoods),
ConnectionData("Farm to Bus Stop", SVRegion.bus_stop),
ConnectionData("Farm to Forest", SVRegion.forest),
ConnectionData("Farm to Farmcave", SVRegion.farm_cave, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Greenhouse", SVRegion.greenhouse),
ConnectionData("Use Desert Obelisk", SVRegion.desert),
ConnectionData("Use Island Obelisk", SVRegion.ginger_island),
ConnectionData("Backwoods to Mountain", SVRegion.mountain),
ConnectionData("Bus Stop to Town", SVRegion.town),
ConnectionData("Bus Stop to Tunnel Entrance", SVRegion.tunnel_entrance),
ConnectionData("Take Bus to Desert", SVRegion.desert),
ConnectionData("Enter Tunnel", SVRegion.tunnel),
ConnectionData("Forest to Town", SVRegion.town),
ConnectionData("Forest to Wizard Tower", SVRegion.wizard_tower, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Wizard Basement", SVRegion.wizard_basement),
ConnectionData("Forest to Marnie's Ranch", SVRegion.ranch, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Forest to Leah's Cottage", SVRegion.leah_house),
ConnectionData("Enter Secret Woods", SVRegion.secret_woods),
ConnectionData("Forest to Sewers", SVRegion.sewers),
ConnectionData("Talk to Traveling Merchant", SVRegion.traveling_cart),
ConnectionData("Town to Sewers", SVRegion.sewers),
ConnectionData("Enter Mutant Bug Lair", SVRegion.mutant_bug_lair),
ConnectionData("Mountain to Railroad", SVRegion.railroad),
ConnectionData("Mountain to Tent", SVRegion.tent, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Mountain to Carpenter Shop", SVRegion.carpenter, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Sebastian's Room", SVRegion.sebastian_room),
ConnectionData("Mountain to Adventurer's Guild", SVRegion.adventurer_guild),
ConnectionData("Enter Quarry", SVRegion.quarry),
ConnectionData("Enter Quarry Mine Entrance", SVRegion.quarry_mine_entrance),
ConnectionData("Enter Quarry Mine", SVRegion.quarry_mine),
ConnectionData("Mountain to Town", SVRegion.town),
ConnectionData("Town to Community Center", SVRegion.community_center, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Access Crafts Room", SVRegion.crafts_room),
ConnectionData("Access Pantry", SVRegion.pantry),
ConnectionData("Access Fish Tank", SVRegion.fish_tank),
ConnectionData("Access Boiler Room", SVRegion.boiler_room),
ConnectionData("Access Bulletin Board", SVRegion.bulletin_board),
ConnectionData("Access Vault", SVRegion.vault),
ConnectionData("Town to Hospital", SVRegion.hospital, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Enter Harvey's Room", SVRegion.harvey_room),
ConnectionData("Town to Pierre's General Store", SVRegion.pierre_store, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Enter Sunroom", SVRegion.sunroom),
ConnectionData("Town to Clint's Blacksmith", SVRegion.blacksmith, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Saloon", SVRegion.saloon, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Play Journey of the Prairie King", SVRegion.jotpk_world_1),
ConnectionData("Reach JotPK World 2", SVRegion.jotpk_world_2),
ConnectionData("Reach JotPK World 3", SVRegion.jotpk_world_3),
ConnectionData("Play Junimo Kart", SVRegion.junimo_kart_1),
ConnectionData("Reach Junimo Kart 2", SVRegion.junimo_kart_2),
ConnectionData("Reach Junimo Kart 3", SVRegion.junimo_kart_3),
ConnectionData("Town to Sam's House", SVRegion.sam_house, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Haley's House", SVRegion.haley_house, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Mayor's Manor", SVRegion.mayor_house, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Alex's House", SVRegion.alex_house, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Trailer", SVRegion.trailer, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Museum", SVRegion.museum, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to JojaMart", SVRegion.jojamart, flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Beach", SVRegion.beach),
ConnectionData("Enter Elliott's House", SVRegion.elliott_house),
ConnectionData("Beach to Willy's Fish Shop", SVRegion.fish_shop, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Tide Pools", SVRegion.tide_pools),
ConnectionData("Mountain to The Mines", SVRegion.mines, flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Dig to The Mines - Floor 5", SVRegion.mines_floor_5),
ConnectionData("Dig to The Mines - Floor 10", SVRegion.mines_floor_10),
ConnectionData("Dig to The Mines - Floor 15", SVRegion.mines_floor_15),
ConnectionData("Dig to The Mines - Floor 20", SVRegion.mines_floor_20),
ConnectionData("Dig to The Mines - Floor 25", SVRegion.mines_floor_25),
ConnectionData("Dig to The Mines - Floor 30", SVRegion.mines_floor_30),
ConnectionData("Dig to The Mines - Floor 35", SVRegion.mines_floor_35),
ConnectionData("Dig to The Mines - Floor 40", SVRegion.mines_floor_40),
ConnectionData("Dig to The Mines - Floor 45", SVRegion.mines_floor_45),
ConnectionData("Dig to The Mines - Floor 50", SVRegion.mines_floor_50),
ConnectionData("Dig to The Mines - Floor 55", SVRegion.mines_floor_55),
ConnectionData("Dig to The Mines - Floor 60", SVRegion.mines_floor_60),
ConnectionData("Dig to The Mines - Floor 65", SVRegion.mines_floor_65),
ConnectionData("Dig to The Mines - Floor 70", SVRegion.mines_floor_70),
ConnectionData("Dig to The Mines - Floor 75", SVRegion.mines_floor_75),
ConnectionData("Dig to The Mines - Floor 80", SVRegion.mines_floor_80),
ConnectionData("Dig to The Mines - Floor 85", SVRegion.mines_floor_85),
ConnectionData("Dig to The Mines - Floor 90", SVRegion.mines_floor_90),
ConnectionData("Dig to The Mines - Floor 95", SVRegion.mines_floor_95),
ConnectionData("Dig to The Mines - Floor 100", SVRegion.mines_floor_100),
ConnectionData("Dig to The Mines - Floor 105", SVRegion.mines_floor_105),
ConnectionData("Dig to The Mines - Floor 110", SVRegion.mines_floor_110),
ConnectionData("Dig to The Mines - Floor 115", SVRegion.mines_floor_115),
ConnectionData("Dig to The Mines - Floor 120", SVRegion.mines_floor_120),
ConnectionData("Enter Skull Cavern Entrance", SVRegion.skull_cavern_entrance),
ConnectionData("Enter Skull Cavern", SVRegion.skull_cavern),
ConnectionData("Mine to Skull Cavern Floor 100", SVRegion.perfect_skull_cavern),
ConnectionData("Enter Witch Warp Cave", SVRegion.witch_warp_cave),
ConnectionData("Enter Witch's Swamp", SVRegion.witch_swamp),
ConnectionData("Enter Bathhouse Entrance", SVRegion.bathhouse_entrance),
ConnectionData("Enter Locker Room", SVRegion.locker_room),
ConnectionData("Enter Public Bath", SVRegion.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