mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-28 13:13:23 -07:00
Major Content update for Stardew Valley ### Features - New BundleRandomization Value: Meme Bundles - Over 100 custom bundles, designed to be jokes, references, trolls, etc - New Setting: Bundles Per Room modifier - New Setting: Backpack Size - New Setting: Secretsanity - Checks for triggering easter eggs and secrets - New Setting: Moviesanity - Checks for watching movies and sharing snacks with Villagers - New Setting: Eatsanity - Checks for eating items - New Setting: Hatsanity - Checks for wearing Hats - New Setting: Start Without - Allows you to select any combination of various "starting" items, that you will actually not start with. Notably, tools, backpack slots, Day5 unlocks, etc. - New Setting: Allowed Filler Items - Allows you to customize the filler items you'll get - New Setting: Endgame Locations - Checks for various expensive endgame tasks and purchases - New Shipsanity value: Crops and Fish - New Settings: Jojapocalypse and settings to customize it - Bundle Plando: Replaced with BundleWhitelist and BundleBlacklist, for more customization freedom - Added a couple of Host.yaml settings to help hosts allow or ban specific difficult settings that could cause problems if the people don't know what they are signing up for. Plus a truckload of improvements on the mod side, not seen in this PR. ### Removed features - Integration for Stardew Valley Expanded. It is simply disabled, the code is all still there, but I'm extremely tired of providing tech support for it, plus Stardew Valley 1.7 was announced and that will break it again, so I'm done. When a maintainer steps up, it can be re-enabled.
212 lines
7.5 KiB
Python
212 lines
7.5 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from functools import cached_property
|
|
from types import MappingProxyType
|
|
from typing import Iterable, Set, Any, Mapping, Generator
|
|
|
|
from .feature import BooksanityFeature, BuildingProgressionFeature, CropsanityFeature, FishsanityFeature, FriendsanityFeature, HatsanityFeature, \
|
|
MuseumsanityFeature, SkillProgressionFeature, ToolProgressionFeature
|
|
from .feature.base import DisableSourceHook, DisableRequirementHook, FeatureBase
|
|
from ..data.animal import Animal
|
|
from ..data.building import Building
|
|
from ..data.fish_data import FishItem
|
|
from ..data.game_item import GameItem, Source, ItemTag, Requirement
|
|
from ..data.hats_data import HatItem
|
|
from ..data.skill import Skill
|
|
from ..data.villagers_data import Villager
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class StardewContent:
|
|
features: StardewFeatures
|
|
registered_packs: Set[str] = field(default_factory=set)
|
|
|
|
# regions -> To be used with can reach rule
|
|
|
|
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)
|
|
animals: dict[str, Animal] = field(default_factory=dict)
|
|
skills: dict[str, Skill] = field(default_factory=dict)
|
|
quests: dict[str, Any] = field(default_factory=dict)
|
|
hats: dict[str, HatItem] = field(default_factory=dict)
|
|
|
|
def find_sources_of_type(self, *types: 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: Source) -> GameItem:
|
|
filtered_sources = list(self._filter_sources(sources))
|
|
|
|
item = self.game_items.setdefault(item_name, GameItem(item_name))
|
|
item.add_sources(filtered_sources)
|
|
|
|
for source in filtered_sources:
|
|
for requirement_name, tags in source.requirement_tags.items():
|
|
self.tag_item(requirement_name, *tags)
|
|
|
|
return item
|
|
|
|
def tag_item(self, item_name: str, *tags: ItemTag):
|
|
item = self.game_items.setdefault(item_name, GameItem(item_name))
|
|
item.add_tags(tags)
|
|
|
|
def untag_item(self, item_name: str, tag: ItemTag):
|
|
self.game_items[item_name].tags.remove(tag)
|
|
|
|
def find_tagged_items(self, tag: ItemTag) -> Generator[GameItem]:
|
|
# TODO might be worth caching this, but it need to only be cached once the content is finalized...
|
|
for item in self.game_items.values():
|
|
if tag in item.tags:
|
|
yield item
|
|
|
|
def are_all_enabled(self, content_packs: frozenset[str]) -> bool:
|
|
return content_packs.issubset(self.registered_packs)
|
|
|
|
def is_enabled(self, content_pack: str | ContentPack) -> bool:
|
|
if isinstance(content_pack, ContentPack):
|
|
content_pack = content_pack.name
|
|
|
|
return content_pack in self.registered_packs
|
|
|
|
def _filter_sources(self, sources: Iterable[Source]) -> Generator[Source]:
|
|
"""Filters out sources that are disabled by features."""
|
|
for source in sources:
|
|
if not self._is_disabled(source):
|
|
yield source
|
|
|
|
def _is_disabled(self, source: Source) -> bool:
|
|
"""Checks if a source is disabled by any feature."""
|
|
try:
|
|
hook = self.features.disable_source_hooks[type(source)]
|
|
if hook(source, content=self):
|
|
return True
|
|
except KeyError:
|
|
pass
|
|
|
|
for requirement in source.all_requirements:
|
|
try:
|
|
hook = self.features.disable_requirement_hooks[type(requirement)]
|
|
if hook(requirement, content=self):
|
|
return True
|
|
except KeyError:
|
|
pass
|
|
|
|
return False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class StardewFeatures:
|
|
booksanity: BooksanityFeature
|
|
building_progression: BuildingProgressionFeature
|
|
cropsanity: CropsanityFeature
|
|
fishsanity: FishsanityFeature
|
|
friendsanity: FriendsanityFeature
|
|
hatsanity: HatsanityFeature
|
|
museumsanity: MuseumsanityFeature
|
|
skill_progression: SkillProgressionFeature
|
|
tool_progression: ToolProgressionFeature
|
|
|
|
@cached_property
|
|
def all_features(self) -> Iterable[FeatureBase]:
|
|
return (self.booksanity, self.building_progression, self.cropsanity, self.fishsanity, self.friendsanity, self.hatsanity, self.museumsanity,
|
|
self.skill_progression, self.tool_progression)
|
|
|
|
@cached_property
|
|
def disable_source_hooks(self) -> Mapping[type[Source], DisableSourceHook]:
|
|
"""Returns a set of source types that are disabled by features. Need to be exact types, subclasses are not considered."""
|
|
hooks = {}
|
|
for feature in self.all_features:
|
|
hooks.update(feature.disable_source_hooks)
|
|
return MappingProxyType(hooks)
|
|
|
|
@cached_property
|
|
def disable_requirement_hooks(self) -> Mapping[type[Requirement], DisableRequirementHook]:
|
|
"""Returns a set of requirement types that are disabled by features. Need to be exact types, subclasses are not considered."""
|
|
hooks = {}
|
|
for feature in self.all_features:
|
|
hooks.update(feature.disable_requirement_hooks)
|
|
return MappingProxyType(hooks)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ContentPack:
|
|
name: str
|
|
|
|
dependencies: Iterable[str] = ()
|
|
""" Hard requirement, generation will fail if it's missing. """
|
|
weak_dependencies: Iterable[str] = ()
|
|
""" Not a strict dependency, only used only for ordering the packs to make sure hooks are applied correctly. """
|
|
|
|
# items
|
|
# def item_hook
|
|
# ...
|
|
|
|
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[Source]] = field(default_factory=dict)
|
|
|
|
def shop_source_hook(self, content: StardewContent):
|
|
...
|
|
|
|
fishes: Iterable[FishItem] = ()
|
|
|
|
def fish_hook(self, content: StardewContent):
|
|
...
|
|
|
|
crafting_sources: Mapping[str, Iterable[Source]] = field(default_factory=dict)
|
|
|
|
def crafting_hook(self, content: StardewContent):
|
|
...
|
|
|
|
artisan_good_sources: Mapping[str, Iterable[Source]] = field(default_factory=dict)
|
|
|
|
def artisan_good_hook(self, content: StardewContent):
|
|
...
|
|
|
|
villagers: Iterable[Villager] = ()
|
|
|
|
def villager_hook(self, content: StardewContent):
|
|
...
|
|
|
|
farm_buildings: Iterable[Building] = ()
|
|
|
|
def farm_building_hook(self, content: StardewContent):
|
|
...
|
|
|
|
animals: Iterable[Animal] = ()
|
|
|
|
def animal_hook(self, content: StardewContent):
|
|
...
|
|
|
|
skills: Iterable[Skill] = ()
|
|
|
|
def skill_hook(self, content: StardewContent):
|
|
...
|
|
|
|
quests: Iterable[Any] = ()
|
|
|
|
def quest_hook(self, content: StardewContent):
|
|
...
|
|
...
|
|
|
|
hat_sources: Mapping[HatItem, Iterable[Source]] = field(default_factory=dict)
|
|
|
|
def hat_source_hook(self, content: StardewContent):
|
|
...
|
|
|
|
def finalize_hook(self, content: StardewContent):
|
|
"""Last hook called on the pack, once all other content packs have been registered.
|
|
|
|
This is the place to do any final adjustments to the content, like adding rules based on tags applied by other packs.
|
|
"""
|
|
...
|