diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index 83a1c11c..bf900742 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -145,7 +145,7 @@ class StardewValleyWorld(World): def create_items(self): self.precollect_starting_season() - self.precollect_farm_type_items() + self.precollect_building_items() items_to_exclude = [excluded_items for excluded_items in self.multiworld.precollected_items[self.player] if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK, @@ -200,9 +200,16 @@ class StardewValleyWorld(World): starting_season = self.create_item(self.random.choice(season_pool)) self.multiworld.push_precollected(starting_season) - def precollect_farm_type_items(self): - if self.options.farm_type == FarmType.option_meadowlands and self.options.building_progression & BuildingProgression.option_progressive: - self.multiworld.push_precollected(self.create_item("Progressive Coop")) + def precollect_building_items(self): + building_progression = self.content.features.building_progression + # Not adding items when building are vanilla because the buildings are already placed in the world. + if not building_progression.is_progressive: + return + + for building in building_progression.starting_buildings: + item, quantity = building_progression.to_progressive_item(building) + for _ in range(quantity): + self.multiworld.push_precollected(self.create_item(item)) def setup_logic_events(self): def register_event(name: str, region: str, rule: StardewRule): diff --git a/worlds/stardew_valley/content/__init__.py b/worlds/stardew_valley/content/__init__.py index 53085064..33608531 100644 --- a/worlds/stardew_valley/content/__init__.py +++ b/worlds/stardew_valley/content/__init__.py @@ -1,8 +1,9 @@ from . import content_packs -from .feature import cropsanity, friendsanity, fishsanity, booksanity, skill_progression, tool_progression +from .feature import cropsanity, friendsanity, fishsanity, booksanity, building_progression, skill_progression, tool_progression from .game_content import ContentPack, StardewContent, StardewFeatures from .unpacking import unpack_content from .. import options +from ..strings.building_names import Building def create_content(player_options: options.StardewValleyOptions) -> StardewContent: @@ -29,6 +30,7 @@ def choose_content_packs(player_options: options.StardewValleyOptions): def choose_features(player_options: options.StardewValleyOptions) -> StardewFeatures: return StardewFeatures( choose_booksanity(player_options.booksanity), + choose_building_progression(player_options.building_progression, player_options.farm_type), choose_cropsanity(player_options.cropsanity), choose_fishsanity(player_options.fishsanity), choose_friendsanity(player_options.friendsanity, player_options.friendsanity_heart_size), @@ -109,6 +111,32 @@ def choose_friendsanity(friendsanity_option: options.Friendsanity, heart_size: o raise ValueError(f"No friendsanity feature mapped to {str(friendsanity_option.value)}") +def choose_building_progression(building_option: options.BuildingProgression, + farm_type_option: options.FarmType) -> building_progression.BuildingProgressionFeature: + starting_buildings = {Building.farm_house, Building.pet_bowl, Building.shipping_bin} + + if farm_type_option == options.FarmType.option_meadowlands: + starting_buildings.add(Building.coop) + + if (building_option == options.BuildingProgression.option_vanilla + or building_option == options.BuildingProgression.option_vanilla_cheap + or building_option == options.BuildingProgression.option_vanilla_very_cheap): + return building_progression.BuildingProgressionVanilla( + starting_buildings=starting_buildings, + ) + + starting_buildings.remove(Building.shipping_bin) + + if (building_option == options.BuildingProgression.option_progressive + or building_option == options.BuildingProgression.option_progressive_cheap + or building_option == options.BuildingProgression.option_progressive_very_cheap): + return building_progression.BuildingProgressionProgressive( + starting_buildings=starting_buildings, + ) + + raise ValueError(f"No building progression feature mapped to {str(building_option.value)}") + + skill_progression_by_option = { options.SkillProgression.option_vanilla: skill_progression.SkillProgressionVanilla(), options.SkillProgression.option_progressive: skill_progression.SkillProgressionProgressive(), diff --git a/worlds/stardew_valley/content/feature/__init__.py b/worlds/stardew_valley/content/feature/__init__.py index eb23f810..b2a88286 100644 --- a/worlds/stardew_valley/content/feature/__init__.py +++ b/worlds/stardew_valley/content/feature/__init__.py @@ -1,4 +1,5 @@ from . import booksanity +from . import building_progression from . import cropsanity from . import fishsanity from . import friendsanity diff --git a/worlds/stardew_valley/content/feature/building_progression.py b/worlds/stardew_valley/content/feature/building_progression.py new file mode 100644 index 00000000..0d317aa0 --- /dev/null +++ b/worlds/stardew_valley/content/feature/building_progression.py @@ -0,0 +1,53 @@ +from abc import ABC +from dataclasses import dataclass +from typing import ClassVar, Set, Tuple + +from ...strings.building_names import Building + +progressive_house = "Progressive House" + +# This assumes that the farm house is always available, which might not be true forever... +progressive_house_by_upgrade_name = { + Building.farm_house: 0, + Building.kitchen: 1, + Building.kids_room: 2, + Building.cellar: 3 +} + + +def to_progressive_item(building: str) -> Tuple[str, int]: + """Return the name of the progressive item and its quantity required to unlock the building. + """ + if building in [Building.coop, Building.barn, Building.shed]: + return f"Progressive {building}", 1 + elif building.startswith("Big"): + return f"Progressive {building[building.index(' ') + 1:]}", 2 + elif building.startswith("Deluxe"): + return f"Progressive {building[building.index(' ') + 1:]}", 3 + elif building in progressive_house_by_upgrade_name: + return progressive_house, progressive_house_by_upgrade_name[building] + + return building, 1 + + +def to_location_name(building: str) -> str: + return f"{building} Blueprint" + + +@dataclass(frozen=True) +class BuildingProgressionFeature(ABC): + is_progressive: ClassVar[bool] + starting_buildings: Set[str] + + to_progressive_item = staticmethod(to_progressive_item) + progressive_house = progressive_house + + to_location_name = staticmethod(to_location_name) + + +class BuildingProgressionVanilla(BuildingProgressionFeature): + is_progressive = False + + +class BuildingProgressionProgressive(BuildingProgressionFeature): + is_progressive = True diff --git a/worlds/stardew_valley/content/game_content.py b/worlds/stardew_valley/content/game_content.py index 3aa3350f..8a72a481 100644 --- a/worlds/stardew_valley/content/game_content.py +++ b/worlds/stardew_valley/content/game_content.py @@ -3,9 +3,10 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, Iterable, Set, Any, Mapping, Type, Tuple, Union -from .feature import booksanity, cropsanity, fishsanity, friendsanity, skill_progression, tool_progression +from .feature import booksanity, cropsanity, fishsanity, friendsanity, skill_progression, building_progression, tool_progression +from ..data.building import Building from ..data.fish_data import FishItem -from ..data.game_item import GameItem, ItemSource, ItemTag +from ..data.game_item import GameItem, Source, ItemTag from ..data.skill import Skill from ..data.villagers_data import Villager @@ -20,16 +21,17 @@ class StardewContent: game_items: Dict[str, GameItem] = field(default_factory=dict) fishes: Dict[str, FishItem] = field(default_factory=dict) villagers: Dict[str, Villager] = field(default_factory=dict) + farm_buildings: Dict[str, Building] = field(default_factory=dict) skills: Dict[str, Skill] = field(default_factory=dict) quests: Dict[str, Any] = field(default_factory=dict) - def find_sources_of_type(self, types: Union[Type[ItemSource], Tuple[Type[ItemSource]]]) -> Iterable[ItemSource]: + def find_sources_of_type(self, types: Union[Type[Source], Tuple[Type[Source]]]) -> Iterable[Source]: for item in self.game_items.values(): for source in item.sources: if isinstance(source, types): yield source - def source_item(self, item_name: str, *sources: ItemSource): + def source_item(self, item_name: str, *sources: Source): item = self.game_items.setdefault(item_name, GameItem(item_name)) item.add_sources(sources) @@ -50,6 +52,7 @@ class StardewContent: @dataclass(frozen=True) class StardewFeatures: booksanity: booksanity.BooksanityFeature + building_progression: building_progression.BuildingProgressionFeature cropsanity: cropsanity.CropsanityFeature fishsanity: fishsanity.FishsanityFeature friendsanity: friendsanity.FriendsanityFeature @@ -70,13 +73,13 @@ class ContentPack: # def item_hook # ... - harvest_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict) + harvest_sources: Mapping[str, Iterable[Source]] = field(default_factory=dict) """Harvest sources contains both crops and forageables, but also fruits from trees, the cave farm and stuff harvested from tapping like maple syrup.""" def harvest_source_hook(self, content: StardewContent): ... - shop_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict) + shop_sources: Mapping[str, Iterable[Source]] = field(default_factory=dict) def shop_source_hook(self, content: StardewContent): ... @@ -86,12 +89,12 @@ class ContentPack: def fish_hook(self, content: StardewContent): ... - crafting_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict) + crafting_sources: Mapping[str, Iterable[Source]] = field(default_factory=dict) def crafting_hook(self, content: StardewContent): ... - artisan_good_sources: Mapping[str, Iterable[ItemSource]] = field(default_factory=dict) + artisan_good_sources: Mapping[str, Iterable[Source]] = field(default_factory=dict) def artisan_good_hook(self, content: StardewContent): ... @@ -101,6 +104,11 @@ class ContentPack: def villager_hook(self, content: StardewContent): ... + farm_buildings: Iterable[Building] = () + + def farm_building_hook(self, content: StardewContent): + ... + skills: Iterable[Skill] = () def skill_hook(self, content: StardewContent): diff --git a/worlds/stardew_valley/content/mods/tractor.py b/worlds/stardew_valley/content/mods/tractor.py index 8f143001..a672bc2b 100644 --- a/worlds/stardew_valley/content/mods/tractor.py +++ b/worlds/stardew_valley/content/mods/tractor.py @@ -1,7 +1,25 @@ from ..game_content import ContentPack from ..mod_registry import register_mod_content_pack +from ...data.building import Building +from ...data.shop import ShopSource from ...mods.mod_data import ModNames +from ...strings.artisan_good_names import ArtisanGood +from ...strings.building_names import ModBuilding +from ...strings.metal_names import MetalBar +from ...strings.region_names import Region register_mod_content_pack(ContentPack( ModNames.tractor, + farm_buildings=( + Building( + ModBuilding.tractor_garage, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=150_000, + items_price=((20, MetalBar.iron), (5, MetalBar.iridium), (1, ArtisanGood.battery_pack)), + ), + ), + ), + ), )) diff --git a/worlds/stardew_valley/content/unpacking.py b/worlds/stardew_valley/content/unpacking.py index 3c57f91a..2d50f771 100644 --- a/worlds/stardew_valley/content/unpacking.py +++ b/worlds/stardew_valley/content/unpacking.py @@ -5,7 +5,7 @@ from typing import Iterable, Mapping, Callable from .game_content import StardewContent, ContentPack, StardewFeatures from .vanilla.base import base_game as base_game_content_pack -from ..data.game_item import GameItem, ItemSource +from ..data.game_item import GameItem, Source def unpack_content(features: StardewFeatures, packs: Iterable[ContentPack]) -> StardewContent: @@ -61,6 +61,10 @@ def register_pack(content: StardewContent, pack: ContentPack): content.villagers[villager.name] = villager pack.villager_hook(content) + for building in pack.farm_buildings: + content.farm_buildings[building.name] = building + pack.farm_building_hook(content) + for skill in pack.skills: content.skills[skill.name] = skill pack.skill_hook(content) @@ -73,7 +77,7 @@ def register_pack(content: StardewContent, pack: ContentPack): def register_sources_and_call_hook(content: StardewContent, - sources_by_item_name: Mapping[str, Iterable[ItemSource]], + sources_by_item_name: Mapping[str, Iterable[Source]], hook: Callable[[StardewContent], None]): for item_name, sources in sources_by_item_name.items(): item = content.game_items.setdefault(item_name, GameItem(item_name)) diff --git a/worlds/stardew_valley/content/vanilla/pelican_town.py b/worlds/stardew_valley/content/vanilla/pelican_town.py index 913fe4b8..aeae4c14 100644 --- a/worlds/stardew_valley/content/vanilla/pelican_town.py +++ b/worlds/stardew_valley/content/vanilla/pelican_town.py @@ -1,10 +1,13 @@ from ..game_content import ContentPack from ...data import villagers_data, fish_data -from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource, CompoundSource +from ...data.building import Building +from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource from ...data.harvest import ForagingSource, SeasonalForagingSource, ArtifactSpotSource from ...data.requirement import ToolRequirement, BookRequirement, SkillRequirement from ...data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource +from ...strings.artisan_good_names import ArtisanGood from ...strings.book_names import Book +from ...strings.building_names import Building as BuildingNames from ...strings.crop_names import Fruit from ...strings.fish_names import WaterItem from ...strings.food_names import Beverage, Meal @@ -12,6 +15,7 @@ from ...strings.forageable_names import Forageable, Mushroom from ...strings.fruit_tree_names import Sapling from ...strings.generic_names import Generic from ...strings.material_names import Material +from ...strings.metal_names import MetalBar from ...strings.region_names import Region, LogicRegion from ...strings.season_names import Season from ...strings.seed_names import Seed, TreeSeed @@ -229,10 +233,10 @@ pelican_town = ContentPack( ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.mapping_cave_systems: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - CompoundSource(sources=( - GenericSource(regions=(Region.adventurer_guild_bedroom,)), - ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3), - ))), + GenericSource(regions=(Region.adventurer_guild_bedroom,)), + # Disabling the shop source for better game design. + # ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3), + ), Book.monster_compendium: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), CustomRuleSource(create_rule=lambda logic: logic.monster.can_kill_many(Generic.any)), @@ -385,5 +389,204 @@ pelican_town = ContentPack( villagers_data.vincent, villagers_data.willy, villagers_data.wizard, + ), + farm_buildings=( + Building( + BuildingNames.barn, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=6000, + items_price=((350, Material.wood), (150, Material.stone)) + ), + ), + ), + Building( + BuildingNames.big_barn, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=12_000, + items_price=((450, Material.wood), (200, Material.stone)) + ), + ), + upgrade_from=BuildingNames.barn, + ), + Building( + BuildingNames.deluxe_barn, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=25_000, + items_price=((550, Material.wood), (300, Material.stone)) + ), + ), + upgrade_from=BuildingNames.big_barn, + ), + Building( + BuildingNames.coop, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=4000, + items_price=((300, Material.wood), (100, Material.stone)) + ), + ), + ), + Building( + BuildingNames.big_coop, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=10_000, + items_price=((400, Material.wood), (150, Material.stone)) + ), + ), + upgrade_from=BuildingNames.coop, + ), + Building( + BuildingNames.deluxe_coop, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=20_000, + items_price=((500, Material.wood), (200, Material.stone)) + ), + ), + upgrade_from=BuildingNames.big_coop, + ), + Building( + BuildingNames.fish_pond, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=5000, + items_price=((200, Material.stone), (5, WaterItem.seaweed), (5, WaterItem.green_algae)) + ), + ), + ), + Building( + BuildingNames.mill, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=2500, + items_price=((50, Material.stone), (150, Material.wood), (4, ArtisanGood.cloth)) + ), + ), + ), + Building( + BuildingNames.shed, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=15_000, + items_price=((300, Material.wood),) + ), + ), + ), + Building( + BuildingNames.big_shed, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=20_000, + items_price=((550, Material.wood), (300, Material.stone)) + ), + ), + upgrade_from=BuildingNames.shed, + ), + Building( + BuildingNames.silo, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=100, + items_price=((100, Material.stone), (10, Material.clay), (5, MetalBar.copper)) + ), + ), + ), + Building( + BuildingNames.slime_hutch, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=10_000, + items_price=((500, Material.stone), (10, MetalBar.quartz), (1, MetalBar.iridium)) + ), + ), + ), + Building( + BuildingNames.stable, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=10_000, + items_price=((100, Material.hardwood), (5, MetalBar.iron)) + ), + ), + ), + Building( + BuildingNames.well, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=1000, + items_price=((75, Material.stone),) + ), + ), + ), + Building( + BuildingNames.shipping_bin, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=250, + items_price=((150, Material.wood),) + ), + ), + ), + Building( + BuildingNames.pet_bowl, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=5000, + items_price=((25, Material.hardwood),) + ), + ), + ), + Building( + BuildingNames.kitchen, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=10_000, + items_price=((450, Material.wood),) + ), + ), + upgrade_from=BuildingNames.farm_house, + ), + Building( + BuildingNames.kids_room, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=65_000, + items_price=((100, Material.hardwood),) + ), + ), + upgrade_from=BuildingNames.kitchen, + ), + Building( + BuildingNames.cellar, + sources=( + ShopSource( + shop_region=Region.carpenter, + money_price=100_000, + ), + ), + upgrade_from=BuildingNames.kids_room, + ), ) ) diff --git a/worlds/stardew_valley/data/artisan.py b/worlds/stardew_valley/data/artisan.py index 90be5b16..a4a722c7 100644 --- a/worlds/stardew_valley/data/artisan.py +++ b/worlds/stardew_valley/data/artisan.py @@ -1,10 +1,10 @@ from dataclasses import dataclass -from .game_item import ItemSource +from .game_item import Source @dataclass(frozen=True, kw_only=True) -class MachineSource(ItemSource): +class MachineSource(Source): item: str # this should be optional (worm bin) machine: str # seasons diff --git a/worlds/stardew_valley/data/building.py b/worlds/stardew_valley/data/building.py new file mode 100644 index 00000000..a3adf77c --- /dev/null +++ b/worlds/stardew_valley/data/building.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass, field +from functools import cached_property +from typing import Optional, Tuple + +from .game_item import Source + + +@dataclass(frozen=True) +class Building: + name: str + sources: Tuple[Source, ...] = field(kw_only=True) + upgrade_from: Optional[str] = field(default=None, kw_only=True) + + @cached_property + def is_upgrade(self) -> bool: + return self.upgrade_from is not None diff --git a/worlds/stardew_valley/data/game_item.py b/worlds/stardew_valley/data/game_item.py index c6e4717c..6c23f59f 100644 --- a/worlds/stardew_valley/data/game_item.py +++ b/worlds/stardew_valley/data/game_item.py @@ -27,7 +27,7 @@ class ItemTag(enum.Enum): @dataclass(frozen=True) -class ItemSource(ABC): +class Source(ABC): add_tags: ClassVar[Tuple[ItemTag]] = () other_requirements: Tuple[Requirement, ...] = field(kw_only=True, default_factory=tuple) @@ -38,23 +38,18 @@ class ItemSource(ABC): @dataclass(frozen=True, kw_only=True) -class GenericSource(ItemSource): +class GenericSource(Source): regions: Tuple[str, ...] = () """No region means it's available everywhere.""" @dataclass(frozen=True) -class CustomRuleSource(ItemSource): +class CustomRuleSource(Source): """Hopefully once everything is migrated to sources, we won't need these custom logic anymore.""" create_rule: Callable[[Any], StardewRule] -@dataclass(frozen=True, kw_only=True) -class CompoundSource(ItemSource): - sources: Tuple[ItemSource, ...] = () - - -class Tag(ItemSource): +class Tag(Source): """Not a real source, just a way to add tags to an item. Will be removed from the item sources during unpacking.""" tag: Tuple[ItemTag, ...] @@ -69,10 +64,10 @@ class Tag(ItemSource): @dataclass(frozen=True) class GameItem: name: str - sources: List[ItemSource] = field(default_factory=list) + sources: List[Source] = field(default_factory=list) tags: Set[ItemTag] = field(default_factory=set) - def add_sources(self, sources: Iterable[ItemSource]): + def add_sources(self, sources: Iterable[Source]): self.sources.extend(source for source in sources if type(source) is not Tag) for source in sources: self.add_tags(source.add_tags) diff --git a/worlds/stardew_valley/data/harvest.py b/worlds/stardew_valley/data/harvest.py index 0fdae954..621288ec 100644 --- a/worlds/stardew_valley/data/harvest.py +++ b/worlds/stardew_valley/data/harvest.py @@ -1,18 +1,18 @@ from dataclasses import dataclass from typing import Tuple, Sequence, Mapping -from .game_item import ItemSource, ItemTag +from .game_item import Source, ItemTag from ..strings.season_names import Season @dataclass(frozen=True, kw_only=True) -class ForagingSource(ItemSource): +class ForagingSource(Source): regions: Tuple[str, ...] seasons: Tuple[str, ...] = Season.all @dataclass(frozen=True, kw_only=True) -class SeasonalForagingSource(ItemSource): +class SeasonalForagingSource(Source): season: str days: Sequence[int] regions: Tuple[str, ...] @@ -22,17 +22,17 @@ class SeasonalForagingSource(ItemSource): @dataclass(frozen=True, kw_only=True) -class FruitBatsSource(ItemSource): +class FruitBatsSource(Source): ... @dataclass(frozen=True, kw_only=True) -class MushroomCaveSource(ItemSource): +class MushroomCaveSource(Source): ... @dataclass(frozen=True, kw_only=True) -class HarvestFruitTreeSource(ItemSource): +class HarvestFruitTreeSource(Source): add_tags = (ItemTag.CROPSANITY,) sapling: str @@ -46,7 +46,7 @@ class HarvestFruitTreeSource(ItemSource): @dataclass(frozen=True, kw_only=True) -class HarvestCropSource(ItemSource): +class HarvestCropSource(Source): add_tags = (ItemTag.CROPSANITY,) seed: str @@ -61,5 +61,5 @@ class HarvestCropSource(ItemSource): @dataclass(frozen=True, kw_only=True) -class ArtifactSpotSource(ItemSource): +class ArtifactSpotSource(Source): amount: int diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index 36e04810..44e16c5d 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -509,6 +509,7 @@ id,name,classification,groups,mod_name 561,Fishing Bar Size Bonus,filler,PLAYER_BUFF, 562,Quality Bonus,filler,PLAYER_BUFF, 563,Glow Bonus,filler,PLAYER_BUFF, +564,Pet Bowl,progression,BUILDING, 4001,Burnt Trap,trap,TRAP, 4002,Darkness Trap,trap,TRAP, 4003,Frozen Trap,trap,TRAP, diff --git a/worlds/stardew_valley/data/requirement.py b/worlds/stardew_valley/data/requirement.py index b2416d8d..d335527a 100644 --- a/worlds/stardew_valley/data/requirement.py +++ b/worlds/stardew_valley/data/requirement.py @@ -21,6 +21,11 @@ class SkillRequirement(Requirement): level: int +@dataclass(frozen=True) +class RegionRequirement(Requirement): + region: str + + @dataclass(frozen=True) class SeasonRequirement(Requirement): season: str diff --git a/worlds/stardew_valley/data/shop.py b/worlds/stardew_valley/data/shop.py index cc950602..3700ee89 100644 --- a/worlds/stardew_valley/data/shop.py +++ b/worlds/stardew_valley/data/shop.py @@ -1,14 +1,14 @@ from dataclasses import dataclass from typing import Tuple, Optional -from .game_item import ItemSource +from .game_item import Source from ..strings.season_names import Season ItemPrice = Tuple[int, str] @dataclass(frozen=True, kw_only=True) -class ShopSource(ItemSource): +class ShopSource(Source): shop_region: str money_price: Optional[int] = None items_price: Optional[Tuple[ItemPrice, ...]] = None @@ -20,20 +20,20 @@ class ShopSource(ItemSource): @dataclass(frozen=True, kw_only=True) -class MysteryBoxSource(ItemSource): +class MysteryBoxSource(Source): amount: int @dataclass(frozen=True, kw_only=True) -class ArtifactTroveSource(ItemSource): +class ArtifactTroveSource(Source): amount: int @dataclass(frozen=True, kw_only=True) -class PrizeMachineSource(ItemSource): +class PrizeMachineSource(Source): amount: int @dataclass(frozen=True, kw_only=True) -class FishingTreasureChestSource(ItemSource): +class FishingTreasureChestSource(Source): amount: int diff --git a/worlds/stardew_valley/early_items.py b/worlds/stardew_valley/early_items.py index 1457c5c7..550a92b4 100644 --- a/worlds/stardew_valley/early_items.py +++ b/worlds/stardew_valley/early_items.py @@ -23,9 +23,9 @@ def setup_early_items(multiworld, options: stardew_options.StardewValleyOptions, add_seasonal_candidates(early_candidates, options) - if options.building_progression & stardew_options.BuildingProgression.option_progressive: + if content.features.building_progression.is_progressive: early_forced.append(Building.shipping_bin) - if options.farm_type != stardew_options.FarmType.option_meadowlands: + if Building.coop not in content.features.building_progression.starting_buildings: early_candidates.append("Progressive Coop") early_candidates.append("Progressive Barn") diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index dcb37a8f..b4b1175c 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -15,7 +15,7 @@ from .data.game_item import ItemTag from .logic.logic_event import all_events from .mods.mod_data import ModNames from .options import StardewValleyOptions, TrapItems, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Museumsanity, \ - BuildingProgression, ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \ + ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \ Chefsanity, Craftsanity, BundleRandomization, EntranceRandomization, Shipsanity, Walnutsanity, EnabledFillerBuffs from .strings.ap_names.ap_option_names import BuffOptionName, WalnutsanityOptionName from .strings.ap_names.ap_weapon_names import APWeapon @@ -225,7 +225,7 @@ def create_unique_items(item_factory: StardewItemFactory, options: StardewValley create_tools(item_factory, content, items) create_skills(item_factory, content, items) create_wizard_buildings(item_factory, options, items) - create_carpenter_buildings(item_factory, options, items) + create_carpenter_buildings(item_factory, content, items) items.append(item_factory("Railroad Boulder Removed")) items.append(item_factory(CommunityUpgrade.fruit_bats)) items.append(item_factory(CommunityUpgrade.mushroom_boxes)) @@ -353,30 +353,14 @@ def create_wizard_buildings(item_factory: StardewItemFactory, options: StardewVa items.append(item_factory("Woods Obelisk")) -def create_carpenter_buildings(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): - building_option = options.building_progression - if not building_option & BuildingProgression.option_progressive: +def create_carpenter_buildings(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]): + building_progression = content.features.building_progression + if not building_progression.is_progressive: return - items.append(item_factory("Progressive Coop")) - items.append(item_factory("Progressive Coop")) - items.append(item_factory("Progressive Coop")) - items.append(item_factory("Progressive Barn")) - items.append(item_factory("Progressive Barn")) - items.append(item_factory("Progressive Barn")) - items.append(item_factory("Well")) - items.append(item_factory("Silo")) - items.append(item_factory("Mill")) - items.append(item_factory("Progressive Shed")) - items.append(item_factory("Progressive Shed", ItemClassification.useful)) - items.append(item_factory("Fish Pond")) - items.append(item_factory("Stable")) - items.append(item_factory("Slime Hutch")) - items.append(item_factory("Shipping Bin")) - items.append(item_factory("Progressive House")) - items.append(item_factory("Progressive House")) - items.append(item_factory("Progressive House")) - if ModNames.tractor in options.mods: - items.append(item_factory("Tractor Garage")) + + for building in content.farm_buildings.values(): + item_name, _ = building_progression.to_progressive_item(building.name) + items.append(item_factory(item_name)) def create_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): diff --git a/worlds/stardew_valley/locations.py b/worlds/stardew_valley/locations.py index c7d787e5..0d621fda 100644 --- a/worlds/stardew_valley/locations.py +++ b/worlds/stardew_valley/locations.py @@ -11,7 +11,7 @@ from .data.game_item import ItemTag from .data.museum_data import all_museum_items from .mods.mod_data import ModNames from .options import ExcludeGingerIsland, ArcadeMachineLocations, SpecialOrderLocations, Museumsanity, \ - FestivalLocations, BuildingProgression, ElevatorProgression, BackpackProgression, FarmType + FestivalLocations, ElevatorProgression, BackpackProgression, FarmType from .options import StardewValleyOptions, Craftsanity, Chefsanity, Cooksanity, Shipsanity, Monstersanity from .strings.goal_names import Goal from .strings.quest_names import ModQuest, Quest @@ -261,6 +261,19 @@ def extend_baby_locations(randomized_locations: List[LocationData]): randomized_locations.extend(baby_locations) +def extend_building_locations(randomized_locations: List[LocationData], content: StardewContent): + building_progression = content.features.building_progression + if not building_progression.is_progressive: + return + + for building in content.farm_buildings.values(): + if building.name in building_progression.starting_buildings: + continue + + location_name = building_progression.to_location_name(building.name) + randomized_locations.append(location_table[location_name]) + + def extend_festival_locations(randomized_locations: List[LocationData], options: StardewValleyOptions, random: Random): if options.festival_locations == FestivalLocations.option_disabled: return @@ -485,10 +498,7 @@ def create_locations(location_collector: StardewLocationCollector, if skill_progression.is_mastery_randomized(skill): randomized_locations.append(location_table[skill.mastery_name]) - if options.building_progression & BuildingProgression.option_progressive: - for location in locations_by_tag[LocationTags.BUILDING_BLUEPRINT]: - if location.mod_name is None or location.mod_name in options.mods: - randomized_locations.append(location_table[location.name]) + extend_building_locations(randomized_locations, content) if options.arcade_machine_locations != ArcadeMachineLocations.option_disabled: randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE_VICTORY]) diff --git a/worlds/stardew_valley/logic/base_logic.py b/worlds/stardew_valley/logic/base_logic.py index 7b377fce..761ee541 100644 --- a/worlds/stardew_valley/logic/base_logic.py +++ b/worlds/stardew_valley/logic/base_logic.py @@ -20,7 +20,6 @@ class LogicRegistry: self.museum_rules: Dict[str, StardewRule] = {} self.festival_rules: Dict[str, StardewRule] = {} self.quest_rules: Dict[str, StardewRule] = {} - self.building_rules: Dict[str, StardewRule] = {} self.special_order_rules: Dict[str, StardewRule] = {} self.sve_location_rules: Dict[str, StardewRule] = {} diff --git a/worlds/stardew_valley/logic/building_logic.py b/worlds/stardew_valley/logic/building_logic.py index b4eff439..58a375d0 100644 --- a/worlds/stardew_valley/logic/building_logic.py +++ b/worlds/stardew_valley/logic/building_logic.py @@ -1,22 +1,22 @@ +import typing from functools import cached_property -from typing import Dict, Union +from typing import Union from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin from .received_logic import ReceivedLogicMixin from .region_logic import RegionLogicMixin -from ..options import BuildingProgression -from ..stardew_rule import StardewRule, True_, False_, Has -from ..strings.artisan_good_names import ArtisanGood +from ..stardew_rule import StardewRule, true_ from ..strings.building_names import Building -from ..strings.fish_names import WaterItem -from ..strings.material_names import Material -from ..strings.metal_names import MetalBar from ..strings.region_names import Region -has_group = "building" +if typing.TYPE_CHECKING: + from .source_logic import SourceLogicMixin +else: + SourceLogicMixin = object + +AUTO_BUILDING_BUILDINGS = {Building.shipping_bin, Building.pet_bowl, Building.farm_house} class BuildingLogicMixin(BaseLogicMixin): @@ -25,78 +25,38 @@ class BuildingLogicMixin(BaseLogicMixin): self.building = BuildingLogic(*args, **kwargs) -class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]): - def initialize_rules(self): - self.registry.building_rules.update({ - # @formatter:off - Building.barn: self.logic.money.can_spend(6000) & self.logic.has_all(Material.wood, Material.stone), - Building.big_barn: self.logic.money.can_spend(12000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.barn), - Building.deluxe_barn: self.logic.money.can_spend(25000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.big_barn), - Building.coop: self.logic.money.can_spend(4000) & self.logic.has_all(Material.wood, Material.stone), - Building.big_coop: self.logic.money.can_spend(10000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.coop), - Building.deluxe_coop: self.logic.money.can_spend(20000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.big_coop), - Building.fish_pond: self.logic.money.can_spend(5000) & self.logic.has_all(Material.stone, WaterItem.seaweed, WaterItem.green_algae), - Building.mill: self.logic.money.can_spend(2500) & self.logic.has_all(Material.stone, Material.wood, ArtisanGood.cloth), - Building.shed: self.logic.money.can_spend(15000) & self.logic.has(Material.wood), - Building.big_shed: self.logic.money.can_spend(20000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.shed), - Building.silo: self.logic.money.can_spend(100) & self.logic.has_all(Material.stone, Material.clay, MetalBar.copper), - Building.slime_hutch: self.logic.money.can_spend(10000) & self.logic.has_all(Material.stone, MetalBar.quartz, MetalBar.iridium), - Building.stable: self.logic.money.can_spend(10000) & self.logic.has_all(Material.hardwood, MetalBar.iron), - Building.well: self.logic.money.can_spend(1000) & self.logic.has(Material.stone), - Building.shipping_bin: self.logic.money.can_spend(250) & self.logic.has(Material.wood), - Building.kitchen: self.logic.money.can_spend(10000) & self.logic.has(Material.wood) & self.logic.building.has_house(0), - Building.kids_room: self.logic.money.can_spend(65000) & self.logic.has(Material.hardwood) & self.logic.building.has_house(1), - Building.cellar: self.logic.money.can_spend(100000) & self.logic.building.has_house(2), - # @formatter:on - }) - - def update_rules(self, new_rules: Dict[str, StardewRule]): - self.registry.building_rules.update(new_rules) +class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SourceLogicMixin]]): @cache_self1 - def has_building(self, building: str) -> StardewRule: - # Shipping bin is special. The mod auto-builds it when received, no need to go to Robin. - if building is Building.shipping_bin: - if not self.options.building_progression & BuildingProgression.option_progressive: - return True_() - return self.logic.received(building) + def can_build(self, building_name: str) -> StardewRule: + building = self.content.farm_buildings.get(building_name) + assert building is not None, f"Building {building_name} not found." + + source_rule = self.logic.source.has_access_to_any(building.sources) + if not building.is_upgrade: + return source_rule + + upgrade_rule = self.logic.building.has_building(building.upgrade_from) + return self.logic.and_(upgrade_rule, source_rule) + + @cache_self1 + def has_building(self, building_name: str) -> StardewRule: + building_progression = self.content.features.building_progression + + if building_name in building_progression.starting_buildings: + return true_ + + if not building_progression.is_progressive: + return self.logic.building.can_build(building_name) + + # Those buildings are special. The mod auto-builds them when received, no need to go to Robin. + if building_name in AUTO_BUILDING_BUILDINGS: + return self.logic.received(Building.shipping_bin) carpenter_rule = self.logic.building.can_construct_buildings - if not self.options.building_progression & BuildingProgression.option_progressive: - return Has(building, self.registry.building_rules, has_group) & carpenter_rule - - count = 1 - if building in [Building.coop, Building.barn, Building.shed]: - building = f"Progressive {building}" - elif building.startswith("Big"): - count = 2 - building = " ".join(["Progressive", *building.split(" ")[1:]]) - elif building.startswith("Deluxe"): - count = 3 - building = " ".join(["Progressive", *building.split(" ")[1:]]) - return self.logic.received(building, count) & carpenter_rule + item, count = building_progression.to_progressive_item(building_name) + return self.logic.received(item, count) & carpenter_rule @cached_property def can_construct_buildings(self) -> StardewRule: return self.logic.region.can_reach(Region.carpenter) - - @cache_self1 - def has_house(self, upgrade_level: int) -> StardewRule: - if upgrade_level < 1: - return True_() - - if upgrade_level > 3: - return False_() - - carpenter_rule = self.logic.building.can_construct_buildings - if self.options.building_progression & BuildingProgression.option_progressive: - return carpenter_rule & self.logic.received(f"Progressive House", upgrade_level) - - if upgrade_level == 1: - return carpenter_rule & Has(Building.kitchen, self.registry.building_rules, has_group) - - if upgrade_level == 2: - return carpenter_rule & Has(Building.kids_room, self.registry.building_rules, has_group) - - # if upgrade_level == 3: - return carpenter_rule & Has(Building.cellar, self.registry.building_rules, has_group) diff --git a/worlds/stardew_valley/logic/cooking_logic.py b/worlds/stardew_valley/logic/cooking_logic.py index 8c8f716a..339b2b98 100644 --- a/worlds/stardew_valley/logic/cooking_logic.py +++ b/worlds/stardew_valley/logic/cooking_logic.py @@ -17,6 +17,7 @@ from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSou from ..data.recipe_source import CutsceneSource, ShopTradeSource from ..options import Chefsanity from ..stardew_rule import StardewRule, True_, False_ +from ..strings.building_names import Building from ..strings.region_names import LogicRegion from ..strings.skill_names import Skill from ..strings.tv_channel_names import Channel @@ -32,7 +33,7 @@ class CookingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogi BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]): @cached_property def can_cook_in_kitchen(self) -> StardewRule: - return self.logic.building.has_house(1) | self.logic.skill.has_level(Skill.foraging, 9) + return self.logic.building.has_building(Building.kitchen) | self.logic.skill.has_level(Skill.foraging, 9) # Should be cached def can_cook(self, recipe: CookingRecipe = None) -> StardewRule: diff --git a/worlds/stardew_valley/logic/goal_logic.py b/worlds/stardew_valley/logic/goal_logic.py index 0ad3eb4f..6ffa4da1 100644 --- a/worlds/stardew_valley/logic/goal_logic.py +++ b/worlds/stardew_valley/logic/goal_logic.py @@ -44,7 +44,7 @@ class GoalLogic(BaseLogic[StardewLogic]): self.logic.museum.can_complete_museum(), # Catching every fish not expected # Shipping every item not expected - self.logic.relationship.can_get_married() & self.logic.building.has_house(2), + self.logic.relationship.can_get_married() & self.logic.building.has_building(Building.kids_room), self.logic.relationship.has_hearts_with_n(5, 8), # 5 Friends self.logic.relationship.has_hearts_with_n(10, 8), # 10 friends self.logic.pet.has_pet_hearts(5), # Max Pet diff --git a/worlds/stardew_valley/logic/grind_logic.py b/worlds/stardew_valley/logic/grind_logic.py index 997300ae..9550a128 100644 --- a/worlds/stardew_valley/logic/grind_logic.py +++ b/worlds/stardew_valley/logic/grind_logic.py @@ -13,6 +13,7 @@ from ..strings.craftable_names import Consumable from ..strings.currency_names import Currency from ..strings.fish_names import WaterChest from ..strings.geode_names import Geode +from ..strings.material_names import Material from ..strings.region_names import Region from ..strings.tool_names import Tool @@ -21,9 +22,14 @@ if TYPE_CHECKING: else: ToolLogicMixin = object -MIN_ITEMS = 10 -MAX_ITEMS = 999 -PERCENT_REQUIRED_FOR_MAX_ITEM = 24 +MIN_MEDIUM_ITEMS = 10 +MAX_MEDIUM_ITEMS = 999 +PERCENT_REQUIRED_FOR_MAX_MEDIUM_ITEM = 24 + +EASY_ITEMS = {Material.wood, Material.stone, Material.fiber, Material.sap} +MIN_EASY_ITEMS = 300 +MAX_EASY_ITEMS = 2997 +PERCENT_REQUIRED_FOR_MAX_EASY_ITEM = 6 class GrindLogicMixin(BaseLogicMixin): @@ -43,7 +49,7 @@ class GrindLogic(BaseLogic[Union[GrindLogicMixin, HasLogicMixin, ReceivedLogicMi # Assuming one box per day, but halved because we don't know how many months have passed before Mr. Qi's Plane Ride. time_rule = self.logic.time.has_lived_months(quantity // 14) return self.logic.and_(opening_rule, mystery_box_rule, - book_of_mysteries_rule, time_rule,) + book_of_mysteries_rule, time_rule, ) def can_grind_artifact_troves(self, quantity: int) -> StardewRule: opening_rule = self.logic.region.can_reach(Region.blacksmith) @@ -67,11 +73,26 @@ class GrindLogic(BaseLogic[Union[GrindLogicMixin, HasLogicMixin, ReceivedLogicMi # Assuming twelve per month if the player does not grind it. self.logic.time.has_lived_months(quantity // 12)) + def can_grind_item(self, quantity: int, item: str | None = None) -> StardewRule: + if item in EASY_ITEMS: + return self.logic.grind.can_grind_easy_item(quantity) + else: + return self.logic.grind.can_grind_medium_item(quantity) + @cache_self1 - def can_grind_item(self, quantity: int) -> StardewRule: - if quantity <= MIN_ITEMS: + def can_grind_medium_item(self, quantity: int) -> StardewRule: + if quantity <= MIN_MEDIUM_ITEMS: return self.logic.true_ - quantity = min(quantity, MAX_ITEMS) - price = max(1, quantity * PERCENT_REQUIRED_FOR_MAX_ITEM // MAX_ITEMS) + quantity = min(quantity, MAX_MEDIUM_ITEMS) + price = max(1, quantity * PERCENT_REQUIRED_FOR_MAX_MEDIUM_ITEM // MAX_MEDIUM_ITEMS) + return HasProgressionPercent(self.player, price) + + @cache_self1 + def can_grind_easy_item(self, quantity: int) -> StardewRule: + if quantity <= MIN_EASY_ITEMS: + return self.logic.true_ + + quantity = min(quantity, MAX_EASY_ITEMS) + price = max(1, quantity * PERCENT_REQUIRED_FOR_MAX_EASY_ITEM // MAX_EASY_ITEMS) return HasProgressionPercent(self.player, price) diff --git a/worlds/stardew_valley/logic/logic.py b/worlds/stardew_valley/logic/logic.py index dd951654..aa4cd075 100644 --- a/worlds/stardew_valley/logic/logic.py +++ b/worlds/stardew_valley/logic/logic.py @@ -254,7 +254,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin Geode.omni: self.mine.can_mine_in_the_mines_floor_41_80() | self.region.can_reach(Region.desert) | self.tool.has_tool(Tool.pan, ToolMaterial.iron) | self.received(Wallet.rusty_key) | (self.has(Fish.octopus) & self.building.has_building(Building.fish_pond)) | self.region.can_reach(Region.volcano_floor_10), Gift.bouquet: self.relationship.has_hearts_with_any_bachelor(8) & self.money.can_spend_at(Region.pierre_store, 100), Gift.golden_pumpkin: self.season.has(Season.fall) | self.action.can_open_geode(Geode.artifact_trove), - Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts_with_any_bachelor(10) & self.building.has_house(1) & self.has(Consumable.rain_totem), + Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts_with_any_bachelor(10) & self.building.has_building(Building.kitchen) & self.has(Consumable.rain_totem), Gift.movie_ticket: self.money.can_spend_at(Region.movie_ticket_stand, 1000), Gift.pearl: (self.has(Fish.blobfish) & self.building.has_building(Building.fish_pond)) | self.action.can_open_geode(Geode.artifact_trove), Gift.tea_set: self.season.has(Season.winter) & self.time.has_lived_max_months, @@ -355,9 +355,6 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_() self.registry.item_rules[recipe] = obtention_rule | crafting_rule - self.building.initialize_rules() - self.building.update_rules(self.mod.building.get_modded_building_rules()) - self.quest.initialize_rules() self.quest.update_rules(self.mod.quest.get_modded_quest_rules()) diff --git a/worlds/stardew_valley/logic/money_logic.py b/worlds/stardew_valley/logic/money_logic.py index 85370273..e272436f 100644 --- a/worlds/stardew_valley/logic/money_logic.py +++ b/worlds/stardew_valley/logic/money_logic.py @@ -17,8 +17,8 @@ from ..strings.region_names import Region, LogicRegion if typing.TYPE_CHECKING: from .shipping_logic import ShippingLogicMixin - - assert ShippingLogicMixin +else: + ShippingLogicMixin = object qi_gem_rewards = ("100 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems", "25 Qi Gems", "20 Qi Gems", "15 Qi Gems", "10 Qi Gems") @@ -31,7 +31,7 @@ class MoneyLogicMixin(BaseLogicMixin): class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SeasonLogicMixin, -GrindLogicMixin, 'ShippingLogicMixin']]): +GrindLogicMixin, ShippingLogicMixin]]): @cache_self1 def can_have_earned_total(self, amount: int) -> StardewRule: @@ -80,7 +80,7 @@ GrindLogicMixin, 'ShippingLogicMixin']]): item_rules = [] if source.items_price is not None: for price, item in source.items_price: - item_rules.append(self.logic.has(item) & self.logic.grind.can_grind_item(price)) + item_rules.append(self.logic.has(item) & self.logic.grind.can_grind_item(price, item)) region_rule = self.logic.region.can_reach(source.shop_region) diff --git a/worlds/stardew_valley/logic/relationship_logic.py b/worlds/stardew_valley/logic/relationship_logic.py index b74bdc56..2de82bf9 100644 --- a/worlds/stardew_valley/logic/relationship_logic.py +++ b/worlds/stardew_valley/logic/relationship_logic.py @@ -15,9 +15,9 @@ from ..content.feature import friendsanity from ..data.villagers_data import Villager from ..stardew_rule import StardewRule, True_, false_, true_ from ..strings.ap_names.mods.mod_items import SVEQuestItem +from ..strings.building_names import Building from ..strings.generic_names import Generic from ..strings.gift_names import Gift -from ..strings.quest_names import ModQuest from ..strings.region_names import Region from ..strings.season_names import Season from ..strings.villager_names import NPC, ModNPC @@ -63,7 +63,7 @@ ReceivedLogicMixin, HasLogicMixin, ModLogicMixin]]): if not self.content.features.friendsanity.is_enabled: return self.logic.relationship.can_reproduce(number_children) - return self.logic.received_n(*possible_kids, count=number_children) & self.logic.building.has_house(2) + return self.logic.received_n(*possible_kids, count=number_children) & self.logic.building.has_building(Building.kids_room) def can_reproduce(self, number_children: int = 1) -> StardewRule: assert number_children >= 0, "Can't have a negative amount of children." @@ -71,7 +71,7 @@ ReceivedLogicMixin, HasLogicMixin, ModLogicMixin]]): return True_() baby_rules = [self.logic.relationship.can_get_married(), - self.logic.building.has_house(2), + self.logic.building.has_building(Building.kids_room), self.logic.relationship.has_hearts_with_any_bachelor(12), self.logic.relationship.has_children(number_children - 1)] diff --git a/worlds/stardew_valley/logic/requirement_logic.py b/worlds/stardew_valley/logic/requirement_logic.py index 6a5adf48..3e83950d 100644 --- a/worlds/stardew_valley/logic/requirement_logic.py +++ b/worlds/stardew_valley/logic/requirement_logic.py @@ -8,6 +8,7 @@ from .fishing_logic import FishingLogicMixin from .has_logic import HasLogicMixin from .quest_logic import QuestLogicMixin from .received_logic import ReceivedLogicMixin +from .region_logic import RegionLogicMixin from .relationship_logic import RelationshipLogicMixin from .season_logic import SeasonLogicMixin from .skill_logic import SkillLogicMixin @@ -16,7 +17,7 @@ from .tool_logic import ToolLogicMixin from .walnut_logic import WalnutLogicMixin from ..data.game_item import Requirement from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, CombatRequirement, QuestRequirement, \ - RelationshipRequirement, FishingRequirement, WalnutRequirement + RelationshipRequirement, FishingRequirement, WalnutRequirement, RegionRequirement class RequirementLogicMixin(BaseLogicMixin): @@ -26,7 +27,7 @@ class RequirementLogicMixin(BaseLogicMixin): class RequirementLogic(BaseLogic[Union[RequirementLogicMixin, HasLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, BookLogicMixin, -SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, RelationshipLogicMixin, FishingLogicMixin, WalnutLogicMixin]]): +SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, RelationshipLogicMixin, FishingLogicMixin, WalnutLogicMixin, RegionLogicMixin]]): def meet_all_requirements(self, requirements: Iterable[Requirement]): if not requirements: @@ -45,6 +46,10 @@ SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, Relationshi def _(self, requirement: SkillRequirement): return self.logic.skill.has_level(requirement.skill, requirement.level) + @meet_requirement.register + def _(self, requirement: RegionRequirement): + return self.logic.region.can_reach(requirement.region) + @meet_requirement.register def _(self, requirement: BookRequirement): return self.logic.book.has_book_power(requirement.book) @@ -76,5 +81,3 @@ SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, Relationshi @meet_requirement.register def _(self, requirement: FishingRequirement): return self.logic.fishing.can_fish_at(requirement.region) - - diff --git a/worlds/stardew_valley/logic/source_logic.py b/worlds/stardew_valley/logic/source_logic.py index 9ef68a02..f1c6fe3d 100644 --- a/worlds/stardew_valley/logic/source_logic.py +++ b/worlds/stardew_valley/logic/source_logic.py @@ -12,7 +12,7 @@ from .region_logic import RegionLogicMixin from .requirement_logic import RequirementLogicMixin from .tool_logic import ToolLogicMixin from ..data.artisan import MachineSource -from ..data.game_item import GenericSource, ItemSource, GameItem, CustomRuleSource, CompoundSource +from ..data.game_item import GenericSource, Source, GameItem, CustomRuleSource from ..data.harvest import ForagingSource, FruitBatsSource, MushroomCaveSource, SeasonalForagingSource, \ HarvestCropSource, HarvestFruitTreeSource, ArtifactSpotSource from ..data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource @@ -25,7 +25,7 @@ class SourceLogicMixin(BaseLogicMixin): class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogicMixin, HarvestingLogicMixin, MoneyLogicMixin, RegionLogicMixin, - ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]): +ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]): def has_access_to_item(self, item: GameItem): rules = [] @@ -36,14 +36,10 @@ class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogic rules.append(self.logic.source.has_access_to_any(item.sources)) return self.logic.and_(*rules) - def has_access_to_any(self, sources: Iterable[ItemSource]): + def has_access_to_any(self, sources: Iterable[Source]): return self.logic.or_(*(self.logic.source.has_access_to(source) & self.logic.requirement.meet_all_requirements(source.other_requirements) for source in sources)) - def has_access_to_all(self, sources: Iterable[ItemSource]): - return self.logic.and_(*(self.logic.source.has_access_to(source) & self.logic.requirement.meet_all_requirements(source.other_requirements) - for source in sources)) - @functools.singledispatchmethod def has_access_to(self, source: Any): raise ValueError(f"Sources of type{type(source)} have no rule registered.") @@ -56,10 +52,6 @@ class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogic def _(self, source: CustomRuleSource): return source.create_rule(self.logic) - @has_access_to.register - def _(self, source: CompoundSource): - return self.logic.source.has_access_to_all(source.sources) - @has_access_to.register def _(self, source: ForagingSource): return self.logic.harvesting.can_forage_from(source) diff --git a/worlds/stardew_valley/mods/logic/buildings_logic.py b/worlds/stardew_valley/mods/logic/buildings_logic.py deleted file mode 100644 index 388204a4..00000000 --- a/worlds/stardew_valley/mods/logic/buildings_logic.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Dict, Union - -from ..mod_data import ModNames -from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.has_logic import HasLogicMixin -from ...logic.money_logic import MoneyLogicMixin -from ...stardew_rule import StardewRule -from ...strings.artisan_good_names import ArtisanGood -from ...strings.building_names import ModBuilding -from ...strings.metal_names import MetalBar -from ...strings.region_names import Region - - -class ModBuildingLogicMixin(BaseLogicMixin): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.building = ModBuildingLogic(*args, **kwargs) - - -class ModBuildingLogic(BaseLogic[Union[MoneyLogicMixin, HasLogicMixin]]): - - def get_modded_building_rules(self) -> Dict[str, StardewRule]: - buildings = dict() - if ModNames.tractor in self.options.mods: - tractor_rule = (self.logic.money.can_spend_at(Region.carpenter, 150000) & - self.logic.has_all(MetalBar.iron, MetalBar.iridium, ArtisanGood.battery_pack)) - buildings.update({ModBuilding.tractor_garage: tractor_rule}) - return buildings diff --git a/worlds/stardew_valley/mods/logic/mod_logic.py b/worlds/stardew_valley/mods/logic/mod_logic.py index 37c17183..ff28108b 100644 --- a/worlds/stardew_valley/mods/logic/mod_logic.py +++ b/worlds/stardew_valley/mods/logic/mod_logic.py @@ -1,4 +1,3 @@ -from .buildings_logic import ModBuildingLogicMixin from .deepwoods_logic import DeepWoodsLogicMixin from .elevator_logic import ModElevatorLogicMixin from .item_logic import ModItemLogicMixin @@ -16,6 +15,6 @@ class ModLogicMixin(BaseLogicMixin): self.mod = ModLogic(*args, **kwargs) -class ModLogic(ModElevatorLogicMixin, MagicLogicMixin, ModSkillLogicMixin, ModItemLogicMixin, ModQuestLogicMixin, ModBuildingLogicMixin, +class ModLogic(ModElevatorLogicMixin, MagicLogicMixin, ModSkillLogicMixin, ModItemLogicMixin, ModQuestLogicMixin, ModSpecialOrderLogicMixin, DeepWoodsLogicMixin, SVELogicMixin): pass diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index dc630186..bdfbc204 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -19,7 +19,7 @@ from .logic.logic import StardewLogic from .logic.time_logic import MAX_MONTHS from .logic.tool_logic import tool_upgrade_prices from .mods.mod_data import ModNames -from .options import BuildingProgression, ExcludeGingerIsland, SpecialOrderLocations, Museumsanity, BackpackProgression, Shipsanity, \ +from .options import ExcludeGingerIsland, SpecialOrderLocations, Museumsanity, BackpackProgression, Shipsanity, \ Monstersanity, Chefsanity, Craftsanity, ArcadeMachineLocations, Cooksanity, StardewValleyOptions, Walnutsanity from .stardew_rule import And, StardewRule, true_ from .stardew_rule.indirect_connection import look_for_indirect_connection @@ -71,7 +71,7 @@ def set_rules(world): set_tool_rules(logic, multiworld, player, world_content) set_skills_rules(logic, multiworld, player, world_content) set_bundle_rules(bundle_rooms, logic, multiworld, player, world_options) - set_building_rules(logic, multiworld, player, world_options) + set_building_rules(logic, multiworld, player, world_content) set_cropsanity_rules(logic, multiworld, player, world_content) set_story_quests_rules(all_location_names, logic, multiworld, player, world_options) set_special_order_rules(all_location_names, logic, multiworld, player, world_options) @@ -130,15 +130,19 @@ def set_tool_rules(logic: StardewLogic, multiworld, player, content: StardewCont MultiWorldRules.set_rule(tool_upgrade_location, logic.tool.has_tool(tool, previous)) -def set_building_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): - if not world_options.building_progression & BuildingProgression.option_progressive: +def set_building_rules(logic: StardewLogic, multiworld, player, content: StardewContent): + building_progression = content.features.building_progression + if not building_progression.is_progressive: return - for building in locations.locations_by_tag[LocationTags.BUILDING_BLUEPRINT]: - if building.mod_name is not None and building.mod_name not in world_options.mods: + for building in content.farm_buildings.values(): + if building.name in building_progression.starting_buildings: continue - MultiWorldRules.set_rule(multiworld.get_location(building.name, player), - logic.registry.building_rules[building.name.replace(" Blueprint", "")]) + + location_name = building_progression.to_location_name(building.name) + + MultiWorldRules.set_rule(multiworld.get_location(location_name, player), + logic.building.can_build(building.name)) def set_bundle_rules(bundle_rooms: List[BundleRoom], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -241,7 +245,7 @@ def set_dangerous_mine_rules(logic, multiworld, player, world_options: StardewVa def set_farm_buildings_entrance_rules(logic, multiworld, player): - set_entrance_rule(multiworld, player, Entrance.downstairs_to_cellar, logic.building.has_house(3)) + set_entrance_rule(multiworld, player, Entrance.downstairs_to_cellar, logic.building.has_building(Building.cellar)) set_entrance_rule(multiworld, player, Entrance.use_desert_obelisk, logic.can_use_obelisk(Transportation.desert_obelisk)) set_entrance_rule(multiworld, player, Entrance.enter_greenhouse, logic.received("Greenhouse")) set_entrance_rule(multiworld, player, Entrance.enter_coop, logic.building.has_building(Building.coop)) diff --git a/worlds/stardew_valley/strings/building_names.py b/worlds/stardew_valley/strings/building_names.py index b67808fe..50c84b67 100644 --- a/worlds/stardew_valley/strings/building_names.py +++ b/worlds/stardew_valley/strings/building_names.py @@ -14,9 +14,11 @@ class Building: stable = "Stable" well = "Well" shipping_bin = "Shipping Bin" + farm_house = "Farm House" kitchen = "Kitchen" kids_room = "Kids Room" cellar = "Cellar" + pet_bowl = "Pet Bowl" class ModBuilding: diff --git a/worlds/stardew_valley/test/TestBooksanity.py b/worlds/stardew_valley/test/TestBooksanity.py index 3c737e50..c25924aa 100644 --- a/worlds/stardew_valley/test/TestBooksanity.py +++ b/worlds/stardew_valley/test/TestBooksanity.py @@ -61,11 +61,13 @@ class TestBooksanityNone(SVTestBase): for location in self.multiworld.get_locations(): if not location.name.startswith(shipsanity_prefix): continue + item_to_ship = location.name[len(shipsanity_prefix):] if item_to_ship not in power_books and item_to_ship not in skill_books: continue + with self.subTest(location.name): - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(location) class TestBooksanityPowers(SVTestBase): @@ -107,11 +109,13 @@ class TestBooksanityPowers(SVTestBase): for location in self.multiworld.get_locations(): if not location.name.startswith(shipsanity_prefix): continue + item_to_ship = location.name[len(shipsanity_prefix):] if item_to_ship not in power_books and item_to_ship not in skill_books: continue + with self.subTest(location.name): - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(location) class TestBooksanityPowersAndSkills(SVTestBase): @@ -153,11 +157,13 @@ class TestBooksanityPowersAndSkills(SVTestBase): for location in self.multiworld.get_locations(): if not location.name.startswith(shipsanity_prefix): continue + item_to_ship = location.name[len(shipsanity_prefix):] if item_to_ship not in power_books and item_to_ship not in skill_books: continue + with self.subTest(location.name): - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(location) class TestBooksanityAll(SVTestBase): @@ -199,8 +205,10 @@ class TestBooksanityAll(SVTestBase): for location in self.multiworld.get_locations(): if not location.name.startswith(shipsanity_prefix): continue + item_to_ship = location.name[len(shipsanity_prefix):] if item_to_ship not in power_books and item_to_ship not in skill_books: continue + with self.subTest(location.name): - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(location) diff --git a/worlds/stardew_valley/test/TestCrops.py b/worlds/stardew_valley/test/TestCrops.py index 4fa836a9..53048259 100644 --- a/worlds/stardew_valley/test/TestCrops.py +++ b/worlds/stardew_valley/test/TestCrops.py @@ -1,5 +1,9 @@ from . import SVTestBase from .. import options +from ..strings.ap_names.transport_names import Transportation +from ..strings.building_names import Building +from ..strings.region_names import Region +from ..strings.seed_names import Seed class TestCropsanityRules(SVTestBase): @@ -8,13 +12,13 @@ class TestCropsanityRules(SVTestBase): } def test_need_greenhouse_for_cactus(self): - harvest_cactus = self.world.logic.region.can_reach_location("Harvest Cactus Fruit") - self.assert_rule_false(harvest_cactus, self.multiworld.state) + harvest_cactus_fruit = "Harvest Cactus Fruit" + self.assert_cannot_reach_location(harvest_cactus_fruit) - self.multiworld.state.collect(self.create_item("Cactus Seeds")) - self.multiworld.state.collect(self.create_item("Shipping Bin")) - self.multiworld.state.collect(self.create_item("Desert Obelisk")) - self.assert_rule_false(harvest_cactus, self.multiworld.state) + self.multiworld.state.collect(self.create_item(Seed.cactus)) + self.multiworld.state.collect(self.create_item(Building.shipping_bin)) + self.multiworld.state.collect(self.create_item(Transportation.desert_obelisk)) + self.assert_cannot_reach_location(harvest_cactus_fruit) - self.multiworld.state.collect(self.create_item("Greenhouse")) - self.assert_rule_true(harvest_cactus, self.multiworld.state) + self.multiworld.state.collect(self.create_item(Region.greenhouse)) + self.assert_can_reach_location(harvest_cactus_fruit) diff --git a/worlds/stardew_valley/test/TestFarmType.py b/worlds/stardew_valley/test/TestFarmType.py index f78edc3e..1bb4404a 100644 --- a/worlds/stardew_valley/test/TestFarmType.py +++ b/worlds/stardew_valley/test/TestFarmType.py @@ -1,3 +1,5 @@ +from collections import Counter + from . import SVTestBase from .assertion import WorldAssertMixin from .. import options @@ -5,27 +7,49 @@ from .. import options class TestStartInventoryStandardFarm(WorldAssertMixin, SVTestBase): options = { - options.FarmType.internal_name: options.FarmType.option_standard, + options.FarmType: options.FarmType.option_standard, } def test_start_inventory_progressive_coops(self): - start_items = dict(map(lambda x: (x.name, self.multiworld.precollected_items[self.player].count(x)), self.multiworld.precollected_items[self.player])) - items = dict(map(lambda x: (x.name, self.multiworld.itempool.count(x)), self.multiworld.itempool)) + start_items = Counter((i.name for i in self.multiworld.precollected_items[self.player])) + items = Counter((i.name for i in self.multiworld.itempool)) + self.assertIn("Progressive Coop", items) self.assertEqual(items["Progressive Coop"], 3) self.assertNotIn("Progressive Coop", start_items) + def test_coop_is_not_logically_available(self): + self.assert_rule_false(self.world.logic.building.has_building("Coop")) -class TestStartInventoryMeadowLands(WorldAssertMixin, SVTestBase): + +class TestStartInventoryMeadowLandsProgressiveBuilding(WorldAssertMixin, SVTestBase): options = { - options.FarmType.internal_name: options.FarmType.option_meadowlands, - options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.FarmType: options.FarmType.option_meadowlands, + options.BuildingProgression: options.BuildingProgression.option_progressive, } def test_start_inventory_progressive_coops(self): - start_items = dict(map(lambda x: (x.name, self.multiworld.precollected_items[self.player].count(x)), self.multiworld.precollected_items[self.player])) - items = dict(map(lambda x: (x.name, self.multiworld.itempool.count(x)), self.multiworld.itempool)) + start_items = Counter((i.name for i in self.multiworld.precollected_items[self.player])) + items = Counter((i.name for i in self.multiworld.itempool)) + self.assertIn("Progressive Coop", items) self.assertEqual(items["Progressive Coop"], 2) self.assertIn("Progressive Coop", start_items) self.assertEqual(start_items["Progressive Coop"], 1) + + def test_coop_is_logically_available(self): + self.assert_rule_true(self.world.logic.building.has_building("Coop")) + + +class TestStartInventoryMeadowLandsVanillaBuildings(WorldAssertMixin, SVTestBase): + options = { + options.FarmType: options.FarmType.option_meadowlands, + options.BuildingProgression: options.BuildingProgression.option_vanilla, + } + + def test_start_inventory_has_no_coop(self): + start_items = Counter((i.name for i in self.multiworld.precollected_items[self.player])) + self.assertNotIn("Progressive Coop", start_items) + + def test_coop_is_logically_available(self): + self.assert_rule_true(self.world.logic.building.has_building("Coop")) diff --git a/worlds/stardew_valley/test/TestGeneration.py b/worlds/stardew_valley/test/TestGeneration.py index 38882136..35cd2007 100644 --- a/worlds/stardew_valley/test/TestGeneration.py +++ b/worlds/stardew_valley/test/TestGeneration.py @@ -3,13 +3,30 @@ from typing import List from BaseClasses import ItemClassification, Item from . import SVTestBase from .. import items, location_table, options -from ..items import Group +from ..items import Group, ItemData from ..locations import LocationTags from ..options import Friendsanity, SpecialOrderLocations, Shipsanity, Chefsanity, SeasonRandomization, Craftsanity, ExcludeGingerIsland, SkillProgression, \ Booksanity, Walnutsanity from ..strings.region_names import Region +def get_all_permanent_progression_items() -> List[ItemData]: + """Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression. + """ + return [ + item + for item in items.all_items + if ItemClassification.progression in item.classification + if item.mod_name is None + if item.name not in {event.name for event in items.events} + if item.name not in {deprecated.name for deprecated in items.items_by_group[Group.DEPRECATED]} + if item.name not in {season.name for season in items.items_by_group[Group.SEASON]} + if item.name not in {weapon.name for weapon in items.items_by_group[Group.WEAPON]} + if item.name not in {baby.name for baby in items.items_by_group[Group.BABY]} + if item.name != "The Gateway Gazette" + ] + + class TestBaseItemGeneration(SVTestBase): options = { SeasonRandomization.internal_name: SeasonRandomization.option_progressive, @@ -25,17 +42,8 @@ class TestBaseItemGeneration(SVTestBase): } def test_all_progression_items_are_added_to_the_pool(self): - all_created_items = [item.name for item in self.multiworld.itempool] - # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression - items_to_ignore = [event.name for event in items.events] - items_to_ignore.extend(item.name for item in items.all_items if item.mod_name is not None) - items_to_ignore.extend(deprecated.name for deprecated in items.items_by_group[Group.DEPRECATED]) - items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) - items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) - items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) - items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) - items_to_ignore.append("The Gateway Gazette") - progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression and item.name not in items_to_ignore] + all_created_items = set(self.get_all_created_items()) + progression_items = get_all_permanent_progression_items() for progression_item in progression_items: with self.subTest(f"{progression_item.name}"): self.assertIn(progression_item.name, all_created_items) @@ -45,19 +53,19 @@ class TestBaseItemGeneration(SVTestBase): self.assertEqual(len(non_event_locations), len(self.multiworld.itempool)) def test_does_not_create_deprecated_items(self): - all_created_items = [item.name for item in self.multiworld.itempool] + all_created_items = set(self.get_all_created_items()) for deprecated_item in items.items_by_group[items.Group.DEPRECATED]: with self.subTest(f"{deprecated_item.name}"): self.assertNotIn(deprecated_item.name, all_created_items) def test_does_not_create_more_than_one_maximum_one_items(self): - all_created_items = [item.name for item in self.multiworld.itempool] + all_created_items = self.get_all_created_items() for maximum_one_item in items.items_by_group[items.Group.MAXIMUM_ONE]: with self.subTest(f"{maximum_one_item.name}"): self.assertLessEqual(all_created_items.count(maximum_one_item.name), 1) - def test_does_not_create_exactly_two_items(self): - all_created_items = [item.name for item in self.multiworld.itempool] + def test_does_not_create_or_create_two_of_exactly_two_items(self): + all_created_items = self.get_all_created_items() for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: with self.subTest(f"{exactly_two_item.name}"): count = all_created_items.count(exactly_two_item.name) @@ -77,17 +85,10 @@ class TestNoGingerIslandItemGeneration(SVTestBase): } def test_all_progression_items_except_island_are_added_to_the_pool(self): - all_created_items = [item.name for item in self.multiworld.itempool] - # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression - items_to_ignore = [event.name for event in items.events] - items_to_ignore.extend(item.name for item in items.all_items if item.mod_name is not None) - items_to_ignore.extend(deprecated.name for deprecated in items.items_by_group[Group.DEPRECATED]) - items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) - items_to_ignore.extend(season.name for season in items.items_by_group[Group.WEAPON]) - items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) - items_to_ignore.append("The Gateway Gazette") - progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression and item.name not in items_to_ignore] + all_created_items = set(self.get_all_created_items()) + progression_items = get_all_permanent_progression_items() for progression_item in progression_items: + with self.subTest(f"{progression_item.name}"): if Group.GINGER_ISLAND in progression_item.groups: self.assertNotIn(progression_item.name, all_created_items) @@ -100,19 +101,19 @@ class TestNoGingerIslandItemGeneration(SVTestBase): self.assertEqual(len(non_event_locations), len(self.multiworld.itempool)) def test_does_not_create_deprecated_items(self): - all_created_items = [item.name for item in self.multiworld.itempool] + all_created_items = self.get_all_created_items() for deprecated_item in items.items_by_group[items.Group.DEPRECATED]: with self.subTest(f"Deprecated item: {deprecated_item.name}"): self.assertNotIn(deprecated_item.name, all_created_items) def test_does_not_create_more_than_one_maximum_one_items(self): - all_created_items = [item.name for item in self.multiworld.itempool] + all_created_items = self.get_all_created_items() for maximum_one_item in items.items_by_group[items.Group.MAXIMUM_ONE]: with self.subTest(f"{maximum_one_item.name}"): self.assertLessEqual(all_created_items.count(maximum_one_item.name), 1) def test_does_not_create_exactly_two_items(self): - all_created_items = [item.name for item in self.multiworld.itempool] + all_created_items = self.get_all_created_items() for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: with self.subTest(f"{exactly_two_item.name}"): count = all_created_items.count(exactly_two_item.name) diff --git a/worlds/stardew_valley/test/TestLogic.py b/worlds/stardew_valley/test/TestLogic.py index 85279ad2..7a6b81ea 100644 --- a/worlds/stardew_valley/test/TestLogic.py +++ b/worlds/stardew_valley/test/TestLogic.py @@ -49,9 +49,9 @@ class LogicTestBase(RuleAssertMixin, TestCase): self.assert_rule_can_be_resolved(rule, self.multiworld.state) def test_given_building_rule_then_can_be_resolved(self): - for building in self.logic.registry.building_rules.keys(): + for building in self.world.content.farm_buildings: with self.subTest(msg=building): - rule = self.logic.registry.building_rules[building] + rule = self.logic.building.can_build(building) self.assert_rule_can_be_resolved(rule, self.multiworld.state) def test_given_quest_rule_then_can_be_resolved(self): diff --git a/worlds/stardew_valley/test/TestOptionFlags.py b/worlds/stardew_valley/test/TestOptionFlags.py index 88f2257c..2833649e 100644 --- a/worlds/stardew_valley/test/TestOptionFlags.py +++ b/worlds/stardew_valley/test/TestOptionFlags.py @@ -8,9 +8,8 @@ class TestBitFlagsVanilla(SVTestBase): BuildingProgression.internal_name: BuildingProgression.option_vanilla} def test_options_are_not_detected_as_progressive(self): - world_options = self.world.options tool_progressive = self.world.content.features.tool_progression.is_progressive - building_progressive = world_options.building_progression & BuildingProgression.option_progressive + building_progressive = self.world.content.features.building_progression.is_progressive self.assertFalse(tool_progressive) self.assertFalse(building_progressive) @@ -25,9 +24,8 @@ class TestBitFlagsVanillaCheap(SVTestBase): BuildingProgression.internal_name: BuildingProgression.option_vanilla_cheap} def test_options_are_not_detected_as_progressive(self): - world_options = self.world.options tool_progressive = self.world.content.features.tool_progression.is_progressive - building_progressive = world_options.building_progression & BuildingProgression.option_progressive + building_progressive = self.world.content.features.building_progression.is_progressive self.assertFalse(tool_progressive) self.assertFalse(building_progressive) @@ -42,9 +40,8 @@ class TestBitFlagsVanillaVeryCheap(SVTestBase): BuildingProgression.internal_name: BuildingProgression.option_vanilla_very_cheap} def test_options_are_not_detected_as_progressive(self): - world_options = self.world.options tool_progressive = self.world.content.features.tool_progression.is_progressive - building_progressive = world_options.building_progression & BuildingProgression.option_progressive + building_progressive = self.world.content.features.building_progression.is_progressive self.assertFalse(tool_progressive) self.assertFalse(building_progressive) @@ -59,9 +56,8 @@ class TestBitFlagsProgressive(SVTestBase): BuildingProgression.internal_name: BuildingProgression.option_progressive} def test_options_are_detected_as_progressive(self): - world_options = self.world.options tool_progressive = self.world.content.features.tool_progression.is_progressive - building_progressive = world_options.building_progression & BuildingProgression.option_progressive + building_progressive = self.world.content.features.building_progression.is_progressive self.assertTrue(tool_progressive) self.assertTrue(building_progressive) @@ -76,9 +72,8 @@ class TestBitFlagsProgressiveCheap(SVTestBase): BuildingProgression.internal_name: BuildingProgression.option_progressive_cheap} def test_options_are_detected_as_progressive(self): - world_options = self.world.options tool_progressive = self.world.content.features.tool_progression.is_progressive - building_progressive = world_options.building_progression & BuildingProgression.option_progressive + building_progressive = self.world.content.features.building_progression.is_progressive self.assertTrue(tool_progressive) self.assertTrue(building_progressive) @@ -93,9 +88,8 @@ class TestBitFlagsProgressiveVeryCheap(SVTestBase): BuildingProgression.internal_name: BuildingProgression.option_progressive_very_cheap} def test_options_are_detected_as_progressive(self): - world_options = self.world.options tool_progressive = self.world.content.features.tool_progression.is_progressive - building_progressive = world_options.building_progression & BuildingProgression.option_progressive + building_progressive = self.world.content.features.building_progression.is_progressive self.assertTrue(tool_progressive) self.assertTrue(building_progressive) diff --git a/worlds/stardew_valley/test/TestWalnutsanity.py b/worlds/stardew_valley/test/TestWalnutsanity.py index e3f06bf1..5cc2f79e 100644 --- a/worlds/stardew_valley/test/TestWalnutsanity.py +++ b/worlds/stardew_valley/test/TestWalnutsanity.py @@ -70,7 +70,6 @@ class TestWalnutsanityPuzzles(SVTestBase): def test_field_office_locations_require_professor_snail(self): location_names = ["Complete Large Animal Collection", "Complete Snake Collection", "Complete Mummified Frog Collection", "Complete Mummified Bat Collection", "Purple Flowers Island Survey", "Purple Starfish Island Survey", ] - locations = [location for location in self.multiworld.get_locations() if location.name in location_names] self.collect("Island Obelisk") self.collect("Island North Turtle") self.collect("Island West Turtle") @@ -84,11 +83,11 @@ class TestWalnutsanityPuzzles(SVTestBase): self.collect("Progressive Sword", 5) self.collect("Combat Level", 10) self.collect("Mining Level", 10) - for location in locations: - self.assert_cannot_reach_location(location, self.multiworld.state) + for location in location_names: + self.assert_cannot_reach_location(location) self.collect("Open Professor Snail Cave") - for location in locations: - self.assert_can_reach_location(location, self.multiworld.state) + for location in location_names: + self.assert_can_reach_location(location) class TestWalnutsanityBushes(SVTestBase): diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py index f06e7d07..800b2105 100644 --- a/worlds/stardew_valley/test/__init__.py +++ b/worlds/stardew_valley/test/__init__.py @@ -1,3 +1,4 @@ +import itertools import logging import os import threading @@ -11,7 +12,8 @@ from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_mul from worlds.AutoWorld import call_all from .assertion import RuleAssertMixin from .options.utils import fill_namespace_with_default, parse_class_option_keys, fill_dataclass_with_default -from .. import StardewValleyWorld, StardewItem +from .. import StardewValleyWorld, StardewItem, StardewRule +from ..logic.time_logic import MONTH_COEFFICIENT from ..options import StardewValleyOption logger = logging.getLogger(__name__) @@ -96,6 +98,12 @@ class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase): return False return super().run_default_tests + def collect_months(self, months: int) -> None: + real_total_prog_items = self.world.total_progression_items + percent = months * MONTH_COEFFICIENT + self.collect("Stardrop", real_total_prog_items * 100 // percent) + self.world.total_progression_items = real_total_prog_items + def collect_lots_of_money(self, percent: float = 0.25): self.collect("Shipping Bin") real_total_prog_items = self.world.total_progression_items @@ -145,12 +153,35 @@ class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase): def create_item(self, item: str) -> StardewItem: return self.world.create_item(item) + def get_all_created_items(self) -> list[str]: + return [item.name for item in itertools.chain(self.multiworld.get_items(), self.multiworld.precollected_items[self.player])] + def remove_one_by_name(self, item: str) -> None: self.remove(self.create_item(item)) - def reset_collection_state(self): + def reset_collection_state(self) -> None: self.multiworld.state = self.original_state.copy() + def assert_rule_true(self, rule: StardewRule, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_rule_true(rule, state) + + def assert_rule_false(self, rule: StardewRule, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_rule_false(rule, state) + + def assert_can_reach_location(self, location: Location | str, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_can_reach_location(location, state) + + def assert_cannot_reach_location(self, location: Location | str, state: CollectionState | None = None) -> None: + if state is None: + state = self.multiworld.state + super().assert_cannot_reach_location(location, state) + pre_generated_worlds = {} diff --git a/worlds/stardew_valley/test/content/__init__.py b/worlds/stardew_valley/test/content/__init__.py index 0832c2e3..626277ca 100644 --- a/worlds/stardew_valley/test/content/__init__.py +++ b/worlds/stardew_valley/test/content/__init__.py @@ -2,9 +2,11 @@ import unittest from typing import ClassVar, Tuple from ...content import content_packs, ContentPack, StardewContent, unpack_content, StardewFeatures, feature +from ...strings.building_names import Building default_features = StardewFeatures( feature.booksanity.BooksanityDisabled(), + feature.building_progression.BuildingProgressionVanilla(starting_buildings={Building.farm_house}), feature.cropsanity.CropsanityDisabled(), feature.fishsanity.FishsanityNone(), feature.friendsanity.FriendsanityNone(), diff --git a/worlds/stardew_valley/test/long/TestOptionsLong.py b/worlds/stardew_valley/test/long/TestOptionsLong.py index 0c8cfcb1..81bb4d1f 100644 --- a/worlds/stardew_valley/test/long/TestOptionsLong.py +++ b/worlds/stardew_valley/test/long/TestOptionsLong.py @@ -6,7 +6,6 @@ from .option_names import all_option_choices from .. import SVTestCase, solo_multiworld from ..assertion.world_assert import WorldAssertMixin from ... import options -from ...mods.mod_data import ModNames class TestGenerateDynamicOptions(WorldAssertMixin, SVTestCase): @@ -34,13 +33,11 @@ class TestDynamicOptionDebug(WorldAssertMixin, SVTestCase): def test_option_pair_debug(self): option_dict = { - options.Goal.internal_name: options.Goal.option_master_angler, - options.QuestLocations.internal_name: -1, - options.Fishsanity.internal_name: options.Fishsanity.option_all, - options.Mods.internal_name: frozenset({ModNames.sve}), + options.Goal.internal_name: options.Goal.option_cryptic_note, + options.EntranceRandomization.internal_name: options.EntranceRandomization.option_buildings, } for i in range(1): - seed = get_seed() + seed = get_seed(76312028554502615508) with self.subTest(f"Seed: {seed}"): print(f"Seed: {seed}") with solo_multiworld(option_dict, seed=seed) as (multiworld, _): diff --git a/worlds/stardew_valley/test/mods/TestMods.py b/worlds/stardew_valley/test/mods/TestMods.py index b4d10f2e..932c76c6 100644 --- a/worlds/stardew_valley/test/mods/TestMods.py +++ b/worlds/stardew_valley/test/mods/TestMods.py @@ -1,11 +1,12 @@ import random -from BaseClasses import get_seed, ItemClassification +from BaseClasses import get_seed from .. import SVTestBase, SVTestCase +from ..TestGeneration import get_all_permanent_progression_items from ..assertion import ModAssertMixin, WorldAssertMixin from ..options.presets import allsanity_mods_6_x_x from ..options.utils import fill_dataclass_with_default -from ... import options, items, Group, create_content +from ... import options, Group, create_content from ...mods.mod_data import ModNames from ...options import SkillProgression, Walnutsanity from ...options.options import all_mods @@ -109,17 +110,8 @@ class TestBaseItemGeneration(SVTestBase): } def test_all_progression_items_are_added_to_the_pool(self): - all_created_items = [item.name for item in self.multiworld.itempool] - # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression - items_to_ignore = [event.name for event in items.events] - items_to_ignore.extend(deprecated.name for deprecated in items.items_by_group[Group.DEPRECATED]) - items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) - items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) - items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) - items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) - items_to_ignore.append("The Gateway Gazette") - progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression - and item.name not in items_to_ignore] + all_created_items = self.get_all_created_items() + progression_items = get_all_permanent_progression_items() for progression_item in progression_items: with self.subTest(f"{progression_item.name}"): self.assertIn(progression_item.name, all_created_items) @@ -139,17 +131,8 @@ class TestNoGingerIslandModItemGeneration(SVTestBase): } def test_all_progression_items_except_island_are_added_to_the_pool(self): - all_created_items = [item.name for item in self.multiworld.itempool] - # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression - items_to_ignore = [event.name for event in items.events] - items_to_ignore.extend(deprecated.name for deprecated in items.items_by_group[Group.DEPRECATED]) - items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) - items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) - items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) - items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) - items_to_ignore.append("The Gateway Gazette") - progression_items = [item for item in items.all_items if item.classification & ItemClassification.progression - and item.name not in items_to_ignore] + all_created_items = self.get_all_created_items() + progression_items = get_all_permanent_progression_items() for progression_item in progression_items: with self.subTest(f"{progression_item.name}"): if Group.GINGER_ISLAND in progression_item.groups: diff --git a/worlds/stardew_valley/test/options/presets.py b/worlds/stardew_valley/test/options/presets.py index aecdeadd..57f8b0be 100644 --- a/worlds/stardew_valley/test/options/presets.py +++ b/worlds/stardew_valley/test/options/presets.py @@ -16,6 +16,7 @@ def default_6_x_x(): options.ElevatorProgression.internal_name: options.ElevatorProgression.default, options.EntranceRandomization.internal_name: options.EntranceRandomization.default, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.default, + options.FarmType.internal_name: options.FarmType.default, options.FestivalLocations.internal_name: options.FestivalLocations.default, options.Fishsanity.internal_name: options.Fishsanity.default, options.Friendsanity.internal_name: options.Friendsanity.default, @@ -52,6 +53,7 @@ def allsanity_no_mods_6_x_x(): options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.FarmType.internal_name: options.FarmType.option_standard, options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, options.Fishsanity.internal_name: options.Fishsanity.option_all, options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, @@ -100,6 +102,7 @@ def get_minsanity_options(): options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.FarmType.internal_name: options.FarmType.option_meadowlands, options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, options.Fishsanity.internal_name: options.Fishsanity.option_none, options.Friendsanity.internal_name: options.Friendsanity.option_none, @@ -136,6 +139,7 @@ def minimal_locations_maximal_items(): options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, options.EntranceRandomization.internal_name: options.EntranceRandomization.option_disabled, options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.FarmType.internal_name: options.FarmType.option_meadowlands, options.FestivalLocations.internal_name: options.FestivalLocations.option_disabled, options.Fishsanity.internal_name: options.Fishsanity.option_none, options.Friendsanity.internal_name: options.Friendsanity.option_none, diff --git a/worlds/stardew_valley/test/rules/TestArcades.py b/worlds/stardew_valley/test/rules/TestArcades.py index 69e5b22c..5fdf7df1 100644 --- a/worlds/stardew_valley/test/rules/TestArcades.py +++ b/worlds/stardew_valley/test/rules/TestArcades.py @@ -11,7 +11,7 @@ class TestArcadeMachinesLogic(SVTestBase): self.assertFalse(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) - self.assertFalse(self.world.logic.region.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) + self.assert_cannot_reach_location("Journey of the Prairie King Victory") boots = self.create_item("JotPK: Progressive Boots") gun = self.create_item("JotPK: Progressive Gun") @@ -24,7 +24,7 @@ class TestArcadeMachinesLogic(SVTestBase): self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) - self.assertFalse(self.world.logic.region.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) + self.assert_cannot_reach_location("Journey of the Prairie King Victory") self.remove(boots) self.remove(gun) @@ -33,7 +33,7 @@ class TestArcadeMachinesLogic(SVTestBase): self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) - self.assertFalse(self.world.logic.region.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) + self.assert_cannot_reach_location("Journey of the Prairie King Victory") self.remove(boots) self.remove(boots) @@ -44,7 +44,7 @@ class TestArcadeMachinesLogic(SVTestBase): self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertFalse(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) - self.assertFalse(self.world.logic.region.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) + self.assert_cannot_reach_location("Journey of the Prairie King Victory") self.remove(boots) self.remove(gun) self.remove(ammo) @@ -60,7 +60,7 @@ class TestArcadeMachinesLogic(SVTestBase): self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) - self.assertFalse(self.world.logic.region.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) + self.assert_cannot_reach_location("Journey of the Prairie King Victory") self.remove(boots) self.remove(gun) self.remove(gun) @@ -83,7 +83,7 @@ class TestArcadeMachinesLogic(SVTestBase): self.assertTrue(self.world.logic.region.can_reach("JotPK World 1")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 2")(self.multiworld.state)) self.assertTrue(self.world.logic.region.can_reach("JotPK World 3")(self.multiworld.state)) - self.assertTrue(self.world.logic.region.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) + self.assert_can_reach_location("Journey of the Prairie King Victory") self.remove(boots) self.remove(boots) self.remove(gun) diff --git a/worlds/stardew_valley/test/rules/TestBooks.py b/worlds/stardew_valley/test/rules/TestBooks.py index af0055d2..4cd84a77 100644 --- a/worlds/stardew_valley/test/rules/TestBooks.py +++ b/worlds/stardew_valley/test/rules/TestBooks.py @@ -7,18 +7,15 @@ class TestBooksLogic(SVTestBase): options.Booksanity.internal_name: options.Booksanity.option_all, } - def test_need_weapon_for_mapping_cave_systems(self): - self.collect_lots_of_money(0.5) - - location = self.multiworld.get_location("Read Mapping Cave Systems", self.player) - - self.assert_cannot_reach_location(location, self.multiworld.state) + def test_can_get_mapping_cave_systems_with_weapon_and_time(self): + self.collect_months(12) + self.assert_cannot_reach_location("Read Mapping Cave Systems") self.collect("Progressive Mine Elevator") self.collect("Progressive Mine Elevator") self.collect("Progressive Mine Elevator") self.collect("Progressive Mine Elevator") - self.assert_cannot_reach_location(location, self.multiworld.state) + self.assert_cannot_reach_location("Read Mapping Cave Systems") self.collect("Progressive Weapon") - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location("Read Mapping Cave Systems") diff --git a/worlds/stardew_valley/test/rules/TestBuildings.py b/worlds/stardew_valley/test/rules/TestBuildings.py index d1f60b20..8eeb9d29 100644 --- a/worlds/stardew_valley/test/rules/TestBuildings.py +++ b/worlds/stardew_valley/test/rules/TestBuildings.py @@ -9,45 +9,37 @@ class TestBuildingLogic(SVTestBase): } def test_coop_blueprint(self): - self.assertFalse(self.world.logic.region.can_reach_location("Coop Blueprint")(self.multiworld.state)) + self.assert_cannot_reach_location("Coop Blueprint") self.collect_lots_of_money() - self.assertTrue(self.world.logic.region.can_reach_location("Coop Blueprint")(self.multiworld.state)) + self.assert_can_reach_location("Coop Blueprint") def test_big_coop_blueprint(self): - big_coop_blueprint_rule = self.world.logic.region.can_reach_location("Big Coop Blueprint") - self.assertFalse(big_coop_blueprint_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") + self.assert_cannot_reach_location("Big Coop Blueprint") self.collect_lots_of_money() - self.assertFalse(big_coop_blueprint_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") + self.assert_cannot_reach_location("Big Coop Blueprint") self.multiworld.state.collect(self.create_item("Progressive Coop")) - self.assertTrue(big_coop_blueprint_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") + self.assert_can_reach_location("Big Coop Blueprint") def test_deluxe_coop_blueprint(self): - self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) + self.assert_cannot_reach_location("Deluxe Coop Blueprint") self.collect_lots_of_money() - self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) + self.assert_cannot_reach_location("Deluxe Coop Blueprint") self.multiworld.state.collect(self.create_item("Progressive Coop")) - self.assertFalse(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) + self.assert_cannot_reach_location("Deluxe Coop Blueprint") self.multiworld.state.collect(self.create_item("Progressive Coop")) - self.assertTrue(self.world.logic.region.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) + self.assert_can_reach_location("Deluxe Coop Blueprint") def test_big_shed_blueprint(self): - big_shed_rule = self.world.logic.region.can_reach_location("Big Shed Blueprint") - self.assertFalse(big_shed_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") + self.assert_cannot_reach_location("Big Shed Blueprint") self.collect_lots_of_money() - self.assertFalse(big_shed_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") + self.assert_cannot_reach_location("Big Shed Blueprint") self.multiworld.state.collect(self.create_item("Progressive Shed")) - self.assertTrue(big_shed_rule(self.multiworld.state), - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") + self.assert_can_reach_location("Big Shed Blueprint") diff --git a/worlds/stardew_valley/test/rules/TestBundles.py b/worlds/stardew_valley/test/rules/TestBundles.py index 0bc7f9bf..918cb8ab 100644 --- a/worlds/stardew_valley/test/rules/TestBundles.py +++ b/worlds/stardew_valley/test/rules/TestBundles.py @@ -11,10 +11,10 @@ class TestBundlesLogic(SVTestBase): } def test_vault_2500g_bundle(self): - self.assertFalse(self.world.logic.region.can_reach_location("2,500g Bundle")(self.multiworld.state)) + self.assert_cannot_reach_location("2,500g Bundle") self.collect_lots_of_money() - self.assertTrue(self.world.logic.region.can_reach_location("2,500g Bundle")(self.multiworld.state)) + self.assert_can_reach_location("2,500g Bundle") class TestRemixedBundlesLogic(SVTestBase): @@ -25,10 +25,10 @@ class TestRemixedBundlesLogic(SVTestBase): } def test_sticky_bundle_has_grind_rules(self): - self.assertFalse(self.world.logic.region.can_reach_location("Sticky Bundle")(self.multiworld.state)) + self.assert_cannot_reach_location("Sticky Bundle") self.collect_all_the_money() - self.assertTrue(self.world.logic.region.can_reach_location("Sticky Bundle")(self.multiworld.state)) + self.assert_can_reach_location("Sticky Bundle") class TestRaccoonBundlesLogic(SVTestBase): @@ -40,11 +40,6 @@ class TestRaccoonBundlesLogic(SVTestBase): seed = 2 # Magic seed that does what I want. Might need to get changed if we change the randomness behavior of raccoon bundles def test_raccoon_bundles_rely_on_previous_ones(self): - # The first raccoon bundle is a fishing one - raccoon_rule_1 = self.world.logic.region.can_reach_location("Raccoon Request 1") - - # The 3th raccoon bundle is a foraging one - raccoon_rule_3 = self.world.logic.region.can_reach_location("Raccoon Request 3") self.collect("Progressive Raccoon", 6) self.collect("Progressive Mine Elevator", 24) self.collect("Mining Level", 12) @@ -58,10 +53,12 @@ class TestRaccoonBundlesLogic(SVTestBase): self.collect("Fishing Level", 10) self.collect("Furnace Recipe") - self.assertFalse(raccoon_rule_1(self.multiworld.state)) - self.assertFalse(raccoon_rule_3(self.multiworld.state)) + # The first raccoon bundle is a fishing one + self.assert_cannot_reach_location("Raccoon Request 1") + # The third raccoon bundle is a foraging one + self.assert_cannot_reach_location("Raccoon Request 3") self.collect("Fish Smoker Recipe") - self.assertTrue(raccoon_rule_1(self.multiworld.state)) - self.assertTrue(raccoon_rule_3(self.multiworld.state)) + self.assert_can_reach_location("Raccoon Request 1") + self.assert_can_reach_location("Raccoon Request 3") diff --git a/worlds/stardew_valley/test/rules/TestCookingRecipes.py b/worlds/stardew_valley/test/rules/TestCookingRecipes.py index d5f9da73..b3aafdb6 100644 --- a/worlds/stardew_valley/test/rules/TestCookingRecipes.py +++ b/worlds/stardew_valley/test/rules/TestCookingRecipes.py @@ -14,18 +14,17 @@ class TestRecipeLearnLogic(SVTestBase): def test_can_learn_qos_recipe(self): location = "Cook Radish Salad" - rule = self.world.logic.region.can_reach_location(location) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Progressive House")) self.multiworld.state.collect(self.create_item("Radish Seeds")) self.multiworld.state.collect(self.create_item("Spring")) self.multiworld.state.collect(self.create_item("Summer")) self.collect_lots_of_money() - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("The Queen of Sauce")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_can_reach_location(location) class TestRecipeReceiveLogic(SVTestBase): @@ -39,34 +38,32 @@ class TestRecipeReceiveLogic(SVTestBase): def test_can_learn_qos_recipe(self): location = "Cook Radish Salad" - rule = self.world.logic.region.can_reach_location(location) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Progressive House")) self.multiworld.state.collect(self.create_item("Radish Seeds")) self.multiworld.state.collect(self.create_item("Summer")) self.collect_lots_of_money() - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) spring = self.create_item("Spring") qos = self.create_item("The Queen of Sauce") self.multiworld.state.collect(spring) self.multiworld.state.collect(qos) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) self.multiworld.state.remove(spring) self.multiworld.state.remove(qos) self.multiworld.state.collect(self.create_item("Radish Salad Recipe")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_can_reach_location(location) def test_get_chefsanity_check_recipe(self): location = "Radish Salad Recipe" - rule = self.world.logic.region.can_reach_location(location) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) self.multiworld.state.collect(self.create_item("Spring")) self.collect_lots_of_money() - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) seeds = self.create_item("Radish Seeds") summer = self.create_item("Summer") @@ -74,10 +71,10 @@ class TestRecipeReceiveLogic(SVTestBase): self.multiworld.state.collect(seeds) self.multiworld.state.collect(summer) self.multiworld.state.collect(house) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location(location) self.multiworld.state.remove(seeds) self.multiworld.state.remove(summer) self.multiworld.state.remove(house) self.multiworld.state.collect(self.create_item("The Queen of Sauce")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_can_reach_location(location) diff --git a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py index 46a1b73d..94d6bc14 100644 --- a/worlds/stardew_valley/test/rules/TestCraftingRecipes.py +++ b/worlds/stardew_valley/test/rules/TestCraftingRecipes.py @@ -13,8 +13,6 @@ class TestCraftsanityLogic(SVTestBase): } def test_can_craft_recipe(self): - location = "Craft Marble Brazier" - rule = self.world.logic.region.can_reach_location(location) self.collect([self.create_item("Progressive Pickaxe")] * 4) self.collect([self.create_item("Progressive Fishing Rod")] * 4) self.collect([self.create_item("Progressive Sword")] * 4) @@ -23,18 +21,16 @@ class TestCraftsanityLogic(SVTestBase): self.collect([self.create_item("Combat Level")] * 10) self.collect([self.create_item("Fishing Level")] * 10) self.collect_all_the_money() - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Craft Marble Brazier") self.multiworld.state.collect(self.create_item("Marble Brazier Recipe")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_can_reach_location("Craft Marble Brazier") def test_can_learn_crafting_recipe(self): - location = "Marble Brazier Recipe" - rule = self.world.logic.region.can_reach_location(location) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Marble Brazier Recipe") self.collect_lots_of_money() - self.assert_rule_true(rule, self.multiworld.state) + self.assert_can_reach_location("Marble Brazier Recipe") def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] @@ -42,13 +38,13 @@ class TestCraftsanityLogic(SVTestBase): self.multiworld.state.collect(self.create_item("Torch Recipe")) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_rule_false(rule) self.multiworld.state.collect(self.create_item("Fall")) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_rule_false(rule) self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_rule_true(rule) def test_require_furnace_recipe_for_smelting_checks(self): locations = ["Craft Furnace", "Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"] @@ -83,13 +79,13 @@ class TestCraftsanityWithFestivalsLogic(SVTestBase): self.multiworld.state.collect(self.create_item("Fall")) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_rule_false(rule) self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe")) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_rule_false(rule) self.multiworld.state.collect(self.create_item("Torch Recipe")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_rule_true(rule) class TestNoCraftsanityLogic(SVTestBase): @@ -105,7 +101,7 @@ class TestNoCraftsanityLogic(SVTestBase): def test_can_craft_recipe(self): recipe = all_crafting_recipes_by_name["Wood Floor"] rule = self.world.logic.crafting.can_craft(recipe) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_rule_true(rule) def test_can_craft_festival_recipe(self): recipe = all_crafting_recipes_by_name["Jack-O-Lantern"] @@ -116,7 +112,7 @@ class TestNoCraftsanityLogic(SVTestBase): self.assertFalse(result) self.collect([self.create_item("Progressive Season")] * 2) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_rule_true(rule) def test_requires_mining_levels_for_smelting_checks(self): locations = ["Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"] @@ -151,7 +147,7 @@ class TestNoCraftsanityWithFestivalsLogic(SVTestBase): self.multiworld.state.collect(self.create_item("Fall")) self.collect_lots_of_money() rule = self.world.logic.crafting.can_craft(recipe) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_rule_false(rule) self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe")) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_rule_true(rule) diff --git a/worlds/stardew_valley/test/rules/TestDonations.py b/worlds/stardew_valley/test/rules/TestDonations.py index 3927bd09..d50f87d3 100644 --- a/worlds/stardew_valley/test/rules/TestDonations.py +++ b/worlds/stardew_valley/test/rules/TestDonations.py @@ -16,12 +16,12 @@ class TestDonationLogicAll(SVTestBase): self.collect_all_except(railroad_item) for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]: - self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) + self.assert_cannot_reach_location(donation.name) self.multiworld.state.collect(self.create_item(railroad_item)) for donation in locations_by_tag[LocationTags.MUSEUM_DONATIONS]: - self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) + self.assert_can_reach_location(donation.name) class TestDonationLogicRandomized(SVTestBase): @@ -37,12 +37,12 @@ class TestDonationLogicRandomized(SVTestBase): LocationTags.MUSEUM_DONATIONS in location_table[location.name].tags] for donation in donation_locations: - self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) + self.assert_cannot_reach_location(donation.name) self.multiworld.state.collect(self.create_item(railroad_item)) for donation in donation_locations: - self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) + self.assert_can_reach_location(donation.name) class TestDonationLogicMilestones(SVTestBase): @@ -56,12 +56,12 @@ class TestDonationLogicMilestones(SVTestBase): self.collect_all_except(railroad_item) for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]: - self.assertFalse(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) + self.assert_cannot_reach_location(donation.name) self.multiworld.state.collect(self.create_item(railroad_item)) for donation in locations_by_tag[LocationTags.MUSEUM_MILESTONES]: - self.assertTrue(self.world.logic.region.can_reach_location(donation.name)(self.multiworld.state)) + self.assert_can_reach_location(donation.name) def swap_museum_and_bathhouse(multiworld, player): diff --git a/worlds/stardew_valley/test/rules/TestFishing.py b/worlds/stardew_valley/test/rules/TestFishing.py index 74a33f36..6a6a4bb3 100644 --- a/worlds/stardew_valley/test/rules/TestFishing.py +++ b/worlds/stardew_valley/test/rules/TestFishing.py @@ -42,19 +42,18 @@ class TestNeedRegionToCatchFish(SVTestBase): with self.subTest(f"Region rules for {fish}"): self.collect_all_the_money() item_names = fish_and_items[fish] - location = self.multiworld.get_location(f"Fishsanity: {fish}", self.player) - self.assert_cannot_reach_location(location, self.multiworld.state) + self.assert_cannot_reach_location(f"Fishsanity: {fish}") items = [] for item_name in item_names: items.append(self.collect(item_name)) with self.subTest(f"{fish} can be reached with {item_names}"): - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(f"Fishsanity: {fish}") for item_required in items: self.multiworld.state = self.original_state.copy() with self.subTest(f"{fish} requires {item_required.name}"): for item_to_collect in items: if item_to_collect.name != item_required.name: self.collect(item_to_collect) - self.assert_cannot_reach_location(location, self.multiworld.state) + self.assert_cannot_reach_location(f"Fishsanity: {fish}") self.multiworld.state = self.original_state.copy() diff --git a/worlds/stardew_valley/test/rules/TestFriendship.py b/worlds/stardew_valley/test/rules/TestFriendship.py index 3e9109ed..9cd3127a 100644 --- a/worlds/stardew_valley/test/rules/TestFriendship.py +++ b/worlds/stardew_valley/test/rules/TestFriendship.py @@ -47,12 +47,8 @@ class TestFriendsanityDatingRules(SVTestBase): for i in range(1, max_reachable + 1): if i % step != 0 and i != 14: continue - location = f"{prefix}{npc} {i}{suffix}" - can_reach = self.world.logic.region.can_reach_location(location)(self.multiworld.state) - self.assertTrue(can_reach, f"Should be able to earn relationship up to {i} hearts") + self.assert_can_reach_location(f"{prefix}{npc} {i}{suffix}") for i in range(max_reachable + 1, 14 + 1): if i % step != 0 and i != 14: continue - location = f"{prefix}{npc} {i}{suffix}" - can_reach = self.world.logic.region.can_reach_location(location)(self.multiworld.state) - self.assertFalse(can_reach, f"Should not be able to earn relationship up to {i} hearts") + self.assert_cannot_reach_location(f"{prefix}{npc} {i}{suffix}") diff --git a/worlds/stardew_valley/test/rules/TestShipping.py b/worlds/stardew_valley/test/rules/TestShipping.py index 125b7f31..fc61ae8e 100644 --- a/worlds/stardew_valley/test/rules/TestShipping.py +++ b/worlds/stardew_valley/test/rules/TestShipping.py @@ -76,10 +76,8 @@ class TestShipsanityEverything(SVTestBase): for location in shipsanity_locations: with self.subTest(location.name): - self.assertFalse(self.world.logic.region.can_reach_location(location.name)(self.multiworld.state)) + self.assert_cannot_reach_location(location.name) self.collect(bin_item) - shipsanity_rule = self.world.logic.region.can_reach_location(location.name) - self.assert_rule_true(shipsanity_rule, self.multiworld.state) - + self.assert_can_reach_location(location.name) self.remove(bin_item) diff --git a/worlds/stardew_valley/test/rules/TestSkills.py b/worlds/stardew_valley/test/rules/TestSkills.py index ee605bfa..a5957488 100644 --- a/worlds/stardew_valley/test/rules/TestSkills.py +++ b/worlds/stardew_valley/test/rules/TestSkills.py @@ -35,14 +35,13 @@ class TestSkillProgressionProgressive(SVTestBase): for level in range(1, 11): location_name = f"Level {level} {skill}" - location = self.multiworld.get_location(location_name, self.player) with self.subTest(location_name): if level > 1: - self.assert_cannot_reach_location(location, self.multiworld.state) + self.assert_cannot_reach_location(location_name) self.collect(f"{skill} Level") - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(location_name) self.reset_collection_state() @@ -87,8 +86,7 @@ class TestSkillProgressionProgressiveWithMasteryWithoutMods(SVTestBase): for skill in all_vanilla_skills: with self.subTest(skill): - location = self.multiworld.get_location(f"{skill} Mastery", self.player) - self.assert_can_reach_location(location, self.multiworld.state) + self.assert_can_reach_location(f"{skill} Mastery") self.reset_collection_state() @@ -98,8 +96,7 @@ class TestSkillProgressionProgressiveWithMasteryWithoutMods(SVTestBase): self.collect_everything() self.remove_one_by_name(f"{skill} Level") - location = self.multiworld.get_location(f"{skill} Mastery", self.player) - self.assert_cannot_reach_location(location, self.multiworld.state) + self.assert_cannot_reach_location(f"{skill} Mastery") self.reset_collection_state() @@ -107,7 +104,6 @@ class TestSkillProgressionProgressiveWithMasteryWithoutMods(SVTestBase): self.collect_everything() self.remove_one_by_name(f"Progressive Pickaxe") - location = self.multiworld.get_location("Mining Mastery", self.player) - self.assert_cannot_reach_location(location, self.multiworld.state) + self.assert_cannot_reach_location("Mining Mastery") self.reset_collection_state() diff --git a/worlds/stardew_valley/test/rules/TestTools.py b/worlds/stardew_valley/test/rules/TestTools.py index 31dd5819..bda29e3d 100644 --- a/worlds/stardew_valley/test/rules/TestTools.py +++ b/worlds/stardew_valley/test/rules/TestTools.py @@ -18,37 +18,37 @@ class TestProgressiveToolsLogic(SVTestBase): self.multiworld.state.prog_items = {1: Counter()} sturgeon_rule = self.world.logic.has("Sturgeon") - self.assert_rule_false(sturgeon_rule, self.multiworld.state) + self.assert_rule_false(sturgeon_rule) summer = self.create_item("Summer") self.multiworld.state.collect(summer) - self.assert_rule_false(sturgeon_rule, self.multiworld.state) + self.assert_rule_false(sturgeon_rule) fishing_rod = self.create_item("Progressive Fishing Rod") self.multiworld.state.collect(fishing_rod) self.multiworld.state.collect(fishing_rod) - self.assert_rule_false(sturgeon_rule, self.multiworld.state) + self.assert_rule_false(sturgeon_rule) fishing_level = self.create_item("Fishing Level") self.multiworld.state.collect(fishing_level) - self.assert_rule_false(sturgeon_rule, self.multiworld.state) + self.assert_rule_false(sturgeon_rule) self.multiworld.state.collect(fishing_level) self.multiworld.state.collect(fishing_level) self.multiworld.state.collect(fishing_level) self.multiworld.state.collect(fishing_level) self.multiworld.state.collect(fishing_level) - self.assert_rule_true(sturgeon_rule, self.multiworld.state) + self.assert_rule_true(sturgeon_rule) self.remove(summer) - self.assert_rule_false(sturgeon_rule, self.multiworld.state) + self.assert_rule_false(sturgeon_rule) winter = self.create_item("Winter") self.multiworld.state.collect(winter) - self.assert_rule_true(sturgeon_rule, self.multiworld.state) + self.assert_rule_true(sturgeon_rule) self.remove(fishing_rod) - self.assert_rule_false(sturgeon_rule, self.multiworld.state) + self.assert_rule_false(sturgeon_rule) def test_old_master_cannoli(self): self.multiworld.state.prog_items = {1: Counter()} @@ -58,35 +58,34 @@ class TestProgressiveToolsLogic(SVTestBase): self.multiworld.state.collect(self.create_item("Summer")) self.collect_lots_of_money() - rule = self.world.logic.region.can_reach_location("Old Master Cannoli") - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Old Master Cannoli") fall = self.create_item("Fall") self.multiworld.state.collect(fall) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Old Master Cannoli") tuesday = self.create_item("Traveling Merchant: Tuesday") self.multiworld.state.collect(tuesday) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Old Master Cannoli") rare_seed = self.create_item("Rare Seed") self.multiworld.state.collect(rare_seed) - self.assert_rule_true(rule, self.multiworld.state) + self.assert_can_reach_location("Old Master Cannoli") self.remove(fall) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Old Master Cannoli") self.remove(tuesday) green_house = self.create_item("Greenhouse") self.multiworld.state.collect(green_house) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Old Master Cannoli") friday = self.create_item("Traveling Merchant: Friday") self.multiworld.state.collect(friday) - self.assertTrue(self.multiworld.get_location("Old Master Cannoli", 1).access_rule(self.multiworld.state)) + self.assert_can_reach_location("Old Master Cannoli") self.remove(green_house) - self.assert_rule_false(rule, self.multiworld.state) + self.assert_cannot_reach_location("Old Master Cannoli") self.remove(friday) @@ -106,13 +105,13 @@ class TestToolVanillaRequiresBlacksmith(SVTestBase): for tool in [Tool.pickaxe, Tool.axe, Tool.hoe, Tool.trash_can, Tool.watering_can]: for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]: - self.assert_rule_false(self.world.logic.tool.has_tool(tool, material), self.multiworld.state) + self.assert_rule_false(self.world.logic.tool.has_tool(tool, material)) self.multiworld.state.collect(self.create_item(railroad_item)) for tool in [Tool.pickaxe, Tool.axe, Tool.hoe, Tool.trash_can, Tool.watering_can]: for material in [ToolMaterial.copper, ToolMaterial.iron, ToolMaterial.gold, ToolMaterial.iridium]: - self.assert_rule_true(self.world.logic.tool.has_tool(tool, material), self.multiworld.state) + self.assert_rule_true(self.world.logic.tool.has_tool(tool, material)) def test_cannot_get_fishing_rod_without_willy_access(self): railroad_item = "Railroad Boulder Removed" @@ -120,12 +119,12 @@ class TestToolVanillaRequiresBlacksmith(SVTestBase): self.collect_all_except(railroad_item) for fishing_rod_level in [3, 4]: - self.assert_rule_false(self.world.logic.tool.has_fishing_rod(fishing_rod_level), self.multiworld.state) + self.assert_rule_false(self.world.logic.tool.has_fishing_rod(fishing_rod_level)) self.multiworld.state.collect(self.create_item(railroad_item)) for fishing_rod_level in [3, 4]: - self.assert_rule_true(self.world.logic.tool.has_fishing_rod(fishing_rod_level), self.multiworld.state) + self.assert_rule_true(self.world.logic.tool.has_fishing_rod(fishing_rod_level)) def place_region_at_entrance(multiworld, player, region, entrance):