diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 9a05c04d..ea0ce9e1 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,9 +1,10 @@ import logging import typing from random import Random -from typing import Dict, Any, Iterable, Optional, List, TextIO +from typing import Dict, Any, Optional, List, TextIO -from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState +import entrance_rando +from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld from .bundles.bundle_room import BundleRoom @@ -21,7 +22,7 @@ from .options.forced_options import force_change_options_if_incompatible from .options.option_groups import sv_option_groups from .options.presets import sv_options_presets from .options.worlds_group import apply_most_restrictive_options -from .regions import create_regions +from .regions import create_regions, prepare_mod_data from .rules import set_rules from .stardew_rule import True_, StardewRule, HasProgressionPercent from .strings.ap_names.event_names import Event @@ -124,18 +125,13 @@ class StardewValleyWorld(World): self.content = create_content(self.options) def create_regions(self): - def create_region(name: str, exits: Iterable[str]) -> Region: - region = Region(name, self.player, self.multiworld) - region.exits = [Entrance(self.player, exit_name, region) for exit_name in exits] - return region + def create_region(name: str) -> Region: + return Region(name, self.player, self.multiworld) - world_regions, world_entrances, self.randomized_entrances = create_regions(create_region, self.random, self.options, self.content) + world_regions = create_regions(create_region, self.options, self.content) self.logic = StardewLogic(self.player, self.options, self.content, world_regions.keys()) - self.modified_bundles = get_all_bundles(self.random, - self.logic, - self.content, - self.options) + self.modified_bundles = get_all_bundles(self.random, self.logic, self.content, self.options) def add_location(name: str, code: Optional[int], region: str): region: Region = world_regions[region] @@ -308,6 +304,11 @@ class StardewValleyWorld(World): def set_rules(self): set_rules(self) + def connect_entrances(self) -> None: + no_target_groups = {0: [0]} + placement = entrance_rando.randomize_entrances(self, coupled=True, target_group_lookup=no_target_groups) + self.randomized_entrances = prepare_mod_data(placement) + def generate_basic(self): pass diff --git a/worlds/stardew_valley/content/mods/sve.py b/worlds/stardew_valley/content/mods/sve.py index 12b3e355..2c9edc81 100644 --- a/worlds/stardew_valley/content/mods/sve.py +++ b/worlds/stardew_valley/content/mods/sve.py @@ -24,6 +24,9 @@ from ...strings.skill_names import Skill from ...strings.tool_names import Tool, ToolMaterial from ...strings.villager_names import ModNPC +# Used to adapt content not yet moved to content packs to easily detect when SVE and Ginger Island are both enabled. +SVE_GINGER_ISLAND_PACK = ModNames.sve + "+" + ginger_island_content_pack.name + class SVEContentPack(ContentPack): @@ -67,6 +70,10 @@ class SVEContentPack(ContentPack): content.game_items.pop(SVESeed.slime) content.game_items.pop(SVEFruit.slime_berry) + def finalize_hook(self, content: StardewContent): + if ginger_island_content_pack.name in content.registered_packs: + content.registered_packs.add(SVE_GINGER_ISLAND_PACK) + register_mod_content_pack(SVEContentPack( ModNames.sve, @@ -80,8 +87,9 @@ register_mod_content_pack(SVEContentPack( ModEdible.lightning_elixir: (ShopSource(money_price=12000, shop_region=SVERegion.galmoran_outpost),), ModEdible.barbarian_elixir: (ShopSource(money_price=22000, shop_region=SVERegion.galmoran_outpost),), ModEdible.gravity_elixir: (ShopSource(money_price=4000, shop_region=SVERegion.galmoran_outpost),), - SVEMeal.grampleton_orange_chicken: ( - ShopSource(money_price=650, shop_region=Region.saloon, other_requirements=(RelationshipRequirement(ModNPC.sophia, 6),)),), + SVEMeal.grampleton_orange_chicken: (ShopSource(money_price=650, + shop_region=Region.saloon, + other_requirements=(RelationshipRequirement(ModNPC.sophia, 6),)),), ModEdible.hero_elixir: (ShopSource(money_price=8000, shop_region=SVERegion.isaac_shop),), ModEdible.aegis_elixir: (ShopSource(money_price=28000, shop_region=SVERegion.galmoran_outpost),), SVEBeverage.sports_drink: (ShopSource(money_price=750, shop_region=Region.hospital),), @@ -118,8 +126,8 @@ register_mod_content_pack(SVEContentPack( ModLoot.green_mushroom: (ForagingSource(regions=(SVERegion.highlands_pond,), seasons=Season.not_winter),), ModLoot.ornate_treasure_chest: (ForagingSource(regions=(SVERegion.highlands_outside,), - other_requirements=( - CombatRequirement(Performance.galaxy), ToolRequirement(Tool.axe, ToolMaterial.iron))),), + other_requirements=(CombatRequirement(Performance.galaxy), + ToolRequirement(Tool.axe, ToolMaterial.iron))),), ModLoot.swirl_stone: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.galaxy),)),), ModLoot.void_soul: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.good),)),), SVEForage.winter_star_rose: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.winter,)),), @@ -139,8 +147,9 @@ register_mod_content_pack(SVEContentPack( SVEForage.thistle: (ForagingSource(regions=(SVERegion.summit,)),), ModLoot.void_pebble: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.great),)),), ModLoot.void_shard: (ForagingSource(regions=(SVERegion.crimson_badlands,), - other_requirements=( - CombatRequirement(Performance.galaxy), SkillRequirement(Skill.combat, 10), YearRequirement(3),)),), + other_requirements=(CombatRequirement(Performance.galaxy), + SkillRequirement(Skill.combat, 10), + YearRequirement(3),)),), SVEWaterItem.dulse_seaweed: (ForagingSource(regions=(Region.beach,), other_requirements=(FishingRequirement(Region.beach),)),), # Fable Reef diff --git a/worlds/stardew_valley/mods/logic/sve_logic.py b/worlds/stardew_valley/mods/logic/sve_logic.py index 7f0c12bc..03f1737c 100644 --- a/worlds/stardew_valley/mods/logic/sve_logic.py +++ b/worlds/stardew_valley/mods/logic/sve_logic.py @@ -1,8 +1,7 @@ -from ..mod_regions import SVERegion from ...logic.base_logic import BaseLogicMixin, BaseLogic from ...strings.ap_names.mods.mod_items import SVELocation, SVERunes, SVEQuestItem from ...strings.quest_names import Quest, ModQuest -from ...strings.region_names import Region +from ...strings.region_names import Region, SVERegion from ...strings.tool_names import Tool, ToolMaterial from ...strings.wallet_item_names import Wallet diff --git a/worlds/stardew_valley/mods/mod_regions.py b/worlds/stardew_valley/mods/region_data.py similarity index 61% rename from worlds/stardew_valley/mods/mod_regions.py rename to worlds/stardew_valley/mods/region_data.py index a402ba60..5dc4a3df 100644 --- a/worlds/stardew_valley/mods/mod_regions.py +++ b/worlds/stardew_valley/mods/region_data.py @@ -1,15 +1,14 @@ -from typing import Dict, List - from .mod_data import ModNames -from ..region_classes import RegionData, ConnectionData, ModificationFlag, RandomizationFlag, ModRegionData +from ..content.mods.sve import SVE_GINGER_ISLAND_PACK +from ..regions.model import RegionData, ConnectionData, MergeFlag, RandomizationFlag, ModRegionsData from ..strings.entrance_names import Entrance, DeepWoodsEntrance, EugeneEntrance, LaceyEntrance, BoardingHouseEntrance, \ JasperEntrance, AlecEntrance, YobaEntrance, JunaEntrance, MagicEntrance, AyeishaEntrance, RileyEntrance, SVEEntrance, AlectoEntrance from ..strings.region_names import Region, DeepWoodsRegion, EugeneRegion, JasperRegion, BoardingHouseRegion, \ AlecRegion, YobaRegion, JunaRegion, MagicRegion, AyeishaRegion, RileyRegion, SVERegion, AlectoRegion, LaceyRegion deep_woods_regions = [ - RegionData(Region.farm, [DeepWoodsEntrance.use_woods_obelisk]), - RegionData(DeepWoodsRegion.woods_obelisk_menu, [DeepWoodsEntrance.deep_woods_depth_1, + RegionData(Region.farm, (DeepWoodsEntrance.use_woods_obelisk,)), + RegionData(DeepWoodsRegion.woods_obelisk_menu, (DeepWoodsEntrance.deep_woods_depth_1, DeepWoodsEntrance.deep_woods_depth_10, DeepWoodsEntrance.deep_woods_depth_20, DeepWoodsEntrance.deep_woods_depth_30, @@ -19,9 +18,9 @@ deep_woods_regions = [ DeepWoodsEntrance.deep_woods_depth_70, DeepWoodsEntrance.deep_woods_depth_80, DeepWoodsEntrance.deep_woods_depth_90, - DeepWoodsEntrance.deep_woods_depth_100]), - RegionData(Region.secret_woods, [DeepWoodsEntrance.secret_woods_to_deep_woods]), - RegionData(DeepWoodsRegion.main_lichtung, [DeepWoodsEntrance.deep_woods_house]), + DeepWoodsEntrance.deep_woods_depth_100)), + RegionData(Region.secret_woods, (DeepWoodsEntrance.secret_woods_to_deep_woods,)), + RegionData(DeepWoodsRegion.main_lichtung, (DeepWoodsEntrance.deep_woods_house,)), RegionData(DeepWoodsRegion.abandoned_home), RegionData(DeepWoodsRegion.floor_10), RegionData(DeepWoodsRegion.floor_20), @@ -32,14 +31,13 @@ deep_woods_regions = [ RegionData(DeepWoodsRegion.floor_70), RegionData(DeepWoodsRegion.floor_80), RegionData(DeepWoodsRegion.floor_90), - RegionData(DeepWoodsRegion.floor_100) + RegionData(DeepWoodsRegion.floor_100), ] deep_woods_entrances = [ ConnectionData(DeepWoodsEntrance.use_woods_obelisk, DeepWoodsRegion.woods_obelisk_menu), ConnectionData(DeepWoodsEntrance.secret_woods_to_deep_woods, DeepWoodsRegion.main_lichtung), - ConnectionData(DeepWoodsEntrance.deep_woods_house, DeepWoodsRegion.abandoned_home, - flag=RandomizationFlag.NON_PROGRESSION), + ConnectionData(DeepWoodsEntrance.deep_woods_house, DeepWoodsRegion.abandoned_home, flag=RandomizationFlag.BUILDINGS), ConnectionData(DeepWoodsEntrance.deep_woods_depth_1, DeepWoodsRegion.main_lichtung), ConnectionData(DeepWoodsEntrance.deep_woods_depth_10, DeepWoodsRegion.floor_10), ConnectionData(DeepWoodsEntrance.deep_woods_depth_20, DeepWoodsRegion.floor_20), @@ -50,165 +48,166 @@ deep_woods_entrances = [ ConnectionData(DeepWoodsEntrance.deep_woods_depth_70, DeepWoodsRegion.floor_70), ConnectionData(DeepWoodsEntrance.deep_woods_depth_80, DeepWoodsRegion.floor_80), ConnectionData(DeepWoodsEntrance.deep_woods_depth_90, DeepWoodsRegion.floor_90), - ConnectionData(DeepWoodsEntrance.deep_woods_depth_100, DeepWoodsRegion.floor_100) + ConnectionData(DeepWoodsEntrance.deep_woods_depth_100, DeepWoodsRegion.floor_100), ] eugene_regions = [ - RegionData(Region.forest, [EugeneEntrance.forest_to_garden]), - RegionData(EugeneRegion.eugene_garden, [EugeneEntrance.garden_to_bedroom]), - RegionData(EugeneRegion.eugene_bedroom) + RegionData(Region.forest, (EugeneEntrance.forest_to_garden,)), + RegionData(EugeneRegion.eugene_garden, (EugeneEntrance.garden_to_bedroom,)), + RegionData(EugeneRegion.eugene_bedroom), ] eugene_entrances = [ ConnectionData(EugeneEntrance.forest_to_garden, EugeneRegion.eugene_garden, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(EugeneEntrance.garden_to_bedroom, EugeneRegion.eugene_bedroom, flag=RandomizationFlag.BUILDINGS) + ConnectionData(EugeneEntrance.garden_to_bedroom, EugeneRegion.eugene_bedroom, flag=RandomizationFlag.BUILDINGS), ] magic_regions = [ - RegionData(Region.pierre_store, [MagicEntrance.store_to_altar]), - RegionData(MagicRegion.altar) + RegionData(Region.pierre_store, (MagicEntrance.store_to_altar,)), + RegionData(MagicRegion.altar), ] magic_entrances = [ - ConnectionData(MagicEntrance.store_to_altar, MagicRegion.altar, flag=RandomizationFlag.NOT_RANDOMIZED) + ConnectionData(MagicEntrance.store_to_altar, MagicRegion.altar, flag=RandomizationFlag.NOT_RANDOMIZED), ] jasper_regions = [ - RegionData(Region.museum, [JasperEntrance.museum_to_bedroom]), - RegionData(JasperRegion.jasper_bedroom) + RegionData(Region.museum, (JasperEntrance.museum_to_bedroom,)), + RegionData(JasperRegion.jasper_bedroom), ] jasper_entrances = [ - ConnectionData(JasperEntrance.museum_to_bedroom, JasperRegion.jasper_bedroom, flag=RandomizationFlag.BUILDINGS) + ConnectionData(JasperEntrance.museum_to_bedroom, JasperRegion.jasper_bedroom, flag=RandomizationFlag.BUILDINGS), ] alec_regions = [ - RegionData(Region.forest, [AlecEntrance.forest_to_petshop]), - RegionData(AlecRegion.pet_store, [AlecEntrance.petshop_to_bedroom]), - RegionData(AlecRegion.alec_bedroom) + RegionData(Region.forest, (AlecEntrance.forest_to_petshop,)), + RegionData(AlecRegion.pet_store, (AlecEntrance.petshop_to_bedroom,)), + RegionData(AlecRegion.alec_bedroom), ] alec_entrances = [ ConnectionData(AlecEntrance.forest_to_petshop, AlecRegion.pet_store, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(AlecEntrance.petshop_to_bedroom, AlecRegion.alec_bedroom, flag=RandomizationFlag.BUILDINGS) + ConnectionData(AlecEntrance.petshop_to_bedroom, AlecRegion.alec_bedroom, flag=RandomizationFlag.BUILDINGS), ] yoba_regions = [ - RegionData(Region.secret_woods, [YobaEntrance.secret_woods_to_clearing]), - RegionData(YobaRegion.yoba_clearing) + RegionData(Region.secret_woods, (YobaEntrance.secret_woods_to_clearing,)), + RegionData(YobaRegion.yoba_clearing), ] yoba_entrances = [ - ConnectionData(YobaEntrance.secret_woods_to_clearing, YobaRegion.yoba_clearing, flag=RandomizationFlag.BUILDINGS) + ConnectionData(YobaEntrance.secret_woods_to_clearing, YobaRegion.yoba_clearing, flag=RandomizationFlag.BUILDINGS), ] juna_regions = [ - RegionData(Region.forest, [JunaEntrance.forest_to_juna_cave]), - RegionData(JunaRegion.juna_cave) + RegionData(Region.forest, (JunaEntrance.forest_to_juna_cave,)), + RegionData(JunaRegion.juna_cave), ] juna_entrances = [ ConnectionData(JunaEntrance.forest_to_juna_cave, JunaRegion.juna_cave, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA) + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), ] ayeisha_regions = [ - RegionData(Region.bus_stop, [AyeishaEntrance.bus_stop_to_mail_van]), - RegionData(AyeishaRegion.mail_van) + RegionData(Region.bus_stop, (AyeishaEntrance.bus_stop_to_mail_van,)), + RegionData(AyeishaRegion.mail_van), ] ayeisha_entrances = [ ConnectionData(AyeishaEntrance.bus_stop_to_mail_van, AyeishaRegion.mail_van, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA) + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), ] riley_regions = [ - RegionData(Region.town, [RileyEntrance.town_to_riley]), - RegionData(RileyRegion.riley_house) + RegionData(Region.town, (RileyEntrance.town_to_riley,)), + RegionData(RileyRegion.riley_house), ] riley_entrances = [ ConnectionData(RileyEntrance.town_to_riley, RileyRegion.riley_house, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA) + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), ] -stardew_valley_expanded_regions = [ - RegionData(Region.backwoods, [SVEEntrance.backwoods_to_grove]), - RegionData(SVERegion.enchanted_grove, [SVEEntrance.grove_to_outpost_warp, SVEEntrance.grove_to_wizard_warp, +sve_main_land_regions = [ + RegionData(Region.backwoods, (SVEEntrance.backwoods_to_grove,)), + RegionData(SVERegion.enchanted_grove, (SVEEntrance.grove_to_outpost_warp, SVEEntrance.grove_to_wizard_warp, SVEEntrance.grove_to_farm_warp, SVEEntrance.grove_to_guild_warp, SVEEntrance.grove_to_junimo_warp, - SVEEntrance.grove_to_spring_warp, SVEEntrance.grove_to_aurora_warp]), - RegionData(SVERegion.grove_farm_warp, [SVEEntrance.farm_warp_to_farm]), - RegionData(SVERegion.grove_aurora_warp, [SVEEntrance.aurora_warp_to_aurora]), - RegionData(SVERegion.grove_guild_warp, [SVEEntrance.guild_warp_to_guild]), - RegionData(SVERegion.grove_junimo_warp, [SVEEntrance.junimo_warp_to_junimo]), - RegionData(SVERegion.grove_spring_warp, [SVEEntrance.spring_warp_to_spring]), - RegionData(SVERegion.grove_outpost_warp, [SVEEntrance.outpost_warp_to_outpost]), - RegionData(SVERegion.grove_wizard_warp, [SVEEntrance.wizard_warp_to_wizard]), - RegionData(SVERegion.galmoran_outpost, [SVEEntrance.outpost_to_badlands_entrance, SVEEntrance.use_alesia_shop, - SVEEntrance.use_isaac_shop]), - RegionData(SVERegion.badlands_entrance, [SVEEntrance.badlands_entrance_to_badlands]), - RegionData(SVERegion.crimson_badlands, [SVEEntrance.badlands_to_cave]), + SVEEntrance.grove_to_spring_warp, SVEEntrance.grove_to_aurora_warp)), + RegionData(SVERegion.grove_farm_warp, (SVEEntrance.farm_warp_to_farm,)), + RegionData(SVERegion.grove_aurora_warp, (SVEEntrance.aurora_warp_to_aurora,)), + RegionData(SVERegion.grove_guild_warp, (SVEEntrance.guild_warp_to_guild,)), + RegionData(SVERegion.grove_junimo_warp, (SVEEntrance.junimo_warp_to_junimo,)), + RegionData(SVERegion.grove_spring_warp, (SVEEntrance.spring_warp_to_spring,)), + RegionData(SVERegion.grove_outpost_warp, (SVEEntrance.outpost_warp_to_outpost,)), + RegionData(SVERegion.grove_wizard_warp, (SVEEntrance.wizard_warp_to_wizard,)), + RegionData(SVERegion.galmoran_outpost, (SVEEntrance.outpost_to_badlands_entrance, SVEEntrance.use_alesia_shop, SVEEntrance.use_isaac_shop)), + RegionData(SVERegion.badlands_entrance, (SVEEntrance.badlands_entrance_to_badlands,)), + RegionData(SVERegion.crimson_badlands, (SVEEntrance.badlands_to_cave,)), RegionData(SVERegion.badlands_cave), - RegionData(Region.bus_stop, [SVEEntrance.bus_stop_to_shed]), - RegionData(SVERegion.grandpas_shed, [SVEEntrance.grandpa_shed_to_interior, SVEEntrance.grandpa_shed_to_town]), - RegionData(SVERegion.grandpas_shed_interior, [SVEEntrance.grandpa_interior_to_upstairs]), + RegionData(Region.bus_stop, (SVEEntrance.bus_stop_to_shed,)), + RegionData(SVERegion.grandpas_shed, (SVEEntrance.grandpa_shed_to_interior, SVEEntrance.grandpa_shed_to_town)), + RegionData(SVERegion.grandpas_shed_interior, (SVEEntrance.grandpa_interior_to_upstairs,)), RegionData(SVERegion.grandpas_shed_upstairs), RegionData(Region.forest, - [SVEEntrance.forest_to_fairhaven, SVEEntrance.forest_to_west, SVEEntrance.forest_to_lost_woods, - SVEEntrance.forest_to_bmv, SVEEntrance.forest_to_marnie_shed]), + (SVEEntrance.forest_to_fairhaven, SVEEntrance.forest_to_west, SVEEntrance.forest_to_lost_woods, + SVEEntrance.forest_to_bmv, SVEEntrance.forest_to_marnie_shed)), RegionData(SVERegion.marnies_shed), RegionData(SVERegion.fairhaven_farm), - RegionData(Region.town, [SVEEntrance.town_to_bmv, SVEEntrance.town_to_jenkins, - SVEEntrance.town_to_bridge, SVEEntrance.town_to_plot]), - RegionData(SVERegion.blue_moon_vineyard, [SVEEntrance.bmv_to_sophia, SVEEntrance.bmv_to_beach]), + RegionData(Region.town, (SVEEntrance.town_to_bmv, SVEEntrance.town_to_jenkins, SVEEntrance.town_to_bridge, SVEEntrance.town_to_plot)), + RegionData(SVERegion.blue_moon_vineyard, (SVEEntrance.bmv_to_sophia, SVEEntrance.bmv_to_beach)), RegionData(SVERegion.sophias_house), - RegionData(SVERegion.jenkins_residence, [SVEEntrance.jenkins_to_cellar]), + RegionData(SVERegion.jenkins_residence, (SVEEntrance.jenkins_to_cellar,)), RegionData(SVERegion.jenkins_cellar), - RegionData(SVERegion.unclaimed_plot, [SVEEntrance.plot_to_bridge]), + RegionData(SVERegion.unclaimed_plot, (SVEEntrance.plot_to_bridge,)), RegionData(SVERegion.shearwater), - RegionData(Region.museum, [SVEEntrance.museum_to_gunther_bedroom]), + RegionData(Region.museum, (SVEEntrance.museum_to_gunther_bedroom,)), RegionData(SVERegion.gunther_bedroom), - RegionData(Region.fish_shop, [SVEEntrance.fish_shop_to_willy_bedroom]), + RegionData(Region.fish_shop, (SVEEntrance.fish_shop_to_willy_bedroom,)), RegionData(SVERegion.willy_bedroom), - RegionData(Region.mountain, [SVEEntrance.mountain_to_guild_summit]), - RegionData(SVERegion.guild_summit, [SVEEntrance.guild_to_interior, SVEEntrance.guild_to_mines, - SVEEntrance.summit_to_highlands]), - RegionData(Region.railroad, [SVEEntrance.to_susan_house, SVEEntrance.enter_summit, SVEEntrance.railroad_to_grampleton_station]), - RegionData(SVERegion.grampleton_station, [SVEEntrance.grampleton_station_to_grampleton_suburbs]), - RegionData(SVERegion.grampleton_suburbs, [SVEEntrance.grampleton_suburbs_to_scarlett_house]), + RegionData(Region.mountain, (SVEEntrance.mountain_to_guild_summit,)), + # These entrances are removed from the mountain region when SVE is enabled + RegionData(Region.mountain, (Entrance.mountain_to_adventurer_guild, Entrance.mountain_to_the_mines), flag=MergeFlag.REMOVE_EXITS), + RegionData(SVERegion.guild_summit, (SVEEntrance.guild_to_interior, SVEEntrance.guild_to_mines)), + RegionData(Region.railroad, (SVEEntrance.to_susan_house, SVEEntrance.enter_summit, SVEEntrance.railroad_to_grampleton_station)), + RegionData(SVERegion.grampleton_station, (SVEEntrance.grampleton_station_to_grampleton_suburbs,)), + RegionData(SVERegion.grampleton_suburbs, (SVEEntrance.grampleton_suburbs_to_scarlett_house,)), RegionData(SVERegion.scarlett_house), - RegionData(Region.wizard_basement, [SVEEntrance.wizard_to_fable_reef]), - RegionData(SVERegion.fable_reef, [SVEEntrance.fable_reef_to_guild], is_ginger_island=True), - RegionData(SVERegion.first_slash_guild, [SVEEntrance.first_slash_guild_to_hallway], is_ginger_island=True), - RegionData(SVERegion.first_slash_hallway, [SVEEntrance.first_slash_hallway_to_room], is_ginger_island=True), - RegionData(SVERegion.first_slash_spare_room, is_ginger_island=True), - RegionData(SVERegion.highlands_outside, [SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave, SVEEntrance.highlands_to_pond], is_ginger_island=True), - RegionData(SVERegion.highlands_pond, is_ginger_island=True), - RegionData(SVERegion.highlands_cavern, [SVEEntrance.to_dwarf_prison], is_ginger_island=True), - RegionData(SVERegion.dwarf_prison, is_ginger_island=True), - RegionData(SVERegion.lances_house, [SVEEntrance.lance_to_ladder], is_ginger_island=True), - RegionData(SVERegion.lances_ladder, [SVEEntrance.lance_ladder_to_highlands], is_ginger_island=True), - RegionData(SVERegion.forest_west, [SVEEntrance.forest_west_to_spring, SVEEntrance.west_to_aurora, - SVEEntrance.use_bear_shop]), - RegionData(SVERegion.aurora_vineyard, [SVEEntrance.to_aurora_basement]), + RegionData(SVERegion.forest_west, (SVEEntrance.forest_west_to_spring, SVEEntrance.west_to_aurora, SVEEntrance.use_bear_shop,)), + RegionData(SVERegion.aurora_vineyard, (SVEEntrance.to_aurora_basement,)), RegionData(SVERegion.aurora_vineyard_basement), - RegionData(Region.secret_woods, [SVEEntrance.secret_woods_to_west]), + RegionData(Region.secret_woods, (SVEEntrance.secret_woods_to_west,)), RegionData(SVERegion.bear_shop), - RegionData(SVERegion.sprite_spring, [SVEEntrance.sprite_spring_to_cave]), + RegionData(SVERegion.sprite_spring, (SVEEntrance.sprite_spring_to_cave,)), RegionData(SVERegion.sprite_spring_cave), - RegionData(SVERegion.lost_woods, [SVEEntrance.lost_woods_to_junimo_woods]), - RegionData(SVERegion.junimo_woods, [SVEEntrance.use_purple_junimo]), + RegionData(SVERegion.lost_woods, (SVEEntrance.lost_woods_to_junimo_woods,)), + RegionData(SVERegion.junimo_woods, (SVEEntrance.use_purple_junimo,)), RegionData(SVERegion.purple_junimo_shop), RegionData(SVERegion.alesia_shop), RegionData(SVERegion.isaac_shop), RegionData(SVERegion.summit), RegionData(SVERegion.susans_house), - RegionData(Region.mountain, [Entrance.mountain_to_adventurer_guild, Entrance.mountain_to_the_mines], ModificationFlag.MODIFIED) - ] -mandatory_sve_connections = [ +sve_ginger_island_regions = [ + RegionData(Region.wizard_basement, (SVEEntrance.wizard_to_fable_reef,)), + + RegionData(SVERegion.fable_reef, (SVEEntrance.fable_reef_to_guild,)), + RegionData(SVERegion.first_slash_guild, (SVEEntrance.first_slash_guild_to_hallway,)), + RegionData(SVERegion.first_slash_hallway, (SVEEntrance.first_slash_hallway_to_room,)), + RegionData(SVERegion.first_slash_spare_room), + RegionData(SVERegion.guild_summit, (SVEEntrance.summit_to_highlands,)), + RegionData(SVERegion.highlands_outside, (SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave, SVEEntrance.highlands_to_pond), ), + RegionData(SVERegion.highlands_pond), + RegionData(SVERegion.highlands_cavern, (SVEEntrance.to_dwarf_prison,)), + RegionData(SVERegion.dwarf_prison), + RegionData(SVERegion.lances_house, (SVEEntrance.lance_to_ladder,)), + RegionData(SVERegion.lances_ladder, (SVEEntrance.lance_ladder_to_highlands,)), +] + +sve_main_land_connections = [ ConnectionData(SVEEntrance.town_to_jenkins, SVERegion.jenkins_residence, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(SVEEntrance.jenkins_to_cellar, SVERegion.jenkins_cellar, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.forest_to_bmv, SVERegion.blue_moon_vineyard), @@ -223,7 +222,7 @@ mandatory_sve_connections = [ ConnectionData(SVEEntrance.grandpa_interior_to_upstairs, SVERegion.grandpas_shed_upstairs, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.grandpa_shed_to_town, Region.town), ConnectionData(SVEEntrance.bmv_to_sophia, SVERegion.sophias_house, flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(SVEEntrance.summit_to_highlands, SVERegion.highlands_outside, flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(SVEEntrance.summit_to_highlands, SVERegion.highlands_outside), ConnectionData(SVEEntrance.guild_to_interior, Region.adventurer_guild, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.backwoods_to_grove, SVERegion.enchanted_grove, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), ConnectionData(SVEEntrance.grove_to_outpost_warp, SVERegion.grove_outpost_warp), @@ -242,8 +241,6 @@ mandatory_sve_connections = [ ConnectionData(SVEEntrance.use_purple_junimo, SVERegion.purple_junimo_shop), ConnectionData(SVEEntrance.grove_to_spring_warp, SVERegion.grove_spring_warp), ConnectionData(SVEEntrance.spring_warp_to_spring, SVERegion.sprite_spring, flag=RandomizationFlag.BUILDINGS), - ConnectionData(SVEEntrance.wizard_to_fable_reef, SVERegion.fable_reef, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(SVEEntrance.fable_reef_to_guild, SVERegion.first_slash_guild, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), ConnectionData(SVEEntrance.outpost_to_badlands_entrance, SVERegion.badlands_entrance, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.badlands_entrance_to_badlands, SVERegion.crimson_badlands, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.badlands_to_cave, SVERegion.badlands_cave, flag=RandomizationFlag.BUILDINGS), @@ -259,71 +256,75 @@ mandatory_sve_connections = [ ConnectionData(SVEEntrance.to_susan_house, SVERegion.susans_house, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.enter_summit, SVERegion.summit, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.forest_to_fairhaven, SVERegion.fairhaven_farm, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData(SVEEntrance.highlands_to_lance, SVERegion.lances_house, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(SVEEntrance.lance_to_ladder, SVERegion.lances_ladder, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(SVEEntrance.lance_ladder_to_highlands, SVERegion.highlands_outside, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(SVEEntrance.highlands_to_cave, SVERegion.highlands_cavern, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), ConnectionData(SVEEntrance.use_bear_shop, SVERegion.bear_shop), ConnectionData(SVEEntrance.use_purple_junimo, SVERegion.purple_junimo_shop), ConnectionData(SVEEntrance.use_alesia_shop, SVERegion.alesia_shop), ConnectionData(SVEEntrance.use_isaac_shop, SVERegion.isaac_shop), - ConnectionData(SVEEntrance.to_dwarf_prison, SVERegion.dwarf_prison, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), ConnectionData(SVEEntrance.railroad_to_grampleton_station, SVERegion.grampleton_station), ConnectionData(SVEEntrance.grampleton_station_to_grampleton_suburbs, SVERegion.grampleton_suburbs), ConnectionData(SVEEntrance.grampleton_suburbs_to_scarlett_house, SVERegion.scarlett_house, flag=RandomizationFlag.BUILDINGS), - ConnectionData(SVEEntrance.first_slash_guild_to_hallway, SVERegion.first_slash_hallway, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(SVEEntrance.first_slash_hallway_to_room, SVERegion.first_slash_spare_room, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), ConnectionData(SVEEntrance.sprite_spring_to_cave, SVERegion.sprite_spring_cave, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.fish_shop_to_willy_bedroom, SVERegion.willy_bedroom, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.museum_to_gunther_bedroom, SVERegion.gunther_bedroom, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.highlands_to_pond, SVERegion.highlands_pond), ] +sve_ginger_island_connections = [ + ConnectionData(SVEEntrance.wizard_to_fable_reef, SVERegion.fable_reef, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.fable_reef_to_guild, SVERegion.first_slash_guild, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.highlands_to_lance, SVERegion.lances_house, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.lance_to_ladder, SVERegion.lances_ladder), + ConnectionData(SVEEntrance.lance_ladder_to_highlands, SVERegion.highlands_outside, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.highlands_to_cave, SVERegion.highlands_cavern, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.to_dwarf_prison, SVERegion.dwarf_prison, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.first_slash_guild_to_hallway, SVERegion.first_slash_hallway, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.first_slash_hallway_to_room, SVERegion.first_slash_spare_room, flag=RandomizationFlag.BUILDINGS), +] + alecto_regions = [ - RegionData(Region.witch_hut, [AlectoEntrance.witch_hut_to_witch_attic]), - RegionData(AlectoRegion.witch_attic) + RegionData(Region.witch_hut, (AlectoEntrance.witch_hut_to_witch_attic,)), + RegionData(AlectoRegion.witch_attic), ] alecto_entrances = [ - ConnectionData(AlectoEntrance.witch_hut_to_witch_attic, AlectoRegion.witch_attic, flag=RandomizationFlag.BUILDINGS) + ConnectionData(AlectoEntrance.witch_hut_to_witch_attic, AlectoRegion.witch_attic, flag=RandomizationFlag.BUILDINGS), ] lacey_regions = [ - RegionData(Region.forest, [LaceyEntrance.forest_to_hat_house]), - RegionData(LaceyRegion.hat_house) + RegionData(Region.forest, (LaceyEntrance.forest_to_hat_house,)), + RegionData(LaceyRegion.hat_house), ] lacey_entrances = [ - ConnectionData(LaceyEntrance.forest_to_hat_house, LaceyRegion.hat_house, flag=RandomizationFlag.BUILDINGS) + ConnectionData(LaceyEntrance.forest_to_hat_house, LaceyRegion.hat_house, flag=RandomizationFlag.BUILDINGS), ] boarding_house_regions = [ - RegionData(Region.bus_stop, [BoardingHouseEntrance.bus_stop_to_boarding_house_plateau]), - RegionData(BoardingHouseRegion.boarding_house_plateau, [BoardingHouseEntrance.boarding_house_plateau_to_boarding_house_first, + RegionData(Region.bus_stop, (BoardingHouseEntrance.bus_stop_to_boarding_house_plateau,)), + RegionData(BoardingHouseRegion.boarding_house_plateau, (BoardingHouseEntrance.boarding_house_plateau_to_boarding_house_first, BoardingHouseEntrance.boarding_house_plateau_to_buffalo_ranch, - BoardingHouseEntrance.boarding_house_plateau_to_abandoned_mines_entrance]), - RegionData(BoardingHouseRegion.boarding_house_first, [BoardingHouseEntrance.boarding_house_first_to_boarding_house_second]), + BoardingHouseEntrance.boarding_house_plateau_to_abandoned_mines_entrance)), + RegionData(BoardingHouseRegion.boarding_house_first, (BoardingHouseEntrance.boarding_house_first_to_boarding_house_second,)), RegionData(BoardingHouseRegion.boarding_house_second), RegionData(BoardingHouseRegion.buffalo_ranch), - RegionData(BoardingHouseRegion.abandoned_mines_entrance, [BoardingHouseEntrance.abandoned_mines_entrance_to_abandoned_mines_1a, - BoardingHouseEntrance.abandoned_mines_entrance_to_the_lost_valley]), - RegionData(BoardingHouseRegion.abandoned_mines_1a, [BoardingHouseEntrance.abandoned_mines_1a_to_abandoned_mines_1b]), - RegionData(BoardingHouseRegion.abandoned_mines_1b, [BoardingHouseEntrance.abandoned_mines_1b_to_abandoned_mines_2a]), - RegionData(BoardingHouseRegion.abandoned_mines_2a, [BoardingHouseEntrance.abandoned_mines_2a_to_abandoned_mines_2b]), - RegionData(BoardingHouseRegion.abandoned_mines_2b, [BoardingHouseEntrance.abandoned_mines_2b_to_abandoned_mines_3]), - RegionData(BoardingHouseRegion.abandoned_mines_3, [BoardingHouseEntrance.abandoned_mines_3_to_abandoned_mines_4]), - RegionData(BoardingHouseRegion.abandoned_mines_4, [BoardingHouseEntrance.abandoned_mines_4_to_abandoned_mines_5]), - RegionData(BoardingHouseRegion.abandoned_mines_5, [BoardingHouseEntrance.abandoned_mines_5_to_the_lost_valley]), - RegionData(BoardingHouseRegion.the_lost_valley, [BoardingHouseEntrance.the_lost_valley_to_gregory_tent, + RegionData(BoardingHouseRegion.abandoned_mines_entrance, (BoardingHouseEntrance.abandoned_mines_entrance_to_abandoned_mines_1a, + BoardingHouseEntrance.abandoned_mines_entrance_to_the_lost_valley)), + RegionData(BoardingHouseRegion.abandoned_mines_1a, (BoardingHouseEntrance.abandoned_mines_1a_to_abandoned_mines_1b,)), + RegionData(BoardingHouseRegion.abandoned_mines_1b, (BoardingHouseEntrance.abandoned_mines_1b_to_abandoned_mines_2a,)), + RegionData(BoardingHouseRegion.abandoned_mines_2a, (BoardingHouseEntrance.abandoned_mines_2a_to_abandoned_mines_2b,)), + RegionData(BoardingHouseRegion.abandoned_mines_2b, (BoardingHouseEntrance.abandoned_mines_2b_to_abandoned_mines_3,)), + RegionData(BoardingHouseRegion.abandoned_mines_3, (BoardingHouseEntrance.abandoned_mines_3_to_abandoned_mines_4,)), + RegionData(BoardingHouseRegion.abandoned_mines_4, (BoardingHouseEntrance.abandoned_mines_4_to_abandoned_mines_5,)), + RegionData(BoardingHouseRegion.abandoned_mines_5, (BoardingHouseEntrance.abandoned_mines_5_to_the_lost_valley,)), + RegionData(BoardingHouseRegion.the_lost_valley, (BoardingHouseEntrance.the_lost_valley_to_gregory_tent, BoardingHouseEntrance.lost_valley_to_lost_valley_minecart, - BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins]), + BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins)), RegionData(BoardingHouseRegion.gregory_tent), - RegionData(BoardingHouseRegion.lost_valley_ruins, [BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_1, - BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_2]), + RegionData(BoardingHouseRegion.lost_valley_ruins, (BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_1, + BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_2)), RegionData(BoardingHouseRegion.lost_valley_minecart), RegionData(BoardingHouseRegion.lost_valley_house_1), - RegionData(BoardingHouseRegion.lost_valley_house_2) + RegionData(BoardingHouseRegion.lost_valley_house_2), ] boarding_house_entrances = [ @@ -351,30 +352,29 @@ boarding_house_entrances = [ ConnectionData(BoardingHouseEntrance.lost_valley_to_lost_valley_minecart, BoardingHouseRegion.lost_valley_minecart), ConnectionData(BoardingHouseEntrance.the_lost_valley_to_lost_valley_ruins, BoardingHouseRegion.lost_valley_ruins, flag=RandomizationFlag.BUILDINGS), ConnectionData(BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_1, BoardingHouseRegion.lost_valley_house_1, flag=RandomizationFlag.BUILDINGS), - ConnectionData(BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_2, BoardingHouseRegion.lost_valley_house_2, flag=RandomizationFlag.BUILDINGS) + ConnectionData(BoardingHouseEntrance.lost_valley_ruins_to_lost_valley_house_2, BoardingHouseRegion.lost_valley_house_2, flag=RandomizationFlag.BUILDINGS), ] -vanilla_connections_to_remove_by_mod: Dict[str, List[ConnectionData]] = { - ModNames.sve: [ - ConnectionData(Entrance.mountain_to_the_mines, Region.mines, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.mountain_to_adventurer_guild, Region.adventurer_guild, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ] +vanilla_connections_to_remove_by_content_pack: dict[str, tuple[str, ...]] = { + ModNames.sve: ( + Entrance.mountain_to_the_mines, + Entrance.mountain_to_adventurer_guild, + ) } -ModDataList = { - ModNames.deepwoods: ModRegionData(ModNames.deepwoods, deep_woods_regions, deep_woods_entrances), - ModNames.eugene: ModRegionData(ModNames.eugene, eugene_regions, eugene_entrances), - ModNames.jasper: ModRegionData(ModNames.jasper, jasper_regions, jasper_entrances), - ModNames.alec: ModRegionData(ModNames.alec, alec_regions, alec_entrances), - ModNames.yoba: ModRegionData(ModNames.yoba, yoba_regions, yoba_entrances), - ModNames.juna: ModRegionData(ModNames.juna, juna_regions, juna_entrances), - ModNames.magic: ModRegionData(ModNames.magic, magic_regions, magic_entrances), - ModNames.ayeisha: ModRegionData(ModNames.ayeisha, ayeisha_regions, ayeisha_entrances), - ModNames.riley: ModRegionData(ModNames.riley, riley_regions, riley_entrances), - ModNames.sve: ModRegionData(ModNames.sve, stardew_valley_expanded_regions, mandatory_sve_connections), - ModNames.alecto: ModRegionData(ModNames.alecto, alecto_regions, alecto_entrances), - ModNames.lacey: ModRegionData(ModNames.lacey, lacey_regions, lacey_entrances), - ModNames.boarding_house: ModRegionData(ModNames.boarding_house, boarding_house_regions, boarding_house_entrances), +region_data_by_content_pack = { + ModNames.deepwoods: ModRegionsData(ModNames.deepwoods, deep_woods_regions, deep_woods_entrances), + ModNames.eugene: ModRegionsData(ModNames.eugene, eugene_regions, eugene_entrances), + ModNames.jasper: ModRegionsData(ModNames.jasper, jasper_regions, jasper_entrances), + ModNames.alec: ModRegionsData(ModNames.alec, alec_regions, alec_entrances), + ModNames.yoba: ModRegionsData(ModNames.yoba, yoba_regions, yoba_entrances), + ModNames.juna: ModRegionsData(ModNames.juna, juna_regions, juna_entrances), + ModNames.magic: ModRegionsData(ModNames.magic, magic_regions, magic_entrances), + ModNames.ayeisha: ModRegionsData(ModNames.ayeisha, ayeisha_regions, ayeisha_entrances), + ModNames.riley: ModRegionsData(ModNames.riley, riley_regions, riley_entrances), + ModNames.sve: ModRegionsData(ModNames.sve, sve_main_land_regions, sve_main_land_connections), + SVE_GINGER_ISLAND_PACK: ModRegionsData(SVE_GINGER_ISLAND_PACK, sve_ginger_island_regions, sve_ginger_island_connections), + ModNames.alecto: ModRegionsData(ModNames.alecto, alecto_regions, alecto_entrances), + ModNames.lacey: ModRegionsData(ModNames.lacey, lacey_regions, lacey_entrances), + ModNames.boarding_house: ModRegionsData(ModNames.boarding_house, boarding_house_regions, boarding_house_entrances), } diff --git a/worlds/stardew_valley/region_classes.py b/worlds/stardew_valley/region_classes.py deleted file mode 100644 index d3d16e38..00000000 --- a/worlds/stardew_valley/region_classes.py +++ /dev/null @@ -1,67 +0,0 @@ -from copy import deepcopy -from dataclasses import dataclass, field -from enum import IntFlag -from typing import Optional, List, Set - -connector_keyword = " to " - - -class ModificationFlag(IntFlag): - NOT_MODIFIED = 0 - MODIFIED = 1 - - -class RandomizationFlag(IntFlag): - NOT_RANDOMIZED = 0b0 - PELICAN_TOWN = 0b00011111 - NON_PROGRESSION = 0b00011110 - BUILDINGS = 0b00011100 - EVERYTHING = 0b00011000 - GINGER_ISLAND = 0b00100000 - LEAD_TO_OPEN_AREA = 0b01000000 - MASTERIES = 0b10000000 - - -@dataclass(frozen=True) -class RegionData: - name: str - exits: List[str] = field(default_factory=list) - flag: ModificationFlag = ModificationFlag.NOT_MODIFIED - is_ginger_island: bool = False - - def get_merged_with(self, exits: List[str]): - merged_exits = [] - merged_exits.extend(self.exits) - if exits is not None: - merged_exits.extend(exits) - merged_exits = sorted(set(merged_exits)) - return RegionData(self.name, merged_exits, is_ginger_island=self.is_ginger_island) - - def get_without_exits(self, exits_to_remove: Set[str]): - exits = [exit_ for exit_ in self.exits if exit_ not in exits_to_remove] - return RegionData(self.name, exits, is_ginger_island=self.is_ginger_island) - - def get_clone(self): - return deepcopy(self) - - -@dataclass(frozen=True) -class ConnectionData: - name: str - destination: str - origin: Optional[str] = None - reverse: Optional[str] = None - flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED - - def __post_init__(self): - if connector_keyword in self.name: - origin, destination = self.name.split(connector_keyword) - if self.reverse is None: - super().__setattr__("reverse", f"{destination}{connector_keyword}{origin}") - - -@dataclass(frozen=True) -class ModRegionData: - mod_name: str - regions: List[RegionData] - connections: List[ConnectionData] diff --git a/worlds/stardew_valley/regions.py b/worlds/stardew_valley/regions.py deleted file mode 100644 index 4d06d598..00000000 --- a/worlds/stardew_valley/regions.py +++ /dev/null @@ -1,775 +0,0 @@ -from random import Random -from typing import Iterable, Dict, Protocol, List, Tuple, Set - -from BaseClasses import Region, Entrance -from .content import content_packs, StardewContent -from .mods.mod_regions import ModDataList, vanilla_connections_to_remove_by_mod -from .options import EntranceRandomization, ExcludeGingerIsland, StardewValleyOptions -from .region_classes import RegionData, ConnectionData, RandomizationFlag, ModificationFlag -from .strings.entrance_names import Entrance, LogicEntrance -from .strings.region_names import Region as RegionName, LogicRegion - - -class RegionFactory(Protocol): - def __call__(self, name: str, regions: Iterable[str]) -> Region: - raise NotImplementedError - - -vanilla_regions = [ - RegionData(RegionName.menu, [Entrance.to_stardew_valley]), - RegionData(RegionName.stardew_valley, [Entrance.to_farmhouse]), - RegionData(RegionName.farm_house, - [Entrance.farmhouse_to_farm, Entrance.downstairs_to_cellar, LogicEntrance.farmhouse_cooking, LogicEntrance.watch_queen_of_sauce]), - RegionData(RegionName.cellar), - RegionData(RegionName.farm, - [Entrance.farm_to_backwoods, Entrance.farm_to_bus_stop, Entrance.farm_to_forest, Entrance.farm_to_farmcave, Entrance.enter_greenhouse, - Entrance.enter_coop, Entrance.enter_barn, Entrance.enter_shed, Entrance.enter_slime_hutch, LogicEntrance.grow_spring_crops, - LogicEntrance.grow_summer_crops, LogicEntrance.grow_fall_crops, LogicEntrance.grow_winter_crops, LogicEntrance.shipping, - LogicEntrance.fishing, ]), - RegionData(RegionName.backwoods, [Entrance.backwoods_to_mountain]), - RegionData(RegionName.bus_stop, - [Entrance.bus_stop_to_town, Entrance.take_bus_to_desert, Entrance.bus_stop_to_tunnel_entrance]), - RegionData(RegionName.forest, - [Entrance.forest_to_town, Entrance.enter_secret_woods, Entrance.forest_to_wizard_tower, Entrance.forest_to_marnie_ranch, - Entrance.forest_to_leah_cottage, Entrance.forest_to_sewer, Entrance.forest_to_mastery_cave, LogicEntrance.buy_from_traveling_merchant, - LogicEntrance.complete_raccoon_requests, LogicEntrance.fish_in_waterfall, LogicEntrance.attend_flower_dance, LogicEntrance.attend_trout_derby, - LogicEntrance.attend_festival_of_ice]), - RegionData(LogicRegion.forest_waterfall), - RegionData(RegionName.farm_cave), - RegionData(RegionName.greenhouse, - [LogicEntrance.grow_spring_crops_in_greenhouse, LogicEntrance.grow_summer_crops_in_greenhouse, LogicEntrance.grow_fall_crops_in_greenhouse, - LogicEntrance.grow_winter_crops_in_greenhouse, LogicEntrance.grow_indoor_crops_in_greenhouse]), - RegionData(RegionName.mountain, - [Entrance.mountain_to_railroad, Entrance.mountain_to_tent, Entrance.mountain_to_carpenter_shop, - Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild, - Entrance.mountain_to_town, Entrance.mountain_to_maru_room, - Entrance.mountain_to_leo_treehouse]), - RegionData(RegionName.leo_treehouse, is_ginger_island=True), - RegionData(RegionName.maru_room), - RegionData(RegionName.tunnel_entrance, [Entrance.tunnel_entrance_to_bus_tunnel]), - RegionData(RegionName.bus_tunnel), - RegionData(RegionName.town, - [Entrance.town_to_community_center, Entrance.town_to_beach, Entrance.town_to_hospital, Entrance.town_to_pierre_general_store, - Entrance.town_to_saloon, Entrance.town_to_alex_house, Entrance.town_to_trailer, Entrance.town_to_mayor_manor, Entrance.town_to_sam_house, - Entrance.town_to_haley_house, Entrance.town_to_sewer, Entrance.town_to_clint_blacksmith, Entrance.town_to_museum, Entrance.town_to_jojamart, - Entrance.purchase_movie_ticket, LogicEntrance.buy_experience_books, LogicEntrance.attend_egg_festival, LogicEntrance.attend_fair, - LogicEntrance.attend_spirit_eve, LogicEntrance.attend_winter_star]), - RegionData(RegionName.beach, - [Entrance.beach_to_willy_fish_shop, Entrance.enter_elliott_house, Entrance.enter_tide_pools, LogicEntrance.attend_luau, - LogicEntrance.attend_moonlight_jellies, LogicEntrance.attend_night_market, LogicEntrance.attend_squidfest]), - RegionData(RegionName.railroad, [Entrance.enter_bathhouse_entrance, Entrance.enter_witch_warp_cave]), - RegionData(RegionName.ranch), - RegionData(RegionName.leah_house), - RegionData(RegionName.mastery_cave), - RegionData(RegionName.sewer, [Entrance.enter_mutant_bug_lair]), - RegionData(RegionName.mutant_bug_lair), - RegionData(RegionName.wizard_tower, [Entrance.enter_wizard_basement, Entrance.use_desert_obelisk, Entrance.use_island_obelisk]), - RegionData(RegionName.wizard_basement), - RegionData(RegionName.tent), - RegionData(RegionName.carpenter, [Entrance.enter_sebastian_room]), - RegionData(RegionName.sebastian_room), - RegionData(RegionName.adventurer_guild, [Entrance.adventurer_guild_to_bedroom]), - RegionData(RegionName.adventurer_guild_bedroom), - RegionData(RegionName.community_center, - [Entrance.access_crafts_room, Entrance.access_pantry, Entrance.access_fish_tank, - Entrance.access_boiler_room, Entrance.access_bulletin_board, Entrance.access_vault]), - RegionData(RegionName.crafts_room), - RegionData(RegionName.pantry), - RegionData(RegionName.fish_tank), - RegionData(RegionName.boiler_room), - RegionData(RegionName.bulletin_board), - RegionData(RegionName.vault), - RegionData(RegionName.hospital, [Entrance.enter_harvey_room]), - RegionData(RegionName.harvey_room), - RegionData(RegionName.pierre_store, [Entrance.enter_sunroom]), - RegionData(RegionName.sunroom), - RegionData(RegionName.saloon, [Entrance.play_journey_of_the_prairie_king, Entrance.play_junimo_kart]), - RegionData(RegionName.jotpk_world_1, [Entrance.reach_jotpk_world_2]), - RegionData(RegionName.jotpk_world_2, [Entrance.reach_jotpk_world_3]), - RegionData(RegionName.jotpk_world_3), - RegionData(RegionName.junimo_kart_1, [Entrance.reach_junimo_kart_2]), - RegionData(RegionName.junimo_kart_2, [Entrance.reach_junimo_kart_3]), - RegionData(RegionName.junimo_kart_3, [Entrance.reach_junimo_kart_4]), - RegionData(RegionName.junimo_kart_4), - RegionData(RegionName.alex_house), - RegionData(RegionName.trailer), - RegionData(RegionName.mayor_house), - RegionData(RegionName.sam_house), - RegionData(RegionName.haley_house), - RegionData(RegionName.blacksmith, [LogicEntrance.blacksmith_copper]), - RegionData(RegionName.museum), - RegionData(RegionName.jojamart, [Entrance.enter_abandoned_jojamart]), - RegionData(RegionName.abandoned_jojamart, [Entrance.enter_movie_theater]), - RegionData(RegionName.movie_ticket_stand), - RegionData(RegionName.movie_theater), - RegionData(RegionName.fish_shop, [Entrance.fish_shop_to_boat_tunnel]), - RegionData(RegionName.boat_tunnel, [Entrance.boat_to_ginger_island], is_ginger_island=True), - RegionData(RegionName.elliott_house), - RegionData(RegionName.tide_pools), - RegionData(RegionName.bathhouse_entrance, [Entrance.enter_locker_room]), - RegionData(RegionName.locker_room, [Entrance.enter_public_bath]), - RegionData(RegionName.public_bath), - RegionData(RegionName.witch_warp_cave, [Entrance.enter_witch_swamp]), - RegionData(RegionName.witch_swamp, [Entrance.enter_witch_hut]), - RegionData(RegionName.witch_hut, [Entrance.witch_warp_to_wizard_basement]), - RegionData(RegionName.quarry, [Entrance.enter_quarry_mine_entrance]), - RegionData(RegionName.quarry_mine_entrance, [Entrance.enter_quarry_mine]), - RegionData(RegionName.quarry_mine), - RegionData(RegionName.secret_woods), - RegionData(RegionName.desert, [Entrance.enter_skull_cavern_entrance, Entrance.enter_oasis, LogicEntrance.attend_desert_festival]), - RegionData(RegionName.oasis, [Entrance.enter_casino]), - RegionData(RegionName.casino), - RegionData(RegionName.skull_cavern_entrance, [Entrance.enter_skull_cavern]), - RegionData(RegionName.skull_cavern, [Entrance.mine_to_skull_cavern_floor_25]), - RegionData(RegionName.skull_cavern_25, [Entrance.mine_to_skull_cavern_floor_50]), - RegionData(RegionName.skull_cavern_50, [Entrance.mine_to_skull_cavern_floor_75]), - RegionData(RegionName.skull_cavern_75, [Entrance.mine_to_skull_cavern_floor_100]), - RegionData(RegionName.skull_cavern_100, [Entrance.mine_to_skull_cavern_floor_125]), - RegionData(RegionName.skull_cavern_125, [Entrance.mine_to_skull_cavern_floor_150]), - RegionData(RegionName.skull_cavern_150, [Entrance.mine_to_skull_cavern_floor_175]), - RegionData(RegionName.skull_cavern_175, [Entrance.mine_to_skull_cavern_floor_200]), - RegionData(RegionName.skull_cavern_200, [Entrance.enter_dangerous_skull_cavern]), - RegionData(RegionName.dangerous_skull_cavern, is_ginger_island=True), - RegionData(RegionName.island_south, - [Entrance.island_south_to_west, Entrance.island_south_to_north, Entrance.island_south_to_east, Entrance.island_south_to_southeast, - Entrance.use_island_resort, Entrance.parrot_express_docks_to_volcano, Entrance.parrot_express_docks_to_dig_site, - Entrance.parrot_express_docks_to_jungle], - is_ginger_island=True), - RegionData(RegionName.island_resort, is_ginger_island=True), - RegionData(RegionName.island_west, - [Entrance.island_west_to_islandfarmhouse, Entrance.island_west_to_gourmand_cave, Entrance.island_west_to_crystals_cave, - Entrance.island_west_to_shipwreck, Entrance.island_west_to_qi_walnut_room, Entrance.use_farm_obelisk, Entrance.parrot_express_jungle_to_docks, - Entrance.parrot_express_jungle_to_dig_site, Entrance.parrot_express_jungle_to_volcano, LogicEntrance.grow_spring_crops_on_island, - LogicEntrance.grow_summer_crops_on_island, LogicEntrance.grow_fall_crops_on_island, LogicEntrance.grow_winter_crops_on_island, - LogicEntrance.grow_indoor_crops_on_island], - is_ginger_island=True), - RegionData(RegionName.island_east, [Entrance.island_east_to_leo_hut, Entrance.island_east_to_island_shrine], is_ginger_island=True), - RegionData(RegionName.island_shrine, is_ginger_island=True), - RegionData(RegionName.island_south_east, [Entrance.island_southeast_to_pirate_cove], is_ginger_island=True), - RegionData(RegionName.island_north, - [Entrance.talk_to_island_trader, Entrance.island_north_to_field_office, Entrance.island_north_to_dig_site, Entrance.island_north_to_volcano, - Entrance.parrot_express_volcano_to_dig_site, Entrance.parrot_express_volcano_to_jungle, Entrance.parrot_express_volcano_to_docks], - is_ginger_island=True), - RegionData(RegionName.volcano, [Entrance.climb_to_volcano_5, Entrance.volcano_to_secret_beach], is_ginger_island=True), - RegionData(RegionName.volcano_secret_beach, is_ginger_island=True), - RegionData(RegionName.volcano_floor_5, [Entrance.talk_to_volcano_dwarf, Entrance.climb_to_volcano_10], is_ginger_island=True), - RegionData(RegionName.volcano_dwarf_shop, is_ginger_island=True), - RegionData(RegionName.volcano_floor_10, is_ginger_island=True), - RegionData(RegionName.island_trader, is_ginger_island=True), - RegionData(RegionName.island_farmhouse, [LogicEntrance.island_cooking], is_ginger_island=True), - RegionData(RegionName.gourmand_frog_cave, is_ginger_island=True), - RegionData(RegionName.colored_crystals_cave, is_ginger_island=True), - RegionData(RegionName.shipwreck, is_ginger_island=True), - RegionData(RegionName.qi_walnut_room, is_ginger_island=True), - RegionData(RegionName.leo_hut, is_ginger_island=True), - RegionData(RegionName.pirate_cove, is_ginger_island=True), - RegionData(RegionName.field_office, is_ginger_island=True), - RegionData(RegionName.dig_site, - [Entrance.dig_site_to_professor_snail_cave, Entrance.parrot_express_dig_site_to_volcano, - Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_dig_site_to_jungle], - is_ginger_island=True), - RegionData(RegionName.professor_snail_cave, is_ginger_island=True), - RegionData(RegionName.coop), - RegionData(RegionName.barn), - RegionData(RegionName.shed), - RegionData(RegionName.slime_hutch), - - RegionData(RegionName.mines, [LogicEntrance.talk_to_mines_dwarf, - Entrance.dig_to_mines_floor_5]), - RegionData(RegionName.mines_floor_5, [Entrance.dig_to_mines_floor_10]), - RegionData(RegionName.mines_floor_10, [Entrance.dig_to_mines_floor_15]), - RegionData(RegionName.mines_floor_15, [Entrance.dig_to_mines_floor_20]), - RegionData(RegionName.mines_floor_20, [Entrance.dig_to_mines_floor_25]), - RegionData(RegionName.mines_floor_25, [Entrance.dig_to_mines_floor_30]), - RegionData(RegionName.mines_floor_30, [Entrance.dig_to_mines_floor_35]), - RegionData(RegionName.mines_floor_35, [Entrance.dig_to_mines_floor_40]), - RegionData(RegionName.mines_floor_40, [Entrance.dig_to_mines_floor_45]), - RegionData(RegionName.mines_floor_45, [Entrance.dig_to_mines_floor_50]), - RegionData(RegionName.mines_floor_50, [Entrance.dig_to_mines_floor_55]), - RegionData(RegionName.mines_floor_55, [Entrance.dig_to_mines_floor_60]), - RegionData(RegionName.mines_floor_60, [Entrance.dig_to_mines_floor_65]), - RegionData(RegionName.mines_floor_65, [Entrance.dig_to_mines_floor_70]), - RegionData(RegionName.mines_floor_70, [Entrance.dig_to_mines_floor_75]), - RegionData(RegionName.mines_floor_75, [Entrance.dig_to_mines_floor_80]), - RegionData(RegionName.mines_floor_80, [Entrance.dig_to_mines_floor_85]), - RegionData(RegionName.mines_floor_85, [Entrance.dig_to_mines_floor_90]), - RegionData(RegionName.mines_floor_90, [Entrance.dig_to_mines_floor_95]), - RegionData(RegionName.mines_floor_95, [Entrance.dig_to_mines_floor_100]), - RegionData(RegionName.mines_floor_100, [Entrance.dig_to_mines_floor_105]), - RegionData(RegionName.mines_floor_105, [Entrance.dig_to_mines_floor_110]), - RegionData(RegionName.mines_floor_110, [Entrance.dig_to_mines_floor_115]), - RegionData(RegionName.mines_floor_115, [Entrance.dig_to_mines_floor_120]), - RegionData(RegionName.mines_floor_120, [Entrance.dig_to_dangerous_mines_20, Entrance.dig_to_dangerous_mines_60, Entrance.dig_to_dangerous_mines_100]), - RegionData(RegionName.dangerous_mines_20, is_ginger_island=True), - RegionData(RegionName.dangerous_mines_60, is_ginger_island=True), - RegionData(RegionName.dangerous_mines_100, is_ginger_island=True), - - RegionData(LogicRegion.mines_dwarf_shop), - RegionData(LogicRegion.blacksmith_copper, [LogicEntrance.blacksmith_iron]), - RegionData(LogicRegion.blacksmith_iron, [LogicEntrance.blacksmith_gold]), - RegionData(LogicRegion.blacksmith_gold, [LogicEntrance.blacksmith_iridium]), - RegionData(LogicRegion.blacksmith_iridium), - RegionData(LogicRegion.kitchen), - RegionData(LogicRegion.queen_of_sauce), - RegionData(LogicRegion.fishing), - - RegionData(LogicRegion.spring_farming), - RegionData(LogicRegion.summer_farming, [LogicEntrance.grow_summer_fall_crops_in_summer]), - RegionData(LogicRegion.fall_farming, [LogicEntrance.grow_summer_fall_crops_in_fall]), - RegionData(LogicRegion.winter_farming), - RegionData(LogicRegion.summer_or_fall_farming), - RegionData(LogicRegion.indoor_farming), - - RegionData(LogicRegion.shipping), - RegionData(LogicRegion.traveling_cart, [LogicEntrance.buy_from_traveling_merchant_sunday, - LogicEntrance.buy_from_traveling_merchant_monday, - LogicEntrance.buy_from_traveling_merchant_tuesday, - LogicEntrance.buy_from_traveling_merchant_wednesday, - LogicEntrance.buy_from_traveling_merchant_thursday, - LogicEntrance.buy_from_traveling_merchant_friday, - LogicEntrance.buy_from_traveling_merchant_saturday]), - RegionData(LogicRegion.traveling_cart_sunday), - RegionData(LogicRegion.traveling_cart_monday), - RegionData(LogicRegion.traveling_cart_tuesday), - RegionData(LogicRegion.traveling_cart_wednesday), - RegionData(LogicRegion.traveling_cart_thursday), - RegionData(LogicRegion.traveling_cart_friday), - RegionData(LogicRegion.traveling_cart_saturday), - RegionData(LogicRegion.raccoon_daddy, [LogicEntrance.buy_from_raccoon]), - RegionData(LogicRegion.raccoon_shop), - - RegionData(LogicRegion.egg_festival), - RegionData(LogicRegion.desert_festival), - RegionData(LogicRegion.flower_dance), - RegionData(LogicRegion.luau), - RegionData(LogicRegion.trout_derby), - RegionData(LogicRegion.moonlight_jellies), - RegionData(LogicRegion.fair), - RegionData(LogicRegion.spirit_eve), - RegionData(LogicRegion.festival_of_ice), - RegionData(LogicRegion.night_market), - RegionData(LogicRegion.winter_star), - RegionData(LogicRegion.squidfest), - RegionData(LogicRegion.bookseller_1, [LogicEntrance.buy_year1_books]), - RegionData(LogicRegion.bookseller_2, [LogicEntrance.buy_year3_books]), - RegionData(LogicRegion.bookseller_3), -] - -# Exists and where they lead -vanilla_connections = [ - ConnectionData(Entrance.to_stardew_valley, RegionName.stardew_valley), - ConnectionData(Entrance.to_farmhouse, RegionName.farm_house), - ConnectionData(Entrance.farmhouse_to_farm, RegionName.farm), - ConnectionData(Entrance.downstairs_to_cellar, RegionName.cellar), - ConnectionData(Entrance.farm_to_backwoods, RegionName.backwoods), - ConnectionData(Entrance.farm_to_bus_stop, RegionName.bus_stop), - ConnectionData(Entrance.farm_to_forest, RegionName.forest), - ConnectionData(Entrance.farm_to_farmcave, RegionName.farm_cave, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData(Entrance.enter_greenhouse, RegionName.greenhouse), - ConnectionData(Entrance.enter_coop, RegionName.coop), - ConnectionData(Entrance.enter_barn, RegionName.barn), - ConnectionData(Entrance.enter_shed, RegionName.shed), - ConnectionData(Entrance.enter_slime_hutch, RegionName.slime_hutch), - ConnectionData(Entrance.use_desert_obelisk, RegionName.desert), - ConnectionData(Entrance.use_island_obelisk, RegionName.island_south, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.use_farm_obelisk, RegionName.farm), - ConnectionData(Entrance.backwoods_to_mountain, RegionName.mountain), - ConnectionData(Entrance.bus_stop_to_town, RegionName.town), - ConnectionData(Entrance.bus_stop_to_tunnel_entrance, RegionName.tunnel_entrance), - ConnectionData(Entrance.tunnel_entrance_to_bus_tunnel, RegionName.bus_tunnel, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData(Entrance.take_bus_to_desert, RegionName.desert), - ConnectionData(Entrance.forest_to_town, RegionName.town), - ConnectionData(Entrance.forest_to_wizard_tower, RegionName.wizard_tower, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_wizard_basement, RegionName.wizard_basement, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.forest_to_marnie_ranch, RegionName.ranch, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.forest_to_leah_cottage, RegionName.leah_house, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_secret_woods, RegionName.secret_woods), - ConnectionData(Entrance.forest_to_sewer, RegionName.sewer, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.forest_to_mastery_cave, RegionName.mastery_cave, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.MASTERIES), - ConnectionData(Entrance.town_to_sewer, RegionName.sewer, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_mutant_bug_lair, RegionName.mutant_bug_lair, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.mountain_to_railroad, RegionName.railroad), - ConnectionData(Entrance.mountain_to_tent, RegionName.tent, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.mountain_to_leo_treehouse, RegionName.leo_treehouse, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.mountain_to_carpenter_shop, RegionName.carpenter, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.mountain_to_maru_room, RegionName.maru_room, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_sebastian_room, RegionName.sebastian_room, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.mountain_to_adventurer_guild, RegionName.adventurer_guild, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.adventurer_guild_to_bedroom, RegionName.adventurer_guild_bedroom), - ConnectionData(Entrance.enter_quarry, RegionName.quarry), - ConnectionData(Entrance.enter_quarry_mine_entrance, RegionName.quarry_mine_entrance, - flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_quarry_mine, RegionName.quarry_mine), - ConnectionData(Entrance.mountain_to_town, RegionName.town), - ConnectionData(Entrance.town_to_community_center, RegionName.community_center, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.access_crafts_room, RegionName.crafts_room), - ConnectionData(Entrance.access_pantry, RegionName.pantry), - ConnectionData(Entrance.access_fish_tank, RegionName.fish_tank), - ConnectionData(Entrance.access_boiler_room, RegionName.boiler_room), - ConnectionData(Entrance.access_bulletin_board, RegionName.bulletin_board), - ConnectionData(Entrance.access_vault, RegionName.vault), - ConnectionData(Entrance.town_to_hospital, RegionName.hospital, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_harvey_room, RegionName.harvey_room, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.town_to_pierre_general_store, RegionName.pierre_store, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_sunroom, RegionName.sunroom, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.town_to_clint_blacksmith, RegionName.blacksmith, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_saloon, RegionName.saloon, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.play_journey_of_the_prairie_king, RegionName.jotpk_world_1), - ConnectionData(Entrance.reach_jotpk_world_2, RegionName.jotpk_world_2), - ConnectionData(Entrance.reach_jotpk_world_3, RegionName.jotpk_world_3), - ConnectionData(Entrance.play_junimo_kart, RegionName.junimo_kart_1), - ConnectionData(Entrance.reach_junimo_kart_2, RegionName.junimo_kart_2), - ConnectionData(Entrance.reach_junimo_kart_3, RegionName.junimo_kart_3), - ConnectionData(Entrance.reach_junimo_kart_4, RegionName.junimo_kart_4), - ConnectionData(Entrance.town_to_sam_house, RegionName.sam_house, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_haley_house, RegionName.haley_house, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_mayor_manor, RegionName.mayor_house, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_alex_house, RegionName.alex_house, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_trailer, RegionName.trailer, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_museum, RegionName.museum, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.town_to_jojamart, RegionName.jojamart, - flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.purchase_movie_ticket, RegionName.movie_ticket_stand), - ConnectionData(Entrance.enter_abandoned_jojamart, RegionName.abandoned_jojamart), - ConnectionData(Entrance.enter_movie_theater, RegionName.movie_theater), - ConnectionData(Entrance.town_to_beach, RegionName.beach), - ConnectionData(Entrance.enter_elliott_house, RegionName.elliott_house, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.beach_to_willy_fish_shop, RegionName.fish_shop, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.fish_shop_to_boat_tunnel, RegionName.boat_tunnel, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.boat_to_ginger_island, RegionName.island_south, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.enter_tide_pools, RegionName.tide_pools), - ConnectionData(Entrance.mountain_to_the_mines, RegionName.mines, - flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.dig_to_mines_floor_5, RegionName.mines_floor_5), - ConnectionData(Entrance.dig_to_mines_floor_10, RegionName.mines_floor_10), - ConnectionData(Entrance.dig_to_mines_floor_15, RegionName.mines_floor_15), - ConnectionData(Entrance.dig_to_mines_floor_20, RegionName.mines_floor_20), - ConnectionData(Entrance.dig_to_mines_floor_25, RegionName.mines_floor_25), - ConnectionData(Entrance.dig_to_mines_floor_30, RegionName.mines_floor_30), - ConnectionData(Entrance.dig_to_mines_floor_35, RegionName.mines_floor_35), - ConnectionData(Entrance.dig_to_mines_floor_40, RegionName.mines_floor_40), - ConnectionData(Entrance.dig_to_mines_floor_45, RegionName.mines_floor_45), - ConnectionData(Entrance.dig_to_mines_floor_50, RegionName.mines_floor_50), - ConnectionData(Entrance.dig_to_mines_floor_55, RegionName.mines_floor_55), - ConnectionData(Entrance.dig_to_mines_floor_60, RegionName.mines_floor_60), - ConnectionData(Entrance.dig_to_mines_floor_65, RegionName.mines_floor_65), - ConnectionData(Entrance.dig_to_mines_floor_70, RegionName.mines_floor_70), - ConnectionData(Entrance.dig_to_mines_floor_75, RegionName.mines_floor_75), - ConnectionData(Entrance.dig_to_mines_floor_80, RegionName.mines_floor_80), - ConnectionData(Entrance.dig_to_mines_floor_85, RegionName.mines_floor_85), - ConnectionData(Entrance.dig_to_mines_floor_90, RegionName.mines_floor_90), - ConnectionData(Entrance.dig_to_mines_floor_95, RegionName.mines_floor_95), - ConnectionData(Entrance.dig_to_mines_floor_100, RegionName.mines_floor_100), - ConnectionData(Entrance.dig_to_mines_floor_105, RegionName.mines_floor_105), - ConnectionData(Entrance.dig_to_mines_floor_110, RegionName.mines_floor_110), - ConnectionData(Entrance.dig_to_mines_floor_115, RegionName.mines_floor_115), - ConnectionData(Entrance.dig_to_mines_floor_120, RegionName.mines_floor_120), - ConnectionData(Entrance.dig_to_dangerous_mines_20, RegionName.dangerous_mines_20, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.dig_to_dangerous_mines_60, RegionName.dangerous_mines_60, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.dig_to_dangerous_mines_100, RegionName.dangerous_mines_100, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.enter_skull_cavern_entrance, RegionName.skull_cavern_entrance, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_oasis, RegionName.oasis, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_casino, RegionName.casino, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_skull_cavern, RegionName.skull_cavern), - ConnectionData(Entrance.mine_to_skull_cavern_floor_25, RegionName.skull_cavern_25), - ConnectionData(Entrance.mine_to_skull_cavern_floor_50, RegionName.skull_cavern_50), - ConnectionData(Entrance.mine_to_skull_cavern_floor_75, RegionName.skull_cavern_75), - ConnectionData(Entrance.mine_to_skull_cavern_floor_100, RegionName.skull_cavern_100), - ConnectionData(Entrance.mine_to_skull_cavern_floor_125, RegionName.skull_cavern_125), - ConnectionData(Entrance.mine_to_skull_cavern_floor_150, RegionName.skull_cavern_150), - ConnectionData(Entrance.mine_to_skull_cavern_floor_175, RegionName.skull_cavern_175), - ConnectionData(Entrance.mine_to_skull_cavern_floor_200, RegionName.skull_cavern_200), - ConnectionData(Entrance.enter_dangerous_skull_cavern, RegionName.dangerous_skull_cavern, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.enter_witch_warp_cave, RegionName.witch_warp_cave, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_witch_swamp, RegionName.witch_swamp, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_witch_hut, RegionName.witch_hut, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.witch_warp_to_wizard_basement, RegionName.wizard_basement, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_bathhouse_entrance, RegionName.bathhouse_entrance, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), - ConnectionData(Entrance.enter_locker_room, RegionName.locker_room, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.enter_public_bath, RegionName.public_bath, flag=RandomizationFlag.BUILDINGS), - ConnectionData(Entrance.island_south_to_west, RegionName.island_west, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_south_to_north, RegionName.island_north, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_south_to_east, RegionName.island_east, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_south_to_southeast, RegionName.island_south_east, - flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.use_island_resort, RegionName.island_resort, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_west_to_islandfarmhouse, RegionName.island_farmhouse, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_west_to_gourmand_cave, RegionName.gourmand_frog_cave, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_west_to_crystals_cave, RegionName.colored_crystals_cave, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_west_to_shipwreck, RegionName.shipwreck, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_west_to_qi_walnut_room, RegionName.qi_walnut_room, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_east_to_leo_hut, RegionName.leo_hut, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_east_to_island_shrine, RegionName.island_shrine, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_southeast_to_pirate_cove, RegionName.pirate_cove, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_north_to_field_office, RegionName.field_office, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_north_to_dig_site, RegionName.dig_site, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.dig_site_to_professor_snail_cave, RegionName.professor_snail_cave, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.island_north_to_volcano, RegionName.volcano, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.volcano_to_secret_beach, RegionName.volcano_secret_beach, - flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.talk_to_island_trader, RegionName.island_trader, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.climb_to_volcano_5, RegionName.volcano_floor_5, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.talk_to_volcano_dwarf, RegionName.volcano_dwarf_shop, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.climb_to_volcano_10, RegionName.volcano_floor_10, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_jungle_to_docks, RegionName.island_south, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_dig_site_to_docks, RegionName.island_south, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_volcano_to_docks, RegionName.island_south, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_volcano_to_jungle, RegionName.island_west, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_docks_to_jungle, RegionName.island_west, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_dig_site_to_jungle, RegionName.island_west, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_docks_to_dig_site, RegionName.dig_site, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_volcano_to_dig_site, RegionName.dig_site, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_jungle_to_dig_site, RegionName.dig_site, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_dig_site_to_volcano, RegionName.island_north, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_docks_to_volcano, RegionName.island_north, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(Entrance.parrot_express_jungle_to_volcano, RegionName.island_north, flag=RandomizationFlag.GINGER_ISLAND), - - ConnectionData(LogicEntrance.talk_to_mines_dwarf, LogicRegion.mines_dwarf_shop), - - ConnectionData(LogicEntrance.buy_from_traveling_merchant, LogicRegion.traveling_cart), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_sunday, LogicRegion.traveling_cart_sunday), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_monday, LogicRegion.traveling_cart_monday), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_tuesday, LogicRegion.traveling_cart_tuesday), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_wednesday, LogicRegion.traveling_cart_wednesday), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_thursday, LogicRegion.traveling_cart_thursday), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_friday, LogicRegion.traveling_cart_friday), - ConnectionData(LogicEntrance.buy_from_traveling_merchant_saturday, LogicRegion.traveling_cart_saturday), - ConnectionData(LogicEntrance.complete_raccoon_requests, LogicRegion.raccoon_daddy), - ConnectionData(LogicEntrance.fish_in_waterfall, LogicRegion.forest_waterfall), - ConnectionData(LogicEntrance.buy_from_raccoon, LogicRegion.raccoon_shop), - ConnectionData(LogicEntrance.farmhouse_cooking, LogicRegion.kitchen), - ConnectionData(LogicEntrance.watch_queen_of_sauce, LogicRegion.queen_of_sauce), - - ConnectionData(LogicEntrance.grow_spring_crops, LogicRegion.spring_farming), - ConnectionData(LogicEntrance.grow_summer_crops, LogicRegion.summer_farming), - ConnectionData(LogicEntrance.grow_fall_crops, LogicRegion.fall_farming), - ConnectionData(LogicEntrance.grow_winter_crops, LogicRegion.winter_farming), - ConnectionData(LogicEntrance.grow_spring_crops_in_greenhouse, LogicRegion.spring_farming), - ConnectionData(LogicEntrance.grow_summer_crops_in_greenhouse, LogicRegion.summer_farming), - ConnectionData(LogicEntrance.grow_fall_crops_in_greenhouse, LogicRegion.fall_farming), - ConnectionData(LogicEntrance.grow_winter_crops_in_greenhouse, LogicRegion.winter_farming), - ConnectionData(LogicEntrance.grow_indoor_crops_in_greenhouse, LogicRegion.indoor_farming), - ConnectionData(LogicEntrance.grow_spring_crops_on_island, LogicRegion.spring_farming, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(LogicEntrance.grow_summer_crops_on_island, LogicRegion.summer_farming, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(LogicEntrance.grow_fall_crops_on_island, LogicRegion.fall_farming, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(LogicEntrance.grow_winter_crops_on_island, LogicRegion.winter_farming, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(LogicEntrance.grow_indoor_crops_on_island, LogicRegion.indoor_farming, flag=RandomizationFlag.GINGER_ISLAND), - ConnectionData(LogicEntrance.grow_summer_fall_crops_in_summer, LogicRegion.summer_or_fall_farming), - ConnectionData(LogicEntrance.grow_summer_fall_crops_in_fall, LogicRegion.summer_or_fall_farming), - - ConnectionData(LogicEntrance.shipping, LogicRegion.shipping), - ConnectionData(LogicEntrance.blacksmith_copper, LogicRegion.blacksmith_copper), - ConnectionData(LogicEntrance.blacksmith_iron, LogicRegion.blacksmith_iron), - ConnectionData(LogicEntrance.blacksmith_gold, LogicRegion.blacksmith_gold), - ConnectionData(LogicEntrance.blacksmith_iridium, LogicRegion.blacksmith_iridium), - ConnectionData(LogicEntrance.fishing, LogicRegion.fishing), - ConnectionData(LogicEntrance.island_cooking, LogicRegion.kitchen), - ConnectionData(LogicEntrance.attend_egg_festival, LogicRegion.egg_festival), - ConnectionData(LogicEntrance.attend_desert_festival, LogicRegion.desert_festival), - ConnectionData(LogicEntrance.attend_flower_dance, LogicRegion.flower_dance), - ConnectionData(LogicEntrance.attend_luau, LogicRegion.luau), - ConnectionData(LogicEntrance.attend_trout_derby, LogicRegion.trout_derby), - ConnectionData(LogicEntrance.attend_moonlight_jellies, LogicRegion.moonlight_jellies), - ConnectionData(LogicEntrance.attend_fair, LogicRegion.fair), - ConnectionData(LogicEntrance.attend_spirit_eve, LogicRegion.spirit_eve), - ConnectionData(LogicEntrance.attend_festival_of_ice, LogicRegion.festival_of_ice), - ConnectionData(LogicEntrance.attend_night_market, LogicRegion.night_market), - ConnectionData(LogicEntrance.attend_winter_star, LogicRegion.winter_star), - ConnectionData(LogicEntrance.attend_squidfest, LogicRegion.squidfest), - ConnectionData(LogicEntrance.buy_experience_books, LogicRegion.bookseller_1), - ConnectionData(LogicEntrance.buy_year1_books, LogicRegion.bookseller_2), - ConnectionData(LogicEntrance.buy_year3_books, LogicRegion.bookseller_3), -] - - -def create_final_regions(world_options) -> List[RegionData]: - final_regions = [] - final_regions.extend(vanilla_regions) - if world_options.mods is None: - return final_regions - for mod in sorted(world_options.mods.value): - if mod not in ModDataList: - continue - for mod_region in ModDataList[mod].regions: - existing_region = next( - (region for region in final_regions if region.name == mod_region.name), None) - if existing_region: - final_regions.remove(existing_region) - if ModificationFlag.MODIFIED in mod_region.flag: - mod_region = modify_vanilla_regions(existing_region, mod_region) - final_regions.append(existing_region.get_merged_with(mod_region.exits)) - continue - final_regions.append(mod_region.get_clone()) - - return final_regions - - -def create_final_connections_and_regions(world_options) -> Tuple[Dict[str, ConnectionData], Dict[str, RegionData]]: - regions_data: Dict[str, RegionData] = {region.name: region for region in create_final_regions(world_options)} - connections = {connection.name: connection for connection in vanilla_connections} - connections = modify_connections_for_mods(connections, sorted(world_options.mods.value)) - include_island = world_options.exclude_ginger_island == ExcludeGingerIsland.option_false - return remove_ginger_island_regions_and_connections(regions_data, connections, include_island) - - -def remove_ginger_island_regions_and_connections(regions_by_name: Dict[str, RegionData], connections: Dict[str, ConnectionData], include_island: bool): - if include_island: - return connections, regions_by_name - - removed_connections = set() - - for connection_name in tuple(connections): - connection = connections[connection_name] - if connection.flag & RandomizationFlag.GINGER_ISLAND: - connections.pop(connection_name) - removed_connections.add(connection_name) - - for region_name in tuple(regions_by_name): - region = regions_by_name[region_name] - if region.is_ginger_island: - regions_by_name.pop(region_name) - else: - regions_by_name[region_name] = region.get_without_exits(removed_connections) - - return connections, regions_by_name - - -def modify_connections_for_mods(connections: Dict[str, ConnectionData], mods: Iterable) -> Dict[str, ConnectionData]: - for mod in mods: - if mod not in ModDataList: - continue - if mod in vanilla_connections_to_remove_by_mod: - for connection_data in vanilla_connections_to_remove_by_mod[mod]: - connections.pop(connection_data.name) - connections.update({connection.name: connection for connection in ModDataList[mod].connections}) - return connections - - -def modify_vanilla_regions(existing_region: RegionData, modified_region: RegionData) -> RegionData: - updated_region = existing_region - region_exits = updated_region.exits - modified_exits = modified_region.exits - for exits in modified_exits: - region_exits.remove(exits) - - return updated_region - - -def create_regions(region_factory: RegionFactory, random: Random, world_options: StardewValleyOptions, content: StardewContent) \ - -> Tuple[Dict[str, Region], Dict[str, Entrance], Dict[str, str]]: - entrances_data, regions_data = create_final_connections_and_regions(world_options) - regions_by_name: Dict[str: Region] = {region_name: region_factory(region_name, regions_data[region_name].exits) for region_name in regions_data} - entrances_by_name: Dict[str: Entrance] = { - entrance.name: entrance - for region in regions_by_name.values() - for entrance in region.exits - if entrance.name in entrances_data - } - - connections, randomized_data = randomize_connections(random, world_options, content, regions_data, entrances_data) - - for connection in connections: - if connection.name in entrances_by_name: - entrances_by_name[connection.name].connect(regions_by_name[connection.destination]) - return regions_by_name, entrances_by_name, randomized_data - - -def randomize_connections(random: Random, world_options: StardewValleyOptions, content: StardewContent, regions_by_name: Dict[str, RegionData], - connections_by_name: Dict[str, ConnectionData]) -> Tuple[List[ConnectionData], Dict[str, str]]: - connections_to_randomize: List[ConnectionData] = [] - if world_options.entrance_randomization == EntranceRandomization.option_pelican_town: - connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if - RandomizationFlag.PELICAN_TOWN in connections_by_name[connection].flag] - elif world_options.entrance_randomization == EntranceRandomization.option_non_progression: - connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if - RandomizationFlag.NON_PROGRESSION in connections_by_name[connection].flag] - elif world_options.entrance_randomization == EntranceRandomization.option_buildings or world_options.entrance_randomization == EntranceRandomization.option_buildings_without_house: - connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if - RandomizationFlag.BUILDINGS in connections_by_name[connection].flag] - elif world_options.entrance_randomization == EntranceRandomization.option_chaos: - connections_to_randomize = [connections_by_name[connection] for connection in connections_by_name if - RandomizationFlag.BUILDINGS in connections_by_name[connection].flag] - connections_to_randomize = remove_excluded_entrances(connections_to_randomize, content) - - # On Chaos, we just add the connections to randomize, unshuffled, and the client does it every day - randomized_data_for_mod = {} - for connection in connections_to_randomize: - randomized_data_for_mod[connection.name] = connection.name - randomized_data_for_mod[connection.reverse] = connection.reverse - return list(connections_by_name.values()), randomized_data_for_mod - - connections_to_randomize = remove_excluded_entrances(connections_to_randomize, content) - random.shuffle(connections_to_randomize) - destination_pool = list(connections_to_randomize) - random.shuffle(destination_pool) - - randomized_connections = randomize_chosen_connections(connections_to_randomize, destination_pool) - add_non_randomized_connections(list(connections_by_name.values()), connections_to_randomize, randomized_connections) - - swap_connections_until_valid(regions_by_name, connections_by_name, randomized_connections, connections_to_randomize, random) - randomized_connections_for_generation = create_connections_for_generation(randomized_connections) - randomized_data_for_mod = create_data_for_mod(randomized_connections, connections_to_randomize) - - return randomized_connections_for_generation, randomized_data_for_mod - - -def remove_excluded_entrances(connections_to_randomize: List[ConnectionData], content: StardewContent) -> List[ConnectionData]: - # FIXME remove when regions are handled in content packs - if content_packs.ginger_island_content_pack.name not in content.registered_packs: - connections_to_randomize = [connection for connection in connections_to_randomize if RandomizationFlag.GINGER_ISLAND not in connection.flag] - if not content.features.skill_progression.are_masteries_shuffled: - connections_to_randomize = [connection for connection in connections_to_randomize if RandomizationFlag.MASTERIES not in connection.flag] - - return connections_to_randomize - - -def randomize_chosen_connections(connections_to_randomize: List[ConnectionData], - destination_pool: List[ConnectionData]) -> Dict[ConnectionData, ConnectionData]: - randomized_connections = {} - for connection in connections_to_randomize: - destination = destination_pool.pop() - randomized_connections[connection] = destination - return randomized_connections - - -def create_connections_for_generation(randomized_connections: Dict[ConnectionData, ConnectionData]) -> List[ConnectionData]: - connections = [] - for connection in randomized_connections: - destination = randomized_connections[connection] - connections.append(ConnectionData(connection.name, destination.destination, destination.reverse)) - return connections - - -def create_data_for_mod(randomized_connections: Dict[ConnectionData, ConnectionData], - connections_to_randomize: List[ConnectionData]) -> Dict[str, str]: - randomized_data_for_mod = {} - for connection in randomized_connections: - if connection not in connections_to_randomize: - continue - destination = randomized_connections[connection] - add_to_mod_data(connection, destination, randomized_data_for_mod) - return randomized_data_for_mod - - -def add_to_mod_data(connection: ConnectionData, destination: ConnectionData, randomized_data_for_mod: Dict[str, str]): - randomized_data_for_mod[connection.name] = destination.name - randomized_data_for_mod[destination.reverse] = connection.reverse - - -def add_non_randomized_connections(all_connections: List[ConnectionData], connections_to_randomize: List[ConnectionData], - randomized_connections: Dict[ConnectionData, ConnectionData]): - for connection in all_connections: - if connection in connections_to_randomize: - continue - randomized_connections[connection] = connection - - -def swap_connections_until_valid(regions_by_name, connections_by_name: Dict[str, ConnectionData], randomized_connections: Dict[ConnectionData, ConnectionData], - connections_to_randomize: List[ConnectionData], random: Random): - while True: - reachable_regions, unreachable_regions = find_reachable_regions(regions_by_name, connections_by_name, randomized_connections) - if not unreachable_regions: - return randomized_connections - swap_one_random_connection(regions_by_name, connections_by_name, randomized_connections, reachable_regions, - unreachable_regions, connections_to_randomize, random) - - -def region_should_be_reachable(region_name: str, connections_in_slot: Iterable[ConnectionData]) -> bool: - if region_name == RegionName.menu: - return True - for connection in connections_in_slot: - if region_name == connection.destination: - return True - return False - - -def find_reachable_regions(regions_by_name, connections_by_name, - randomized_connections: Dict[ConnectionData, ConnectionData]): - reachable_regions = {RegionName.menu} - unreachable_regions = {region for region in regions_by_name.keys()} - # unreachable_regions = {region for region in regions_by_name.keys() if region_should_be_reachable(region, connections_by_name.values())} - unreachable_regions.remove(RegionName.menu) - exits_to_explore = list(regions_by_name[RegionName.menu].exits) - while exits_to_explore: - exit_name = exits_to_explore.pop() - # if exit_name not in connections_by_name: - # continue - exit_connection = connections_by_name[exit_name] - replaced_connection = randomized_connections[exit_connection] - target_region_name = replaced_connection.destination - if target_region_name in reachable_regions: - continue - - target_region = regions_by_name[target_region_name] - reachable_regions.add(target_region_name) - unreachable_regions.remove(target_region_name) - exits_to_explore.extend(target_region.exits) - return reachable_regions, unreachable_regions - - -def swap_one_random_connection(regions_by_name, connections_by_name, randomized_connections: Dict[ConnectionData, ConnectionData], - reachable_regions: Set[str], unreachable_regions: Set[str], - connections_to_randomize: List[ConnectionData], random: Random): - randomized_connections_already_shuffled = {connection: randomized_connections[connection] - for connection in randomized_connections - if connection != randomized_connections[connection]} - unreachable_regions_names_leading_somewhere = [region for region in sorted(unreachable_regions) if len(regions_by_name[region].exits) > 0] - unreachable_regions_leading_somewhere = [regions_by_name[region_name] for region_name in unreachable_regions_names_leading_somewhere] - unreachable_regions_exits_names = [exit_name for region in unreachable_regions_leading_somewhere for exit_name in region.exits] - unreachable_connections = [connections_by_name[exit_name] for exit_name in unreachable_regions_exits_names] - unreachable_connections_that_can_be_randomized = [connection for connection in unreachable_connections if connection in connections_to_randomize] - - chosen_unreachable_entrance = random.choice(unreachable_connections_that_can_be_randomized) - - chosen_reachable_entrance = None - while chosen_reachable_entrance is None or chosen_reachable_entrance not in randomized_connections_already_shuffled: - chosen_reachable_region_name = random.choice(sorted(reachable_regions)) - chosen_reachable_region = regions_by_name[chosen_reachable_region_name] - if not any(chosen_reachable_region.exits): - continue - chosen_reachable_entrance_name = random.choice(chosen_reachable_region.exits) - chosen_reachable_entrance = connections_by_name[chosen_reachable_entrance_name] - - swap_two_connections(chosen_reachable_entrance, chosen_unreachable_entrance, randomized_connections) - - -def swap_two_connections(entrance_1, entrance_2, randomized_connections): - reachable_destination = randomized_connections[entrance_1] - unreachable_destination = randomized_connections[entrance_2] - randomized_connections[entrance_1] = unreachable_destination - randomized_connections[entrance_2] = reachable_destination diff --git a/worlds/stardew_valley/regions/__init__.py b/worlds/stardew_valley/regions/__init__.py new file mode 100644 index 00000000..63e8afc2 --- /dev/null +++ b/worlds/stardew_valley/regions/__init__.py @@ -0,0 +1,2 @@ +from .entrance_rando import prepare_mod_data +from .regions import create_regions, RegionFactory diff --git a/worlds/stardew_valley/regions/entrance_rando.py b/worlds/stardew_valley/regions/entrance_rando.py new file mode 100644 index 00000000..7aa91685 --- /dev/null +++ b/worlds/stardew_valley/regions/entrance_rando.py @@ -0,0 +1,73 @@ +from BaseClasses import Region +from entrance_rando import ERPlacementState +from .model import ConnectionData, RandomizationFlag, reverse_connection_name, RegionData +from ..content import StardewContent +from ..options import EntranceRandomization + + +def create_player_randomization_flag(entrance_randomization_choice: EntranceRandomization, content: StardewContent): + """Return the flag that a connection is expected to have to be randomized. Only the bit corresponding to the player randomization choice will be enabled. + + Other bits for content exclusion might also be enabled, tho the preferred solution to exclude content should be to not create those regions at alls, when possible. + """ + flag = RandomizationFlag.NOT_RANDOMIZED + + if entrance_randomization_choice.value == EntranceRandomization.option_disabled: + return flag + + if entrance_randomization_choice == EntranceRandomization.option_pelican_town: + flag |= RandomizationFlag.BIT_PELICAN_TOWN + elif entrance_randomization_choice == EntranceRandomization.option_non_progression: + flag |= RandomizationFlag.BIT_NON_PROGRESSION + elif entrance_randomization_choice in ( + EntranceRandomization.option_buildings, + EntranceRandomization.option_buildings_without_house, + EntranceRandomization.option_chaos + ): + flag |= RandomizationFlag.BIT_BUILDINGS + + if not content.features.skill_progression.are_masteries_shuffled: + flag |= RandomizationFlag.EXCLUDE_MASTERIES + + return flag + + +def connect_regions(region_data_by_name: dict[str, RegionData], connection_data_by_name: dict[str, ConnectionData], regions_by_name: dict[str, Region], + player_randomization_flag: RandomizationFlag) -> None: + for region_name, region_data in region_data_by_name.items(): + origin_region = regions_by_name[region_name] + + for exit_name in region_data.exits: + connection_data = connection_data_by_name[exit_name] + destination_region = regions_by_name[connection_data.destination] + + if connection_data.is_eligible_for_randomization(player_randomization_flag): + create_entrance_rando_target(origin_region, destination_region, connection_data) + else: + origin_region.connect(destination_region, connection_data.name) + + +def create_entrance_rando_target(origin: Region, destination: Region, connection_data: ConnectionData) -> None: + """We need our own function to create the GER targets, because the Stardew Mod have very specific expectations for the name of the entrances. + We need to know exactly which entrances to swap in both directions.""" + origin.create_exit(connection_data.name) + destination.create_er_target(connection_data.reverse) + + +def prepare_mod_data(placements: ERPlacementState) -> dict[str, str]: + """Take the placements from GER and prepare the data for the mod. + The mod require a dictionary detailing which connections need to be swapped. It acts as if the connections are decoupled, so both directions are required. + + For instance, GER will provide placements like (Town to Community Center, Hospital to Town), meaning that the door of the Community Center will instead lead + to the Hospital, and that the exit of the Hospital will lead to the Town by the Community Center door. The StardewAP mod need to know both swaps, being the + original destination of the "Town to Community Center" connection is to be replaced by the original destination of "Town to Hospital", and the original + destination of "Hospital to Town" is to be replaced by the original destination of "Community Center to Town". + """ + + swapped_connections = {} + + for entrance, exit_ in placements.pairings: + swapped_connections[entrance] = reverse_connection_name(exit_) + swapped_connections[exit_] = reverse_connection_name(entrance) + + return swapped_connections diff --git a/worlds/stardew_valley/regions/model.py b/worlds/stardew_valley/regions/model.py new file mode 100644 index 00000000..07c39015 --- /dev/null +++ b/worlds/stardew_valley/regions/model.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from collections.abc import Container +from dataclasses import dataclass, field +from enum import IntFlag + +connector_keyword = " to " + + +def reverse_connection_name(name: str) -> str | None: + try: + origin, destination = name.split(connector_keyword) + except ValueError: + return None + return f"{destination}{connector_keyword}{origin}" + + +class MergeFlag(IntFlag): + ADD_EXITS = 0 + REMOVE_EXITS = 1 + + +class RandomizationFlag(IntFlag): + NOT_RANDOMIZED = 0 + + # Randomization options + # The first 4 bits are used to mark if an entrance is eligible for randomization according to the entrance randomization options. + BIT_PELICAN_TOWN = 1 # 0b0001 + BIT_NON_PROGRESSION = 1 << 1 # 0b0010 + BIT_BUILDINGS = 1 << 2 # 0b0100 + BIT_EVERYTHING = 1 << 3 # 0b1000 + + # Content flag for entrances exclusions + # The next 2 bits are used to mark if an entrance is to be excluded from randomization according to the content options. + # Those bits must be removed from an entrance flags when then entrance must be excluded. + __UNUSED = 1 << 4 # 0b010000 + EXCLUDE_MASTERIES = 1 << 5 # 0b100000 + + # Entrance groups + # The last bit is used to add additional qualifiers on entrances to group them + # Those bits should be added when an entrance need additional qualifiers. + LEAD_TO_OPEN_AREA = 1 << 6 + + # Tags to apply on connections + EVERYTHING = EXCLUDE_MASTERIES | BIT_EVERYTHING + BUILDINGS = EVERYTHING | BIT_BUILDINGS + NON_PROGRESSION = BUILDINGS | BIT_NON_PROGRESSION + PELICAN_TOWN = NON_PROGRESSION | BIT_PELICAN_TOWN + + +@dataclass(frozen=True) +class RegionData: + name: str + exits: tuple[str, ...] = field(default_factory=tuple) + flag: MergeFlag = MergeFlag.ADD_EXITS + + def __post_init__(self): + assert not isinstance(self.exits, str), "Exits must be a tuple of strings, you probably forgot a trailing comma." + + def merge_with(self, other: RegionData) -> RegionData: + assert self.name == other.name, "Regions must have the same name to be merged" + + if other.flag == MergeFlag.REMOVE_EXITS: + return self.get_without_exits(other.exits) + + merged_exits = self.exits + other.exits + assert len(merged_exits) == len(set(merged_exits)), "Two regions getting merged have duplicated exists..." + + return RegionData(self.name, merged_exits) + + def get_without_exits(self, exits_to_remove: Container[str]) -> RegionData: + exits = tuple(exit_ for exit_ in self.exits if exit_ not in exits_to_remove) + return RegionData(self.name, exits) + + +@dataclass(frozen=True) +class ConnectionData: + name: str + destination: str + flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED + + @property + def reverse(self) -> str | None: + return reverse_connection_name(self.name) + + def is_eligible_for_randomization(self, chosen_randomization_flag: RandomizationFlag) -> bool: + return chosen_randomization_flag and chosen_randomization_flag in self.flag + + +@dataclass(frozen=True) +class ModRegionsData: + mod_name: str + regions: list[RegionData] + connections: list[ConnectionData] diff --git a/worlds/stardew_valley/regions/mods.py b/worlds/stardew_valley/regions/mods.py new file mode 100644 index 00000000..fca54619 --- /dev/null +++ b/worlds/stardew_valley/regions/mods.py @@ -0,0 +1,46 @@ +from collections.abc import Iterable + +from .model import ConnectionData, RegionData, ModRegionsData +from ..mods.region_data import region_data_by_content_pack, vanilla_connections_to_remove_by_content_pack + + +def modify_regions_for_mods(current_regions_by_name: dict[str, RegionData], active_content_packs: Iterable[str]) -> None: + for content_pack in active_content_packs: + try: + region_data = region_data_by_content_pack[content_pack] + except KeyError: + continue + + merge_mod_regions(current_regions_by_name, region_data) + + +def merge_mod_regions(current_regions_by_name: dict[str, RegionData], mod_region_data: ModRegionsData) -> None: + for new_region in mod_region_data.regions: + region_name = new_region.name + try: + current_region = current_regions_by_name[region_name] + except KeyError: + current_regions_by_name[region_name] = new_region + continue + + current_regions_by_name[region_name] = current_region.merge_with(new_region) + + +def modify_connections_for_mods(connections: dict[str, ConnectionData], active_mods: Iterable[str]) -> None: + for active_mod in active_mods: + try: + region_data = region_data_by_content_pack[active_mod] + except KeyError: + continue + + try: + vanilla_connections_to_remove = vanilla_connections_to_remove_by_content_pack[active_mod] + for connection_name in vanilla_connections_to_remove: + connections.pop(connection_name) + except KeyError: + pass + + connections.update({ + connection.name: connection + for connection in region_data.connections + }) diff --git a/worlds/stardew_valley/regions/regions.py b/worlds/stardew_valley/regions/regions.py new file mode 100644 index 00000000..ceaec5b2 --- /dev/null +++ b/worlds/stardew_valley/regions/regions.py @@ -0,0 +1,61 @@ +from typing import Protocol + +from BaseClasses import Region +from . import vanilla_data, mods +from .entrance_rando import create_player_randomization_flag, connect_regions +from .model import ConnectionData, RegionData +from ..content import StardewContent +from ..content.vanilla.ginger_island import ginger_island_content_pack +from ..options import StardewValleyOptions + + +class RegionFactory(Protocol): + def __call__(self, name: str) -> Region: + raise NotImplementedError + + +def create_regions(region_factory: RegionFactory, world_options: StardewValleyOptions, content: StardewContent) -> dict[str, Region]: + connection_data_by_name, region_data_by_name = create_connections_and_regions(content.registered_packs) + + regions_by_name: dict[str: Region] = { + region_name: region_factory(region_name) + for region_name in region_data_by_name + } + + randomization_flag = create_player_randomization_flag(world_options.entrance_randomization, content) + connect_regions(region_data_by_name, connection_data_by_name, regions_by_name, randomization_flag) + + return regions_by_name + + +def create_connections_and_regions(active_content_packs: set[str]) -> tuple[dict[str, ConnectionData], dict[str, RegionData]]: + regions_by_name = create_all_regions(active_content_packs) + connections_by_name = create_all_connections(active_content_packs) + + return connections_by_name, regions_by_name + + +def create_all_regions(active_content_packs: set[str]) -> dict[str, RegionData]: + current_regions_by_name = create_vanilla_regions(active_content_packs) + mods.modify_regions_for_mods(current_regions_by_name, sorted(active_content_packs)) + return current_regions_by_name + + +def create_vanilla_regions(active_content_packs: set[str]) -> dict[str, RegionData]: + if ginger_island_content_pack.name in active_content_packs: + return {**vanilla_data.regions_with_ginger_island_by_name} + else: + return {**vanilla_data.regions_without_ginger_island_by_name} + + +def create_all_connections(active_content_packs: set[str]) -> dict[str, ConnectionData]: + connections = create_vanilla_connections(active_content_packs) + mods.modify_connections_for_mods(connections, sorted(active_content_packs)) + return connections + + +def create_vanilla_connections(active_content_packs: set[str]) -> dict[str, ConnectionData]: + if ginger_island_content_pack.name in active_content_packs: + return {**vanilla_data.connections_with_ginger_island_by_name} + else: + return {**vanilla_data.connections_without_ginger_island_by_name} diff --git a/worlds/stardew_valley/regions/vanilla_data.py b/worlds/stardew_valley/regions/vanilla_data.py new file mode 100644 index 00000000..dbb83e10 --- /dev/null +++ b/worlds/stardew_valley/regions/vanilla_data.py @@ -0,0 +1,522 @@ +from collections.abc import Mapping +from types import MappingProxyType + +from .model import ConnectionData, RandomizationFlag, RegionData +from ..strings.entrance_names import LogicEntrance, Entrance +from ..strings.region_names import LogicRegion, Region as RegionName + +vanilla_regions: tuple[RegionData, ...] = ( + RegionData(RegionName.menu, (Entrance.to_stardew_valley,)), + RegionData(RegionName.stardew_valley, (Entrance.to_farmhouse,)), + RegionData(RegionName.farm_house, + (Entrance.farmhouse_to_farm, Entrance.downstairs_to_cellar, LogicEntrance.farmhouse_cooking, LogicEntrance.watch_queen_of_sauce)), + RegionData(RegionName.cellar), + RegionData(RegionName.farm, + (Entrance.farm_to_backwoods, Entrance.farm_to_bus_stop, Entrance.farm_to_forest, Entrance.farm_to_farmcave, Entrance.enter_greenhouse, + Entrance.enter_coop, Entrance.enter_barn, Entrance.enter_shed, Entrance.enter_slime_hutch, LogicEntrance.grow_spring_crops, + LogicEntrance.grow_summer_crops, LogicEntrance.grow_fall_crops, LogicEntrance.grow_winter_crops, LogicEntrance.shipping, + LogicEntrance.fishing,)), + RegionData(RegionName.backwoods, (Entrance.backwoods_to_mountain,)), + RegionData(RegionName.bus_stop, + (Entrance.bus_stop_to_town, Entrance.take_bus_to_desert, Entrance.bus_stop_to_tunnel_entrance)), + RegionData(RegionName.forest, + (Entrance.forest_to_town, Entrance.enter_secret_woods, Entrance.forest_to_wizard_tower, Entrance.forest_to_marnie_ranch, + Entrance.forest_to_leah_cottage, Entrance.forest_to_sewer, Entrance.forest_to_mastery_cave, LogicEntrance.buy_from_traveling_merchant, + LogicEntrance.complete_raccoon_requests, LogicEntrance.fish_in_waterfall, LogicEntrance.attend_flower_dance, LogicEntrance.attend_trout_derby, + LogicEntrance.attend_festival_of_ice)), + RegionData(LogicRegion.forest_waterfall), + RegionData(RegionName.farm_cave), + RegionData(RegionName.greenhouse, + (LogicEntrance.grow_spring_crops_in_greenhouse, LogicEntrance.grow_summer_crops_in_greenhouse, LogicEntrance.grow_fall_crops_in_greenhouse, + LogicEntrance.grow_winter_crops_in_greenhouse, LogicEntrance.grow_indoor_crops_in_greenhouse)), + RegionData(RegionName.mountain, + (Entrance.mountain_to_railroad, Entrance.mountain_to_tent, Entrance.mountain_to_carpenter_shop, + Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild, + Entrance.mountain_to_town, Entrance.mountain_to_maru_room)), + RegionData(RegionName.maru_room), + RegionData(RegionName.tunnel_entrance, (Entrance.tunnel_entrance_to_bus_tunnel,)), + RegionData(RegionName.bus_tunnel), + RegionData(RegionName.town, + (Entrance.town_to_community_center, Entrance.town_to_beach, Entrance.town_to_hospital, Entrance.town_to_pierre_general_store, + Entrance.town_to_saloon, Entrance.town_to_alex_house, Entrance.town_to_trailer, Entrance.town_to_mayor_manor, Entrance.town_to_sam_house, + Entrance.town_to_haley_house, Entrance.town_to_sewer, Entrance.town_to_clint_blacksmith, Entrance.town_to_museum, Entrance.town_to_jojamart, + Entrance.purchase_movie_ticket, LogicEntrance.buy_experience_books, LogicEntrance.attend_egg_festival, LogicEntrance.attend_fair, + LogicEntrance.attend_spirit_eve, LogicEntrance.attend_winter_star)), + RegionData(RegionName.beach, + (Entrance.beach_to_willy_fish_shop, Entrance.enter_elliott_house, Entrance.enter_tide_pools, LogicEntrance.attend_luau, + LogicEntrance.attend_moonlight_jellies, LogicEntrance.attend_night_market, LogicEntrance.attend_squidfest)), + RegionData(RegionName.railroad, (Entrance.enter_bathhouse_entrance, Entrance.enter_witch_warp_cave)), + RegionData(RegionName.ranch), + RegionData(RegionName.leah_house), + RegionData(RegionName.mastery_cave), + RegionData(RegionName.sewer, (Entrance.enter_mutant_bug_lair,)), + RegionData(RegionName.mutant_bug_lair), + RegionData(RegionName.wizard_tower, (Entrance.enter_wizard_basement, Entrance.use_desert_obelisk)), + RegionData(RegionName.wizard_basement), + RegionData(RegionName.tent), + RegionData(RegionName.carpenter, (Entrance.enter_sebastian_room,)), + RegionData(RegionName.sebastian_room), + RegionData(RegionName.adventurer_guild, (Entrance.adventurer_guild_to_bedroom,)), + RegionData(RegionName.adventurer_guild_bedroom), + RegionData(RegionName.community_center, + (Entrance.access_crafts_room, Entrance.access_pantry, Entrance.access_fish_tank, + Entrance.access_boiler_room, Entrance.access_bulletin_board, Entrance.access_vault)), + RegionData(RegionName.crafts_room), + RegionData(RegionName.pantry), + RegionData(RegionName.fish_tank), + RegionData(RegionName.boiler_room), + RegionData(RegionName.bulletin_board), + RegionData(RegionName.vault), + RegionData(RegionName.hospital, (Entrance.enter_harvey_room,)), + RegionData(RegionName.harvey_room), + RegionData(RegionName.pierre_store, (Entrance.enter_sunroom,)), + RegionData(RegionName.sunroom), + RegionData(RegionName.saloon, (Entrance.play_journey_of_the_prairie_king, Entrance.play_junimo_kart)), + RegionData(RegionName.jotpk_world_1, (Entrance.reach_jotpk_world_2,)), + RegionData(RegionName.jotpk_world_2, (Entrance.reach_jotpk_world_3,)), + RegionData(RegionName.jotpk_world_3), + RegionData(RegionName.junimo_kart_1, (Entrance.reach_junimo_kart_2,)), + RegionData(RegionName.junimo_kart_2, (Entrance.reach_junimo_kart_3,)), + RegionData(RegionName.junimo_kart_3, (Entrance.reach_junimo_kart_4,)), + RegionData(RegionName.junimo_kart_4), + RegionData(RegionName.alex_house), + RegionData(RegionName.trailer), + RegionData(RegionName.mayor_house), + RegionData(RegionName.sam_house), + RegionData(RegionName.haley_house), + RegionData(RegionName.blacksmith, (LogicEntrance.blacksmith_copper,)), + RegionData(RegionName.museum), + RegionData(RegionName.jojamart, (Entrance.enter_abandoned_jojamart,)), + RegionData(RegionName.abandoned_jojamart, (Entrance.enter_movie_theater,)), + RegionData(RegionName.movie_ticket_stand), + RegionData(RegionName.movie_theater), + RegionData(RegionName.fish_shop), + RegionData(RegionName.elliott_house), + RegionData(RegionName.tide_pools), + RegionData(RegionName.bathhouse_entrance, (Entrance.enter_locker_room,)), + RegionData(RegionName.locker_room, (Entrance.enter_public_bath,)), + RegionData(RegionName.public_bath), + RegionData(RegionName.witch_warp_cave, (Entrance.enter_witch_swamp,)), + RegionData(RegionName.witch_swamp, (Entrance.enter_witch_hut,)), + RegionData(RegionName.witch_hut, (Entrance.witch_warp_to_wizard_basement,)), + RegionData(RegionName.quarry, (Entrance.enter_quarry_mine_entrance,)), + RegionData(RegionName.quarry_mine_entrance, (Entrance.enter_quarry_mine,)), + RegionData(RegionName.quarry_mine), + RegionData(RegionName.secret_woods), + RegionData(RegionName.desert, (Entrance.enter_skull_cavern_entrance, Entrance.enter_oasis, LogicEntrance.attend_desert_festival)), + RegionData(RegionName.oasis, (Entrance.enter_casino,)), + RegionData(RegionName.casino), + RegionData(RegionName.skull_cavern_entrance, (Entrance.enter_skull_cavern,)), + RegionData(RegionName.skull_cavern, (Entrance.mine_to_skull_cavern_floor_25,)), + RegionData(RegionName.skull_cavern_25, (Entrance.mine_to_skull_cavern_floor_50,)), + RegionData(RegionName.skull_cavern_50, (Entrance.mine_to_skull_cavern_floor_75,)), + RegionData(RegionName.skull_cavern_75, (Entrance.mine_to_skull_cavern_floor_100,)), + RegionData(RegionName.skull_cavern_100, (Entrance.mine_to_skull_cavern_floor_125,)), + RegionData(RegionName.skull_cavern_125, (Entrance.mine_to_skull_cavern_floor_150,)), + RegionData(RegionName.skull_cavern_150, (Entrance.mine_to_skull_cavern_floor_175,)), + RegionData(RegionName.skull_cavern_175, (Entrance.mine_to_skull_cavern_floor_200,)), + RegionData(RegionName.skull_cavern_200), + + RegionData(RegionName.coop), + RegionData(RegionName.barn), + RegionData(RegionName.shed), + RegionData(RegionName.slime_hutch), + + RegionData(RegionName.mines, (LogicEntrance.talk_to_mines_dwarf, Entrance.dig_to_mines_floor_5)), + RegionData(RegionName.mines_floor_5, (Entrance.dig_to_mines_floor_10,)), + RegionData(RegionName.mines_floor_10, (Entrance.dig_to_mines_floor_15,)), + RegionData(RegionName.mines_floor_15, (Entrance.dig_to_mines_floor_20,)), + RegionData(RegionName.mines_floor_20, (Entrance.dig_to_mines_floor_25,)), + RegionData(RegionName.mines_floor_25, (Entrance.dig_to_mines_floor_30,)), + RegionData(RegionName.mines_floor_30, (Entrance.dig_to_mines_floor_35,)), + RegionData(RegionName.mines_floor_35, (Entrance.dig_to_mines_floor_40,)), + RegionData(RegionName.mines_floor_40, (Entrance.dig_to_mines_floor_45,)), + RegionData(RegionName.mines_floor_45, (Entrance.dig_to_mines_floor_50,)), + RegionData(RegionName.mines_floor_50, (Entrance.dig_to_mines_floor_55,)), + RegionData(RegionName.mines_floor_55, (Entrance.dig_to_mines_floor_60,)), + RegionData(RegionName.mines_floor_60, (Entrance.dig_to_mines_floor_65,)), + RegionData(RegionName.mines_floor_65, (Entrance.dig_to_mines_floor_70,)), + RegionData(RegionName.mines_floor_70, (Entrance.dig_to_mines_floor_75,)), + RegionData(RegionName.mines_floor_75, (Entrance.dig_to_mines_floor_80,)), + RegionData(RegionName.mines_floor_80, (Entrance.dig_to_mines_floor_85,)), + RegionData(RegionName.mines_floor_85, (Entrance.dig_to_mines_floor_90,)), + RegionData(RegionName.mines_floor_90, (Entrance.dig_to_mines_floor_95,)), + RegionData(RegionName.mines_floor_95, (Entrance.dig_to_mines_floor_100,)), + RegionData(RegionName.mines_floor_100, (Entrance.dig_to_mines_floor_105,)), + RegionData(RegionName.mines_floor_105, (Entrance.dig_to_mines_floor_110,)), + RegionData(RegionName.mines_floor_110, (Entrance.dig_to_mines_floor_115,)), + RegionData(RegionName.mines_floor_115, (Entrance.dig_to_mines_floor_120,)), + RegionData(RegionName.mines_floor_120), + + RegionData(LogicRegion.mines_dwarf_shop), + RegionData(LogicRegion.blacksmith_copper, (LogicEntrance.blacksmith_iron,)), + RegionData(LogicRegion.blacksmith_iron, (LogicEntrance.blacksmith_gold,)), + RegionData(LogicRegion.blacksmith_gold, (LogicEntrance.blacksmith_iridium,)), + RegionData(LogicRegion.blacksmith_iridium), + RegionData(LogicRegion.kitchen), + RegionData(LogicRegion.queen_of_sauce), + RegionData(LogicRegion.fishing), + + RegionData(LogicRegion.spring_farming), + RegionData(LogicRegion.summer_farming, (LogicEntrance.grow_summer_fall_crops_in_summer,)), + RegionData(LogicRegion.fall_farming, (LogicEntrance.grow_summer_fall_crops_in_fall,)), + RegionData(LogicRegion.winter_farming), + RegionData(LogicRegion.summer_or_fall_farming), + RegionData(LogicRegion.indoor_farming), + + RegionData(LogicRegion.shipping), + RegionData(LogicRegion.traveling_cart, (LogicEntrance.buy_from_traveling_merchant_sunday, + LogicEntrance.buy_from_traveling_merchant_monday, + LogicEntrance.buy_from_traveling_merchant_tuesday, + LogicEntrance.buy_from_traveling_merchant_wednesday, + LogicEntrance.buy_from_traveling_merchant_thursday, + LogicEntrance.buy_from_traveling_merchant_friday, + LogicEntrance.buy_from_traveling_merchant_saturday)), + RegionData(LogicRegion.traveling_cart_sunday), + RegionData(LogicRegion.traveling_cart_monday), + RegionData(LogicRegion.traveling_cart_tuesday), + RegionData(LogicRegion.traveling_cart_wednesday), + RegionData(LogicRegion.traveling_cart_thursday), + RegionData(LogicRegion.traveling_cart_friday), + RegionData(LogicRegion.traveling_cart_saturday), + RegionData(LogicRegion.raccoon_daddy, (LogicEntrance.buy_from_raccoon,)), + RegionData(LogicRegion.raccoon_shop), + + RegionData(LogicRegion.egg_festival), + RegionData(LogicRegion.desert_festival), + RegionData(LogicRegion.flower_dance), + RegionData(LogicRegion.luau), + RegionData(LogicRegion.trout_derby), + RegionData(LogicRegion.moonlight_jellies), + RegionData(LogicRegion.fair), + RegionData(LogicRegion.spirit_eve), + RegionData(LogicRegion.festival_of_ice), + RegionData(LogicRegion.night_market), + RegionData(LogicRegion.winter_star), + RegionData(LogicRegion.squidfest), + RegionData(LogicRegion.bookseller_1, (LogicEntrance.buy_year1_books,)), + RegionData(LogicRegion.bookseller_2, (LogicEntrance.buy_year3_books,)), + RegionData(LogicRegion.bookseller_3), +) +ginger_island_regions = ( + # This overrides the regions from vanilla... When regions are moved to content packs, overriding existing entrances should no longer be necessary. + RegionData(RegionName.mountain, + (Entrance.mountain_to_railroad, Entrance.mountain_to_tent, Entrance.mountain_to_carpenter_shop, + Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild, + Entrance.mountain_to_town, Entrance.mountain_to_maru_room, Entrance.mountain_to_leo_treehouse)), + RegionData(RegionName.wizard_tower, (Entrance.enter_wizard_basement, Entrance.use_desert_obelisk, Entrance.use_island_obelisk,)), + RegionData(RegionName.fish_shop, (Entrance.fish_shop_to_boat_tunnel,)), + RegionData(RegionName.mines_floor_120, (Entrance.dig_to_dangerous_mines_20, Entrance.dig_to_dangerous_mines_60, Entrance.dig_to_dangerous_mines_100)), + RegionData(RegionName.skull_cavern_200, (Entrance.enter_dangerous_skull_cavern,)), + + RegionData(RegionName.leo_treehouse), + RegionData(RegionName.boat_tunnel, (Entrance.boat_to_ginger_island,)), + RegionData(RegionName.dangerous_skull_cavern), + RegionData(RegionName.island_south, + (Entrance.island_south_to_west, Entrance.island_south_to_north, Entrance.island_south_to_east, Entrance.island_south_to_southeast, + Entrance.use_island_resort, Entrance.parrot_express_docks_to_volcano, Entrance.parrot_express_docks_to_dig_site, + Entrance.parrot_express_docks_to_jungle), ), + RegionData(RegionName.island_resort), + RegionData(RegionName.island_west, + (Entrance.island_west_to_islandfarmhouse, Entrance.island_west_to_gourmand_cave, Entrance.island_west_to_crystals_cave, + Entrance.island_west_to_shipwreck, Entrance.island_west_to_qi_walnut_room, Entrance.use_farm_obelisk, Entrance.parrot_express_jungle_to_docks, + Entrance.parrot_express_jungle_to_dig_site, Entrance.parrot_express_jungle_to_volcano, LogicEntrance.grow_spring_crops_on_island, + LogicEntrance.grow_summer_crops_on_island, LogicEntrance.grow_fall_crops_on_island, LogicEntrance.grow_winter_crops_on_island, + LogicEntrance.grow_indoor_crops_on_island), ), + RegionData(RegionName.island_east, (Entrance.island_east_to_leo_hut, Entrance.island_east_to_island_shrine)), + RegionData(RegionName.island_shrine), + RegionData(RegionName.island_south_east, (Entrance.island_southeast_to_pirate_cove,)), + RegionData(RegionName.island_north, + (Entrance.talk_to_island_trader, Entrance.island_north_to_field_office, Entrance.island_north_to_dig_site, Entrance.island_north_to_volcano, + Entrance.parrot_express_volcano_to_dig_site, Entrance.parrot_express_volcano_to_jungle, Entrance.parrot_express_volcano_to_docks), ), + RegionData(RegionName.volcano, (Entrance.climb_to_volcano_5, Entrance.volcano_to_secret_beach)), + RegionData(RegionName.volcano_secret_beach), + RegionData(RegionName.volcano_floor_5, (Entrance.talk_to_volcano_dwarf, Entrance.climb_to_volcano_10)), + RegionData(RegionName.volcano_dwarf_shop), + RegionData(RegionName.volcano_floor_10), + RegionData(RegionName.island_trader), + RegionData(RegionName.island_farmhouse, (LogicEntrance.island_cooking,)), + RegionData(RegionName.gourmand_frog_cave), + RegionData(RegionName.colored_crystals_cave), + RegionData(RegionName.shipwreck), + RegionData(RegionName.qi_walnut_room), + RegionData(RegionName.leo_hut), + RegionData(RegionName.pirate_cove), + RegionData(RegionName.field_office), + RegionData(RegionName.dig_site, + (Entrance.dig_site_to_professor_snail_cave, Entrance.parrot_express_dig_site_to_volcano, + Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_dig_site_to_jungle), ), + + RegionData(RegionName.professor_snail_cave), + RegionData(RegionName.dangerous_mines_20), + RegionData(RegionName.dangerous_mines_60), + RegionData(RegionName.dangerous_mines_100), +) + +# Exists and where they lead +vanilla_connections: tuple[ConnectionData, ...] = ( + ConnectionData(Entrance.to_stardew_valley, RegionName.stardew_valley), + ConnectionData(Entrance.to_farmhouse, RegionName.farm_house), + ConnectionData(Entrance.farmhouse_to_farm, RegionName.farm), + ConnectionData(Entrance.downstairs_to_cellar, RegionName.cellar), + ConnectionData(Entrance.farm_to_backwoods, RegionName.backwoods), + ConnectionData(Entrance.farm_to_bus_stop, RegionName.bus_stop), + ConnectionData(Entrance.farm_to_forest, RegionName.forest), + ConnectionData(Entrance.farm_to_farmcave, RegionName.farm_cave, flag=RandomizationFlag.NON_PROGRESSION), + ConnectionData(Entrance.enter_greenhouse, RegionName.greenhouse), + ConnectionData(Entrance.enter_coop, RegionName.coop), + ConnectionData(Entrance.enter_barn, RegionName.barn), + ConnectionData(Entrance.enter_shed, RegionName.shed), + ConnectionData(Entrance.enter_slime_hutch, RegionName.slime_hutch), + ConnectionData(Entrance.use_desert_obelisk, RegionName.desert), + ConnectionData(Entrance.backwoods_to_mountain, RegionName.mountain), + ConnectionData(Entrance.bus_stop_to_town, RegionName.town), + ConnectionData(Entrance.bus_stop_to_tunnel_entrance, RegionName.tunnel_entrance), + ConnectionData(Entrance.tunnel_entrance_to_bus_tunnel, RegionName.bus_tunnel, flag=RandomizationFlag.NON_PROGRESSION), + ConnectionData(Entrance.take_bus_to_desert, RegionName.desert), + ConnectionData(Entrance.forest_to_town, RegionName.town), + ConnectionData(Entrance.forest_to_wizard_tower, RegionName.wizard_tower, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_wizard_basement, RegionName.wizard_basement, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.forest_to_marnie_ranch, RegionName.ranch, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.forest_to_leah_cottage, RegionName.leah_house, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_secret_woods, RegionName.secret_woods), + ConnectionData(Entrance.forest_to_sewer, RegionName.sewer, flag=RandomizationFlag.BUILDINGS), + # We remove the bit for masteries, because the mastery cave is to be excluded from the randomization if masteries are not shuffled. + ConnectionData(Entrance.forest_to_mastery_cave, RegionName.mastery_cave, flag=RandomizationFlag.BUILDINGS ^ RandomizationFlag.EXCLUDE_MASTERIES), + ConnectionData(Entrance.town_to_sewer, RegionName.sewer, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_mutant_bug_lair, RegionName.mutant_bug_lair, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.mountain_to_railroad, RegionName.railroad), + ConnectionData(Entrance.mountain_to_tent, RegionName.tent, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.mountain_to_carpenter_shop, RegionName.carpenter, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.mountain_to_maru_room, RegionName.maru_room, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_sebastian_room, RegionName.sebastian_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.mountain_to_adventurer_guild, RegionName.adventurer_guild, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.adventurer_guild_to_bedroom, RegionName.adventurer_guild_bedroom), + ConnectionData(Entrance.enter_quarry, RegionName.quarry), + ConnectionData(Entrance.enter_quarry_mine_entrance, RegionName.quarry_mine_entrance, + flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_quarry_mine, RegionName.quarry_mine), + ConnectionData(Entrance.mountain_to_town, RegionName.town), + ConnectionData(Entrance.town_to_community_center, RegionName.community_center, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.access_crafts_room, RegionName.crafts_room), + ConnectionData(Entrance.access_pantry, RegionName.pantry), + ConnectionData(Entrance.access_fish_tank, RegionName.fish_tank), + ConnectionData(Entrance.access_boiler_room, RegionName.boiler_room), + ConnectionData(Entrance.access_bulletin_board, RegionName.bulletin_board), + ConnectionData(Entrance.access_vault, RegionName.vault), + ConnectionData(Entrance.town_to_hospital, RegionName.hospital, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_harvey_room, RegionName.harvey_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.town_to_pierre_general_store, RegionName.pierre_store, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_sunroom, RegionName.sunroom, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.town_to_clint_blacksmith, RegionName.blacksmith, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_saloon, RegionName.saloon, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.play_journey_of_the_prairie_king, RegionName.jotpk_world_1), + ConnectionData(Entrance.reach_jotpk_world_2, RegionName.jotpk_world_2), + ConnectionData(Entrance.reach_jotpk_world_3, RegionName.jotpk_world_3), + ConnectionData(Entrance.play_junimo_kart, RegionName.junimo_kart_1), + ConnectionData(Entrance.reach_junimo_kart_2, RegionName.junimo_kart_2), + ConnectionData(Entrance.reach_junimo_kart_3, RegionName.junimo_kart_3), + ConnectionData(Entrance.reach_junimo_kart_4, RegionName.junimo_kart_4), + ConnectionData(Entrance.town_to_sam_house, RegionName.sam_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_haley_house, RegionName.haley_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_mayor_manor, RegionName.mayor_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_alex_house, RegionName.alex_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_trailer, RegionName.trailer, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_museum, RegionName.museum, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_jojamart, RegionName.jojamart, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.purchase_movie_ticket, RegionName.movie_ticket_stand), + ConnectionData(Entrance.enter_abandoned_jojamart, RegionName.abandoned_jojamart), + ConnectionData(Entrance.enter_movie_theater, RegionName.movie_theater), + ConnectionData(Entrance.town_to_beach, RegionName.beach), + ConnectionData(Entrance.enter_elliott_house, RegionName.elliott_house, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.beach_to_willy_fish_shop, RegionName.fish_shop, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_tide_pools, RegionName.tide_pools), + ConnectionData(Entrance.mountain_to_the_mines, RegionName.mines, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.dig_to_mines_floor_5, RegionName.mines_floor_5), + ConnectionData(Entrance.dig_to_mines_floor_10, RegionName.mines_floor_10), + ConnectionData(Entrance.dig_to_mines_floor_15, RegionName.mines_floor_15), + ConnectionData(Entrance.dig_to_mines_floor_20, RegionName.mines_floor_20), + ConnectionData(Entrance.dig_to_mines_floor_25, RegionName.mines_floor_25), + ConnectionData(Entrance.dig_to_mines_floor_30, RegionName.mines_floor_30), + ConnectionData(Entrance.dig_to_mines_floor_35, RegionName.mines_floor_35), + ConnectionData(Entrance.dig_to_mines_floor_40, RegionName.mines_floor_40), + ConnectionData(Entrance.dig_to_mines_floor_45, RegionName.mines_floor_45), + ConnectionData(Entrance.dig_to_mines_floor_50, RegionName.mines_floor_50), + ConnectionData(Entrance.dig_to_mines_floor_55, RegionName.mines_floor_55), + ConnectionData(Entrance.dig_to_mines_floor_60, RegionName.mines_floor_60), + ConnectionData(Entrance.dig_to_mines_floor_65, RegionName.mines_floor_65), + ConnectionData(Entrance.dig_to_mines_floor_70, RegionName.mines_floor_70), + ConnectionData(Entrance.dig_to_mines_floor_75, RegionName.mines_floor_75), + ConnectionData(Entrance.dig_to_mines_floor_80, RegionName.mines_floor_80), + ConnectionData(Entrance.dig_to_mines_floor_85, RegionName.mines_floor_85), + ConnectionData(Entrance.dig_to_mines_floor_90, RegionName.mines_floor_90), + ConnectionData(Entrance.dig_to_mines_floor_95, RegionName.mines_floor_95), + ConnectionData(Entrance.dig_to_mines_floor_100, RegionName.mines_floor_100), + ConnectionData(Entrance.dig_to_mines_floor_105, RegionName.mines_floor_105), + ConnectionData(Entrance.dig_to_mines_floor_110, RegionName.mines_floor_110), + ConnectionData(Entrance.dig_to_mines_floor_115, RegionName.mines_floor_115), + ConnectionData(Entrance.dig_to_mines_floor_120, RegionName.mines_floor_120), + ConnectionData(Entrance.enter_skull_cavern_entrance, RegionName.skull_cavern_entrance, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_oasis, RegionName.oasis, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_casino, RegionName.casino, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_skull_cavern, RegionName.skull_cavern), + ConnectionData(Entrance.mine_to_skull_cavern_floor_25, RegionName.skull_cavern_25), + ConnectionData(Entrance.mine_to_skull_cavern_floor_50, RegionName.skull_cavern_50), + ConnectionData(Entrance.mine_to_skull_cavern_floor_75, RegionName.skull_cavern_75), + ConnectionData(Entrance.mine_to_skull_cavern_floor_100, RegionName.skull_cavern_100), + ConnectionData(Entrance.mine_to_skull_cavern_floor_125, RegionName.skull_cavern_125), + ConnectionData(Entrance.mine_to_skull_cavern_floor_150, RegionName.skull_cavern_150), + ConnectionData(Entrance.mine_to_skull_cavern_floor_175, RegionName.skull_cavern_175), + ConnectionData(Entrance.mine_to_skull_cavern_floor_200, RegionName.skull_cavern_200), + ConnectionData(Entrance.enter_witch_warp_cave, RegionName.witch_warp_cave, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_witch_swamp, RegionName.witch_swamp, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_witch_hut, RegionName.witch_hut, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.witch_warp_to_wizard_basement, RegionName.wizard_basement, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_bathhouse_entrance, RegionName.bathhouse_entrance, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_locker_room, RegionName.locker_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_public_bath, RegionName.public_bath, flag=RandomizationFlag.BUILDINGS), + ConnectionData(LogicEntrance.talk_to_mines_dwarf, LogicRegion.mines_dwarf_shop), + + ConnectionData(LogicEntrance.buy_from_traveling_merchant, LogicRegion.traveling_cart), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_sunday, LogicRegion.traveling_cart_sunday), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_monday, LogicRegion.traveling_cart_monday), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_tuesday, LogicRegion.traveling_cart_tuesday), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_wednesday, LogicRegion.traveling_cart_wednesday), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_thursday, LogicRegion.traveling_cart_thursday), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_friday, LogicRegion.traveling_cart_friday), + ConnectionData(LogicEntrance.buy_from_traveling_merchant_saturday, LogicRegion.traveling_cart_saturday), + ConnectionData(LogicEntrance.complete_raccoon_requests, LogicRegion.raccoon_daddy), + ConnectionData(LogicEntrance.fish_in_waterfall, LogicRegion.forest_waterfall), + ConnectionData(LogicEntrance.buy_from_raccoon, LogicRegion.raccoon_shop), + ConnectionData(LogicEntrance.farmhouse_cooking, LogicRegion.kitchen), + ConnectionData(LogicEntrance.watch_queen_of_sauce, LogicRegion.queen_of_sauce), + + ConnectionData(LogicEntrance.grow_spring_crops, LogicRegion.spring_farming), + ConnectionData(LogicEntrance.grow_summer_crops, LogicRegion.summer_farming), + ConnectionData(LogicEntrance.grow_fall_crops, LogicRegion.fall_farming), + ConnectionData(LogicEntrance.grow_winter_crops, LogicRegion.winter_farming), + ConnectionData(LogicEntrance.grow_spring_crops_in_greenhouse, LogicRegion.spring_farming), + ConnectionData(LogicEntrance.grow_summer_crops_in_greenhouse, LogicRegion.summer_farming), + ConnectionData(LogicEntrance.grow_fall_crops_in_greenhouse, LogicRegion.fall_farming), + ConnectionData(LogicEntrance.grow_winter_crops_in_greenhouse, LogicRegion.winter_farming), + ConnectionData(LogicEntrance.grow_indoor_crops_in_greenhouse, LogicRegion.indoor_farming), + ConnectionData(LogicEntrance.grow_summer_fall_crops_in_summer, LogicRegion.summer_or_fall_farming), + ConnectionData(LogicEntrance.grow_summer_fall_crops_in_fall, LogicRegion.summer_or_fall_farming), + + ConnectionData(LogicEntrance.shipping, LogicRegion.shipping), + ConnectionData(LogicEntrance.blacksmith_copper, LogicRegion.blacksmith_copper), + ConnectionData(LogicEntrance.blacksmith_iron, LogicRegion.blacksmith_iron), + ConnectionData(LogicEntrance.blacksmith_gold, LogicRegion.blacksmith_gold), + ConnectionData(LogicEntrance.blacksmith_iridium, LogicRegion.blacksmith_iridium), + ConnectionData(LogicEntrance.fishing, LogicRegion.fishing), + ConnectionData(LogicEntrance.attend_egg_festival, LogicRegion.egg_festival), + ConnectionData(LogicEntrance.attend_desert_festival, LogicRegion.desert_festival), + ConnectionData(LogicEntrance.attend_flower_dance, LogicRegion.flower_dance), + ConnectionData(LogicEntrance.attend_luau, LogicRegion.luau), + ConnectionData(LogicEntrance.attend_trout_derby, LogicRegion.trout_derby), + ConnectionData(LogicEntrance.attend_moonlight_jellies, LogicRegion.moonlight_jellies), + ConnectionData(LogicEntrance.attend_fair, LogicRegion.fair), + ConnectionData(LogicEntrance.attend_spirit_eve, LogicRegion.spirit_eve), + ConnectionData(LogicEntrance.attend_festival_of_ice, LogicRegion.festival_of_ice), + ConnectionData(LogicEntrance.attend_night_market, LogicRegion.night_market), + ConnectionData(LogicEntrance.attend_winter_star, LogicRegion.winter_star), + ConnectionData(LogicEntrance.attend_squidfest, LogicRegion.squidfest), + ConnectionData(LogicEntrance.buy_experience_books, LogicRegion.bookseller_1), + ConnectionData(LogicEntrance.buy_year1_books, LogicRegion.bookseller_2), + ConnectionData(LogicEntrance.buy_year3_books, LogicRegion.bookseller_3), +) + +ginger_island_connections = ( + ConnectionData(Entrance.use_island_obelisk, RegionName.island_south), + ConnectionData(Entrance.use_farm_obelisk, RegionName.farm), + ConnectionData(Entrance.mountain_to_leo_treehouse, RegionName.leo_treehouse, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.fish_shop_to_boat_tunnel, RegionName.boat_tunnel, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.boat_to_ginger_island, RegionName.island_south), + ConnectionData(Entrance.enter_dangerous_skull_cavern, RegionName.dangerous_skull_cavern), + ConnectionData(Entrance.dig_to_dangerous_mines_20, RegionName.dangerous_mines_20), + ConnectionData(Entrance.dig_to_dangerous_mines_60, RegionName.dangerous_mines_60), + ConnectionData(Entrance.dig_to_dangerous_mines_100, RegionName.dangerous_mines_100), + ConnectionData(Entrance.island_south_to_west, RegionName.island_west), + ConnectionData(Entrance.island_south_to_north, RegionName.island_north), + ConnectionData(Entrance.island_south_to_east, RegionName.island_east), + ConnectionData(Entrance.island_south_to_southeast, RegionName.island_south_east), + ConnectionData(Entrance.use_island_resort, RegionName.island_resort), + ConnectionData(Entrance.island_west_to_islandfarmhouse, RegionName.island_farmhouse, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_west_to_gourmand_cave, RegionName.gourmand_frog_cave, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_west_to_crystals_cave, RegionName.colored_crystals_cave, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_west_to_shipwreck, RegionName.shipwreck, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_west_to_qi_walnut_room, RegionName.qi_walnut_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_east_to_leo_hut, RegionName.leo_hut, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_east_to_island_shrine, RegionName.island_shrine, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_southeast_to_pirate_cove, RegionName.pirate_cove, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_north_to_field_office, RegionName.field_office, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_north_to_dig_site, RegionName.dig_site), + ConnectionData(Entrance.dig_site_to_professor_snail_cave, RegionName.professor_snail_cave, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_north_to_volcano, RegionName.volcano, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.volcano_to_secret_beach, RegionName.volcano_secret_beach, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.talk_to_island_trader, RegionName.island_trader), + ConnectionData(Entrance.climb_to_volcano_5, RegionName.volcano_floor_5), + ConnectionData(Entrance.talk_to_volcano_dwarf, RegionName.volcano_dwarf_shop), + ConnectionData(Entrance.climb_to_volcano_10, RegionName.volcano_floor_10), + ConnectionData(Entrance.parrot_express_jungle_to_docks, RegionName.island_south), + ConnectionData(Entrance.parrot_express_dig_site_to_docks, RegionName.island_south), + ConnectionData(Entrance.parrot_express_volcano_to_docks, RegionName.island_south), + ConnectionData(Entrance.parrot_express_volcano_to_jungle, RegionName.island_west), + ConnectionData(Entrance.parrot_express_docks_to_jungle, RegionName.island_west), + ConnectionData(Entrance.parrot_express_dig_site_to_jungle, RegionName.island_west), + ConnectionData(Entrance.parrot_express_docks_to_dig_site, RegionName.dig_site), + ConnectionData(Entrance.parrot_express_volcano_to_dig_site, RegionName.dig_site), + ConnectionData(Entrance.parrot_express_jungle_to_dig_site, RegionName.dig_site), + ConnectionData(Entrance.parrot_express_dig_site_to_volcano, RegionName.island_north), + ConnectionData(Entrance.parrot_express_docks_to_volcano, RegionName.island_north), + ConnectionData(Entrance.parrot_express_jungle_to_volcano, RegionName.island_north), + ConnectionData(LogicEntrance.grow_spring_crops_on_island, LogicRegion.spring_farming), + ConnectionData(LogicEntrance.grow_summer_crops_on_island, LogicRegion.summer_farming), + ConnectionData(LogicEntrance.grow_fall_crops_on_island, LogicRegion.fall_farming), + ConnectionData(LogicEntrance.grow_winter_crops_on_island, LogicRegion.winter_farming), + ConnectionData(LogicEntrance.grow_indoor_crops_on_island, LogicRegion.indoor_farming), + ConnectionData(LogicEntrance.island_cooking, LogicRegion.kitchen), +) + +connections_without_ginger_island_by_name: Mapping[str, ConnectionData] = MappingProxyType({ + connection.name: connection + for connection in vanilla_connections +}) +regions_without_ginger_island_by_name: Mapping[str, RegionData] = MappingProxyType({ + region.name: region + for region in vanilla_regions +}) + +connections_with_ginger_island_by_name: Mapping[str, ConnectionData] = MappingProxyType({ + connection.name: connection + for connection in vanilla_connections + ginger_island_connections +}) +regions_with_ginger_island_by_name: Mapping[str, RegionData] = MappingProxyType({ + region.name: region + for region in vanilla_regions + ginger_island_regions +}) diff --git a/worlds/stardew_valley/test/TestRegions.py b/worlds/stardew_valley/test/TestRegions.py deleted file mode 100644 index 07e3094f..00000000 --- a/worlds/stardew_valley/test/TestRegions.py +++ /dev/null @@ -1,173 +0,0 @@ -import random -import unittest -from typing import Set - -from BaseClasses import get_seed -from .bases import SVTestCase -from .options.utils import fill_dataclass_with_default -from .. import create_content -from ..options import EntranceRandomization, ExcludeGingerIsland, SkillProgression -from ..regions import vanilla_regions, vanilla_connections, randomize_connections, RandomizationFlag, create_final_connections_and_regions -from ..strings.entrance_names import Entrance as EntranceName -from ..strings.region_names import Region as RegionName - -connections_by_name = {connection.name for connection in vanilla_connections} -regions_by_name = {region.name for region in vanilla_regions} - - -class TestRegions(unittest.TestCase): - def test_region_exits_lead_somewhere(self): - for region in vanilla_regions: - with self.subTest(region=region): - for exit in region.exits: - self.assertIn(exit, connections_by_name, - f"{region.name} is leading to {exit} but it does not exist.") - - def test_connection_lead_somewhere(self): - for connection in vanilla_connections: - with self.subTest(connection=connection): - self.assertIn(connection.destination, regions_by_name, - f"{connection.name} is leading to {connection.destination} but it does not exist.") - - -def explore_connections_tree_up_to_blockers(blocked_entrances: Set[str], connections_by_name, regions_by_name): - explored_entrances = set() - explored_regions = set() - entrances_to_explore = set() - current_node_name = "Menu" - current_node = regions_by_name[current_node_name] - entrances_to_explore.update(current_node.exits) - while entrances_to_explore: - current_entrance_name = entrances_to_explore.pop() - current_entrance = connections_by_name[current_entrance_name] - current_node_name = current_entrance.destination - - explored_entrances.add(current_entrance_name) - explored_regions.add(current_node_name) - - if current_entrance_name in blocked_entrances: - continue - - current_node = regions_by_name[current_node_name] - entrances_to_explore.update({entrance for entrance in current_node.exits if entrance not in explored_entrances}) - return explored_regions - - -class TestEntranceRando(SVTestCase): - - def test_entrance_randomization(self): - for option, flag in [(EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), - (EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), - (EntranceRandomization.option_buildings_without_house, RandomizationFlag.BUILDINGS), - (EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: - sv_options = fill_dataclass_with_default({ - EntranceRandomization.internal_name: option, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false, - SkillProgression.internal_name: SkillProgression.option_progressive_with_masteries, - }) - content = create_content(sv_options) - seed = get_seed() - rand = random.Random(seed) - with self.subTest(flag=flag, msg=f"Seed: {seed}"): - entrances, regions = create_final_connections_and_regions(sv_options) - _, randomized_connections = randomize_connections(rand, sv_options, content, regions, entrances) - - for connection in vanilla_connections: - if flag in connection.flag: - connection_in_randomized = connection.name in randomized_connections - reverse_in_randomized = connection.reverse in randomized_connections - self.assertTrue(connection_in_randomized, f"Connection {connection.name} should be randomized but it is not in the output.") - self.assertTrue(reverse_in_randomized, f"Connection {connection.reverse} should be randomized but it is not in the output.") - - self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), - f"Connections are duplicated in randomization.") - - def test_entrance_randomization_without_island(self): - for option, flag in [(EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), - (EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), - (EntranceRandomization.option_buildings_without_house, RandomizationFlag.BUILDINGS), - (EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: - - sv_options = fill_dataclass_with_default({ - EntranceRandomization.internal_name: option, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true, - SkillProgression.internal_name: SkillProgression.option_progressive_with_masteries, - }) - content = create_content(sv_options) - seed = get_seed() - rand = random.Random(seed) - with self.subTest(option=option, flag=flag, seed=seed): - entrances, regions = create_final_connections_and_regions(sv_options) - _, randomized_connections = randomize_connections(rand, sv_options, content, regions, entrances) - - for connection in vanilla_connections: - if flag in connection.flag: - if RandomizationFlag.GINGER_ISLAND in connection.flag: - self.assertNotIn(connection.name, randomized_connections, - f"Connection {connection.name} should not be randomized but it is in the output.") - self.assertNotIn(connection.reverse, randomized_connections, - f"Connection {connection.reverse} should not be randomized but it is in the output.") - else: - self.assertIn(connection.name, randomized_connections, - f"Connection {connection.name} should be randomized but it is not in the output.") - self.assertIn(connection.reverse, randomized_connections, - f"Connection {connection.reverse} should be randomized but it is not in the output.") - - self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), - f"Connections are duplicated in randomization.") - - def test_cannot_put_island_access_on_island(self): - sv_options = fill_dataclass_with_default({ - EntranceRandomization.internal_name: EntranceRandomization.option_buildings, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_false, - SkillProgression.internal_name: SkillProgression.option_progressive_with_masteries, - }) - content = create_content(sv_options) - - for i in range(0, 100 if self.skip_long_tests else 10000): - seed = get_seed() - rand = random.Random(seed) - with self.subTest(msg=f"Seed: {seed}"): - entrances, regions = create_final_connections_and_regions(sv_options) - randomized_connections, randomized_data = randomize_connections(rand, sv_options, content, regions, entrances) - connections_by_name = {connection.name: connection for connection in randomized_connections} - - blocked_entrances = {EntranceName.use_island_obelisk, EntranceName.boat_to_ginger_island} - required_regions = {RegionName.wizard_tower, RegionName.boat_tunnel} - self.assert_can_reach_any_region_before_blockers(required_regions, blocked_entrances, connections_by_name, regions) - - def assert_can_reach_any_region_before_blockers(self, required_regions, blocked_entrances, connections_by_name, regions_by_name): - explored_regions = explore_connections_tree_up_to_blockers(blocked_entrances, connections_by_name, regions_by_name) - self.assertTrue(any(region in explored_regions for region in required_regions)) - - -class TestEntranceClassifications(SVTestCase): - - def test_non_progression_are_all_accessible_with_empty_inventory(self): - for option, flag in [(EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), - (EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION)]: - world_options = { - EntranceRandomization.internal_name: option - } - with self.solo_world_sub_test(world_options=world_options, flag=flag) as (multiworld, sv_world): - ap_entrances = {entrance.name: entrance for entrance in multiworld.get_entrances()} - for randomized_entrance in sv_world.randomized_entrances: - if randomized_entrance in ap_entrances: - ap_entrance_origin = ap_entrances[randomized_entrance] - self.assertTrue(ap_entrance_origin.access_rule(multiworld.state)) - if sv_world.randomized_entrances[randomized_entrance] in ap_entrances: - ap_entrance_destination = multiworld.get_entrance(sv_world.randomized_entrances[randomized_entrance], 1) - self.assertTrue(ap_entrance_destination.access_rule(multiworld.state)) - - def test_no_ginger_island_entrances_when_excluded(self): - world_options = { - EntranceRandomization.internal_name: EntranceRandomization.option_disabled, - ExcludeGingerIsland.internal_name: ExcludeGingerIsland.option_true - } - with self.solo_world_sub_test(world_options=world_options) as (multiworld, _): - ap_entrances = {entrance.name: entrance for entrance in multiworld.get_entrances()} - entrance_data_by_name = {entrance.name: entrance for entrance in vanilla_connections} - for entrance_name in ap_entrances: - entrance_data = entrance_data_by_name[entrance_name] - with self.subTest(f"{entrance_name}: {entrance_data.flag}"): - self.assertFalse(entrance_data.flag & RandomizationFlag.GINGER_ISLAND) diff --git a/worlds/stardew_valley/test/assertion/rule_assert.py b/worlds/stardew_valley/test/assertion/rule_assert.py index 02362f2d..39b69a52 100644 --- a/worlds/stardew_valley/test/assertion/rule_assert.py +++ b/worlds/stardew_valley/test/assertion/rule_assert.py @@ -1,7 +1,7 @@ from typing import List from unittest import TestCase -from BaseClasses import CollectionState, Location, Region +from BaseClasses import CollectionState, Location, Region, Entrance from ...stardew_rule import StardewRule, false_, MISSING_ITEM, Reach from ...stardew_rule.rule_explain import explain @@ -79,3 +79,13 @@ class RuleAssertMixin(TestCase): except KeyError as e: raise AssertionError(f"Error while checking region {region_name}: {e}" f"\nExplanation: {expl}") + + def assert_can_reach_entrance(self, entrance: Entrance | str, state: CollectionState) -> None: + entrance_name = entrance.name if isinstance(entrance, Entrance) else entrance + expl = explain(Reach(entrance_name, "Entrance", 1), state) + try: + can_reach = state.can_reach_entrance(entrance_name, 1) + self.assertTrue(can_reach, expl) + except KeyError as e: + raise AssertionError(f"Error while checking entrance {entrance_name}: {e}" + f"\nExplanation: {expl}") diff --git a/worlds/stardew_valley/test/bases.py b/worlds/stardew_valley/test/bases.py index affc20cd..a2852183 100644 --- a/worlds/stardew_valley/test/bases.py +++ b/worlds/stardew_valley/test/bases.py @@ -7,7 +7,7 @@ import unittest from contextlib import contextmanager from typing import Optional, Dict, Union, Any, List, Iterable -from BaseClasses import get_seed, MultiWorld, Location, Item, CollectionState +from BaseClasses import get_seed, MultiWorld, Location, Item, CollectionState, Entrance from test.bases import WorldTestBase from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld from worlds.AutoWorld import call_all @@ -179,6 +179,11 @@ class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase): state = self.multiworld.state super().assert_cannot_reach_location(location, state) + def assert_can_reach_entrance(self, entrance: Entrance | str, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_can_reach_entrance(entrance, state) + pre_generated_worlds = {} diff --git a/worlds/stardew_valley/test/mods/TestMods.py b/worlds/stardew_valley/test/mods/TestMods.py index be6ce710..8cff10b4 100644 --- a/worlds/stardew_valley/test/mods/TestMods.py +++ b/worlds/stardew_valley/test/mods/TestMods.py @@ -1,17 +1,13 @@ -import random from typing import ClassVar -from BaseClasses import get_seed from test.param import classvar_matrix from ..TestGeneration import get_all_permanent_progression_items from ..assertion import ModAssertMixin, WorldAssertMixin from ..bases import SVTestCase, SVTestBase, solo_multiworld from ..options.presets import allsanity_mods_6_x_x -from ..options.utils import fill_dataclass_with_default -from ... import options, Group, create_content +from ... import options, Group from ...mods.mod_data import ModNames from ...options.options import all_mods -from ...regions import RandomizationFlag, randomize_connections, create_final_connections_and_regions class TestCanGenerateAllsanityWithMods(WorldAssertMixin, ModAssertMixin, SVTestCase): @@ -117,39 +113,6 @@ class TestNoGingerIslandModItemGeneration(SVTestBase): self.assertIn(progression_item.name, all_created_items) -class TestModEntranceRando(SVTestCase): - - def test_mod_entrance_randomization(self): - for option, flag in [(options.EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), - (options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), - (options.EntranceRandomization.option_buildings_without_house, RandomizationFlag.BUILDINGS), - (options.EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: - sv_options = fill_dataclass_with_default({ - options.EntranceRandomization.internal_name: option, - options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, - options.SkillProgression.internal_name: options.SkillProgression.option_progressive_with_masteries, - options.Mods.internal_name: frozenset(options.Mods.valid_keys) - }) - content = create_content(sv_options) - seed = get_seed() - rand = random.Random(seed) - with self.subTest(option=option, flag=flag, seed=seed): - final_connections, final_regions = create_final_connections_and_regions(sv_options) - - _, randomized_connections = randomize_connections(rand, sv_options, content, final_regions, final_connections) - - for connection_name in final_connections: - connection = final_connections[connection_name] - if flag in connection.flag: - connection_in_randomized = connection_name in randomized_connections - reverse_in_randomized = connection.reverse in randomized_connections - self.assertTrue(connection_in_randomized, f"Connection {connection_name} should be randomized but it is not in the output") - self.assertTrue(reverse_in_randomized, f"Connection {connection.reverse} should be randomized but it is not in the output.") - - self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), - f"Connections are duplicated in randomization.") - - class TestVanillaLogicAlternativeWhenQuestsAreNotRandomized(WorldAssertMixin, SVTestBase): """We often forget to add an alternative rule that works when quests are not randomized. When this happens, some Location are not reachable because they depend on items that are only added to the pool when quests are randomized. diff --git a/worlds/stardew_valley/test/regions/TestEntranceClassifications.py b/worlds/stardew_valley/test/regions/TestEntranceClassifications.py new file mode 100644 index 00000000..43a70904 --- /dev/null +++ b/worlds/stardew_valley/test/regions/TestEntranceClassifications.py @@ -0,0 +1,36 @@ +from ..bases import SVTestBase +from ... import options +from ...regions.model import RandomizationFlag +from ...regions.regions import create_all_connections + + +class EntranceRandomizationAssertMixin: + + def assert_non_progression_are_all_accessible_with_empty_inventory(self: SVTestBase): + all_connections = create_all_connections(self.world.content.registered_packs) + non_progression_connections = [connection for connection in all_connections.values() if RandomizationFlag.BIT_NON_PROGRESSION in connection.flag] + + for non_progression_connections in non_progression_connections: + with self.subTest(connection=non_progression_connections): + self.assert_can_reach_entrance(non_progression_connections.name) + + +# This test does not actually need to generate with entrance randomization. Entrances rules are the same regardless of the randomization. +class TestVanillaEntranceClassifications(EntranceRandomizationAssertMixin, SVTestBase): + options = { + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false, + options.Mods: frozenset() + } + + def test_non_progression_are_all_accessible_with_empty_inventory(self): + self.assert_non_progression_are_all_accessible_with_empty_inventory() + + +class TestModdedEntranceClassifications(EntranceRandomizationAssertMixin, SVTestBase): + options = { + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false, + options.Mods: frozenset(options.Mods.valid_keys) + } + + def test_non_progression_are_all_accessible_with_empty_inventory(self): + self.assert_non_progression_are_all_accessible_with_empty_inventory() diff --git a/worlds/stardew_valley/test/regions/TestEntranceRandomization.py b/worlds/stardew_valley/test/regions/TestEntranceRandomization.py new file mode 100644 index 00000000..15c46637 --- /dev/null +++ b/worlds/stardew_valley/test/regions/TestEntranceRandomization.py @@ -0,0 +1,167 @@ +from collections import deque +from collections.abc import Collection +from unittest.mock import patch, Mock + +from BaseClasses import get_seed, MultiWorld, Entrance +from ..assertion import WorldAssertMixin +from ..bases import SVTestCase, solo_multiworld +from ... import options +from ...mods.mod_data import ModNames +from ...options import EntranceRandomization, ExcludeGingerIsland, SkillProgression +from ...options.options import all_mods +from ...regions.entrance_rando import create_entrance_rando_target, prepare_mod_data, connect_regions +from ...regions.model import RegionData, ConnectionData, RandomizationFlag +from ...strings.entrance_names import Entrance as EntranceName +from ...strings.region_names import Region as RegionName + + +class TestEntranceRando(SVTestCase): + + def test_given_connection_matching_randomization_when_connect_regions_then_make_connection_entrance_rando_target(self): + region_data_by_name = { + "Region1": RegionData("Region1", ("randomized_connection", "not_randomized")), + "Region2": RegionData("Region2"), + "Region3": RegionData("Region3"), + } + connection_data_by_name = { + "randomized_connection": ConnectionData("randomized_connection", "Region2", flag=RandomizationFlag.PELICAN_TOWN), + "not_randomized": ConnectionData("not_randomized", "Region2", flag=RandomizationFlag.BUILDINGS), + } + regions_by_name = { + "Region1": Mock(), + "Region2": Mock(), + "Region3": Mock(), + } + player_randomization_flag = RandomizationFlag.BIT_PELICAN_TOWN + + with patch("worlds.stardew_valley.regions.entrance_rando.create_entrance_rando_target") as mock_create_entrance_rando_target: + connect_regions(region_data_by_name, connection_data_by_name, regions_by_name, player_randomization_flag) + + expected_origin, expected_destination = regions_by_name["Region1"], regions_by_name["Region2"] + expected_connection = connection_data_by_name["randomized_connection"] + mock_create_entrance_rando_target.assert_called_once_with(expected_origin, expected_destination, expected_connection) + + def test_when_create_entrance_rando_target_then_create_exit_and_er_target(self): + origin = Mock() + destination = Mock() + connection_data = ConnectionData("origin to destination", "destination") + + create_entrance_rando_target(origin, destination, connection_data) + + origin.create_exit.assert_called_once_with("origin to destination") + destination.create_er_target.assert_called_once_with("destination to origin") + + def test_when_prepare_mod_data_then_swapped_connections_contains_both_directions(self): + placements = Mock(pairings=[("A to B", "C to A"), ("C to D", "A to C")]) + + swapped_connections = prepare_mod_data(placements) + + self.assertEqual({"A to B": "A to C", "C to A": "B to A", "C to D": "C to A", "A to C": "D to C"}, swapped_connections) + + +class TestEntranceRandoCreatesValidWorlds(WorldAssertMixin, SVTestCase): + + # The following tests validate that ER still generates winnable and logically-sane games with given mods. + # Mods that do not interact with entrances are skipped + # Not all ER settings are tested, because 'buildings' is, essentially, a superset of all others + def test_ginger_island_excluded_buildings(self): + world_options = { + options.EntranceRandomization: options.EntranceRandomization.option_buildings, + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_true + } + with solo_multiworld(world_options) as (multi_world, _): + self.assert_basic_checks(multi_world) + + def test_deepwoods_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.deepwoods, options.EntranceRandomization.option_buildings) + + def test_juna_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.juna, options.EntranceRandomization.option_buildings) + + def test_jasper_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.jasper, options.EntranceRandomization.option_buildings) + + def test_alec_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.alec, options.EntranceRandomization.option_buildings) + + def test_yoba_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.yoba, options.EntranceRandomization.option_buildings) + + def test_eugene_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.eugene, options.EntranceRandomization.option_buildings) + + def test_ayeisha_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.ayeisha, options.EntranceRandomization.option_buildings) + + def test_riley_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.riley, options.EntranceRandomization.option_buildings) + + def test_sve_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.sve, options.EntranceRandomization.option_buildings) + + def test_alecto_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.alecto, options.EntranceRandomization.option_buildings) + + def test_lacey_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.lacey, options.EntranceRandomization.option_buildings) + + def test_boarding_house_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(ModNames.boarding_house, options.EntranceRandomization.option_buildings) + + def test_all_mods_entrance_randomization_buildings(self): + self.perform_basic_checks_on_mod_with_er(all_mods, options.EntranceRandomization.option_buildings) + + def perform_basic_checks_on_mod_with_er(self, mods: str | set[str], er_option: int) -> None: + if isinstance(mods, str): + mods = {mods} + world_options = { + options.EntranceRandomization: er_option, + options.Mods: frozenset(mods), + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false + } + with solo_multiworld(world_options) as (multi_world, _): + self.assert_basic_checks(multi_world) + + +# GER should have this covered, but it's good to have a backup +class TestGingerIslandEntranceRando(SVTestCase): + def test_cannot_put_island_access_on_island(self): + test_options = { + options.EntranceRandomization: EntranceRandomization.option_buildings, + options.ExcludeGingerIsland: ExcludeGingerIsland.option_false, + options.SkillProgression: SkillProgression.option_progressive_with_masteries, + } + + blocked_entrances = {EntranceName.use_island_obelisk, EntranceName.boat_to_ginger_island} + required_regions = {RegionName.wizard_tower, RegionName.boat_tunnel} + + for i in range(0, 10 if self.skip_long_tests else 1000): + seed = get_seed() + with self.solo_world_sub_test(f"Seed: {seed}", world_options=test_options, world_caching=False, seed=seed) as (multiworld, world): + self.assert_can_reach_any_region_before_blockers(required_regions, blocked_entrances, multiworld) + + def assert_can_reach_any_region_before_blockers(self, required_regions: Collection[str], blocked_entrances: Collection[str], multiworld: MultiWorld): + explored_regions = explore_regions_up_to_blockers(blocked_entrances, multiworld) + self.assertTrue(any(region in explored_regions for region in required_regions)) + + +def explore_regions_up_to_blockers(blocked_entrances: Collection[str], multiworld: MultiWorld) -> set[str]: + explored_regions: set[str] = set() + regions_by_name = multiworld.regions.region_cache[1] + regions_to_explore = deque([regions_by_name["Menu"]]) + + while regions_to_explore: + region = regions_to_explore.pop() + + if region.name in explored_regions: + continue + + explored_regions.add(region.name) + + for exit_ in region.exits: + exit_: Entrance + if exit_.name in blocked_entrances: + continue + regions_to_explore.append(exit_.connected_region) + + return explored_regions diff --git a/worlds/stardew_valley/test/regions/TestRandomizationFlag.py b/worlds/stardew_valley/test/regions/TestRandomizationFlag.py new file mode 100644 index 00000000..6a01ef07 --- /dev/null +++ b/worlds/stardew_valley/test/regions/TestRandomizationFlag.py @@ -0,0 +1,88 @@ +import unittest + +from ..options.utils import fill_dataclass_with_default +from ... import create_content, options +from ...regions.entrance_rando import create_player_randomization_flag +from ...regions.model import RandomizationFlag, ConnectionData + + +class TestConnectionData(unittest.TestCase): + + def test_given_entrances_not_randomized_when_is_eligible_for_randomization_then_not_eligible(self): + player_flag = RandomizationFlag.NOT_RANDOMIZED + + connection = ConnectionData("Go to Somewhere", "Somewhere", RandomizationFlag.PELICAN_TOWN) + is_eligible = connection.is_eligible_for_randomization(player_flag) + + self.assertFalse(is_eligible) + + def test_given_pelican_town_connection_when_is_eligible_for_pelican_town_randomization_then_eligible(self): + player_flag = RandomizationFlag.BIT_PELICAN_TOWN + connection = ConnectionData("Go to Somewhere", "Somewhere", RandomizationFlag.PELICAN_TOWN) + + is_eligible = connection.is_eligible_for_randomization(player_flag) + + self.assertTrue(is_eligible) + + def test_given_pelican_town_connection_when_is_eligible_for_buildings_randomization_then_eligible(self): + player_flag = RandomizationFlag.BIT_BUILDINGS + connection = ConnectionData("Go to Somewhere", "Somewhere", RandomizationFlag.PELICAN_TOWN) + + is_eligible = connection.is_eligible_for_randomization(player_flag) + + self.assertTrue(is_eligible) + + def test_given_non_progression_connection_when_is_eligible_for_pelican_town_randomization_then_not_eligible(self): + player_flag = RandomizationFlag.BIT_PELICAN_TOWN + connection = ConnectionData("Go to Somewhere", "Somewhere", RandomizationFlag.NON_PROGRESSION) + + is_eligible = connection.is_eligible_for_randomization(player_flag) + + self.assertFalse(is_eligible) + + def test_given_non_progression_masteries_connection_when_is_eligible_for_non_progression_randomization_then_eligible(self): + player_flag = RandomizationFlag.BIT_NON_PROGRESSION + connection = ConnectionData("Go to Somewhere", "Somewhere", RandomizationFlag.NON_PROGRESSION ^ RandomizationFlag.EXCLUDE_MASTERIES) + + is_eligible = connection.is_eligible_for_randomization(player_flag) + + self.assertTrue(is_eligible) + + def test_given_non_progression_masteries_connection_when_is_eligible_for_non_progression_without_masteries_randomization_then_not_eligible(self): + player_flag = RandomizationFlag.BIT_NON_PROGRESSION | RandomizationFlag.EXCLUDE_MASTERIES + connection = ConnectionData("Go to Somewhere", "Somewhere", RandomizationFlag.NON_PROGRESSION ^ RandomizationFlag.EXCLUDE_MASTERIES) + + is_eligible = connection.is_eligible_for_randomization(player_flag) + + self.assertFalse(is_eligible) + + +class TestRandomizationFlag(unittest.TestCase): + + def test_given_entrance_randomization_choice_when_create_player_randomization_flag_then_only_relevant_bit_is_enabled(self): + for entrance_randomization_choice, expected_bit in ( + (options.EntranceRandomization.option_disabled, RandomizationFlag.NOT_RANDOMIZED), + (options.EntranceRandomization.option_pelican_town, RandomizationFlag.BIT_PELICAN_TOWN), + (options.EntranceRandomization.option_non_progression, RandomizationFlag.BIT_NON_PROGRESSION), + (options.EntranceRandomization.option_buildings_without_house, RandomizationFlag.BIT_BUILDINGS), + (options.EntranceRandomization.option_buildings, RandomizationFlag.BIT_BUILDINGS), + (options.EntranceRandomization.option_chaos, RandomizationFlag.BIT_BUILDINGS), + ): + player_options = fill_dataclass_with_default({options.EntranceRandomization: entrance_randomization_choice}) + content = create_content(player_options) + + flag = create_player_randomization_flag(player_options.entrance_randomization, content) + + self.assertEqual(flag, expected_bit) + + def test_given_masteries_not_randomized_when_create_player_randomization_flag_then_exclude_masteries_bit_enabled(self): + for entrance_randomization_choice in set(options.EntranceRandomization.options.values()) ^ {options.EntranceRandomization.option_disabled}: + player_options = fill_dataclass_with_default({ + options.EntranceRandomization: entrance_randomization_choice, + options.SkillProgression: options.SkillProgression.option_progressive + }) + content = create_content(player_options) + + flag = create_player_randomization_flag(player_options.entrance_randomization, content) + + self.assertIn(RandomizationFlag.EXCLUDE_MASTERIES, flag) diff --git a/worlds/stardew_valley/test/regions/TestRegionConnections.py b/worlds/stardew_valley/test/regions/TestRegionConnections.py new file mode 100644 index 00000000..42a2e361 --- /dev/null +++ b/worlds/stardew_valley/test/regions/TestRegionConnections.py @@ -0,0 +1,66 @@ +import unittest + +from ..options.utils import fill_dataclass_with_default +from ... import options +from ...content import create_content +from ...mods.region_data import region_data_by_content_pack +from ...regions import vanilla_data +from ...regions.model import MergeFlag +from ...regions.regions import create_all_regions, create_all_connections + + +class TestVanillaRegionsConnectionsWithGingerIsland(unittest.TestCase): + def test_region_exits_lead_somewhere(self): + for region in vanilla_data.regions_with_ginger_island_by_name.values(): + with self.subTest(region=region): + for exit_ in region.exits: + self.assertIn(exit_, vanilla_data.connections_with_ginger_island_by_name, + f"{region.name} is leading to {exit_} but it does not exist.") + + def test_connection_lead_somewhere(self): + for connection in vanilla_data.connections_with_ginger_island_by_name.values(): + with self.subTest(connection=connection): + self.assertIn(connection.destination, vanilla_data.regions_with_ginger_island_by_name, + f"{connection.name} is leading to {connection.destination} but it does not exist.") + + +class TestVanillaRegionsConnectionsWithoutGingerIsland(unittest.TestCase): + def test_region_exits_lead_somewhere(self): + for region in vanilla_data.regions_without_ginger_island_by_name.values(): + with self.subTest(region=region): + for exit_ in region.exits: + self.assertIn(exit_, vanilla_data.connections_without_ginger_island_by_name, + f"{region.name} is leading to {exit_} but it does not exist.") + + def test_connection_lead_somewhere(self): + for connection in vanilla_data.connections_without_ginger_island_by_name.values(): + with self.subTest(connection=connection): + self.assertIn(connection.destination, vanilla_data.regions_without_ginger_island_by_name, + f"{connection.name} is leading to {connection.destination} but it does not exist.") + + +class TestModsConnections(unittest.TestCase): + options = { + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false, + options.Mods: frozenset(options.Mods.valid_keys) + } + content = create_content(fill_dataclass_with_default(options)) + all_regions_by_name = create_all_regions(content.registered_packs) + all_connections_by_name = create_all_connections(content.registered_packs) + + def test_region_exits_lead_somewhere(self): + for mod_region_data in region_data_by_content_pack.values(): + for region in mod_region_data.regions: + if MergeFlag.REMOVE_EXITS in region.flag: + continue + + with self.subTest(mod=mod_region_data.mod_name, region=region.name): + for exit_ in region.exits: + self.assertIn(exit_, self.all_connections_by_name, f"{region.name} is leading to {exit_} but it does not exist.") + + def test_connection_lead_somewhere(self): + for mod_region_data in region_data_by_content_pack.values(): + for connection in mod_region_data.connections: + with self.subTest(mod=mod_region_data.mod_name, connection=connection.name): + self.assertIn(connection.destination, self.all_regions_by_name, + f"{connection.name} is leading to {connection.destination} but it does not exist.") diff --git a/worlds/stardew_valley/test/regions/__init__.py b/worlds/stardew_valley/test/regions/__init__.py new file mode 100644 index 00000000..e69de29b