mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Stardew Valley: Refactor buildings to use content packs (#4239)
* create building data object and rename ItemSource to Source to be more generic # Conflicts: # worlds/stardew_valley/content/game_content.py # Conflicts: # worlds/stardew_valley/data/artisan.py # worlds/stardew_valley/data/game_item.py # worlds/stardew_valley/data/harvest.py # worlds/stardew_valley/data/shop.py * remove compound sources, replace by other requirements which already handle this usecase * add coops to content packs * add building progression in game features * add shippping bin to starting building; remove has_house * replace config check with feature * add other buildings in content packs * not passing * tests passes, unbelievable * use newly create methods more * use new assets to ease readability * self review * fix flake8 maybe * properly split rule for mapping cave systems * fix tractor garage name * self review * add upgrade_from to farm house buldings * don't override building name variable in logic * remove has_group from buildings * mark some items easy in grinding logic so blueprints buildings can be in more early spheres * move stuff around to maybe avoid future conflicts cuz I have like 10 PRs opened right now * remove price_multiplier, turns out it's unused during generation * disable shop source for mapping cave systems * bunch of code review changes * add petbowl and farmhouse to autobuilding * set min easy items to 300 * fix farm type
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from . import booksanity
|
||||
from . import building_progression
|
||||
from . import cropsanity
|
||||
from . import fishsanity
|
||||
from . import friendsanity
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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),
|
||||
))),
|
||||
# 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,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
16
worlds/stardew_valley/data/building.py
Normal file
16
worlds/stardew_valley/data/building.py
Normal file
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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]):
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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] = {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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, _):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user