Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stardew Valley: Move progressive tool options handling in features #4374

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion worlds/stardew_valley/content/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from . import content_packs
from .feature import cropsanity, friendsanity, fishsanity, booksanity, skill_progression
from .feature import cropsanity, friendsanity, fishsanity, booksanity, skill_progression, tool_progression
from .game_content import ContentPack, StardewContent, StardewFeatures
from .unpacking import unpack_content
from .. import options
Expand Down Expand Up @@ -33,6 +33,7 @@ def choose_features(player_options: options.StardewValleyOptions) -> StardewFeat
choose_fishsanity(player_options.fishsanity),
choose_friendsanity(player_options.friendsanity, player_options.friendsanity_heart_size),
choose_skill_progression(player_options.skill_progression),
choose_tool_progression(player_options.tool_progression, player_options.skill_progression),
)


Expand Down Expand Up @@ -122,3 +123,18 @@ def choose_skill_progression(skill_progression_option: options.SkillProgression)
raise ValueError(f"No skill progression feature mapped to {str(skill_progression_option.value)}")

return skill_progression_feature


def choose_tool_progression(tool_option: options.ToolProgression, skill_option: options.SkillProgression) -> tool_progression.ToolProgressionFeature:
if tool_option.is_vanilla:
return tool_progression.ToolProgressionVanilla()

tools_distribution = tool_progression.get_tools_distribution(
progressive_tools_enabled=True,
skill_masteries_enabled=skill_option == options.SkillProgression.option_progressive_with_masteries,
)

if tool_option.is_progressive:
return tool_progression.ToolProgressionProgressive(tools_distribution)

raise ValueError(f"No tool progression feature mapped to {str(tool_option.value)}")
1 change: 1 addition & 0 deletions worlds/stardew_valley/content/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from . import fishsanity
from . import friendsanity
from . import skill_progression
from . import tool_progression
68 changes: 68 additions & 0 deletions worlds/stardew_valley/content/feature/tool_progression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from abc import ABC
from collections import Counter
from collections.abc import Mapping
from dataclasses import dataclass, field
from functools import cache
from types import MappingProxyType
from typing import ClassVar

from ...strings.tool_names import Tool


def to_progressive_item(tool: str) -> str:
"""Return the name of the progressive item."""
return f"Progressive {tool}"


# The golden scythe is always randomized
VANILLA_TOOL_DISTRIBUTION = MappingProxyType({
Tool.scythe: 1,
})

PROGRESSIVE_TOOL_DISTRIBUTION = MappingProxyType({
Tool.axe: 4,
Tool.hoe: 4,
Tool.pickaxe: 4,
Tool.pan: 4,
Tool.trash_can: 4,
Tool.watering_can: 4,
Tool.fishing_rod: 4,
})

# Masteries add another tier to the scythe and the fishing rod
SKILL_MASTERIES_TOOL_DISTRIBUTION = MappingProxyType({
Tool.scythe: 1,
Tool.fishing_rod: 1,
})


@cache
def get_tools_distribution(progressive_tools_enabled: bool, skill_masteries_enabled: bool) -> Mapping[str, int]:
distribution = Counter(VANILLA_TOOL_DISTRIBUTION)

if progressive_tools_enabled:
distribution += PROGRESSIVE_TOOL_DISTRIBUTION

if skill_masteries_enabled:
distribution += SKILL_MASTERIES_TOOL_DISTRIBUTION

return MappingProxyType(distribution)


@dataclass(frozen=True)
class ToolProgressionFeature(ABC):
is_progressive: ClassVar[bool]
tool_distribution: Mapping[str, int]

to_progressive_item = staticmethod(to_progressive_item)


@dataclass(frozen=True)
class ToolProgressionVanilla(ToolProgressionFeature):
is_progressive = False
# FIXME change the default_factory to a simple default when python 3.11 is no longer supported
tool_distribution: Mapping[str, int] = field(default_factory=lambda: VANILLA_TOOL_DISTRIBUTION)


class ToolProgressionProgressive(ToolProgressionFeature):
is_progressive = True
3 changes: 2 additions & 1 deletion worlds/stardew_valley/content/game_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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
from .feature import booksanity, cropsanity, fishsanity, friendsanity, skill_progression, tool_progression
from ..data.fish_data import FishItem
from ..data.game_item import GameItem, ItemSource, ItemTag
from ..data.skill import Skill
Expand Down Expand Up @@ -54,6 +54,7 @@ class StardewFeatures:
fishsanity: fishsanity.FishsanityFeature
friendsanity: friendsanity.FriendsanityFeature
skill_progression: skill_progression.SkillProgressionFeature
tool_progression: tool_progression.ToolProgressionFeature


@dataclass(frozen=True)
Expand Down
2 changes: 1 addition & 1 deletion worlds/stardew_valley/early_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def setup_early_items(multiworld, options: stardew_options.StardewValleyOptions,
if options.backpack_progression == stardew_options.BackpackProgression.option_early_progressive:
early_forced.append("Progressive Backpack")

if options.tool_progression & stardew_options.ToolProgression.option_progressive:
if content.features.tool_progression.is_progressive:
if content.features.fishsanity.is_enabled:
early_candidates.append("Progressive Fishing Rod")
early_forced.append("Progressive Pickaxe")
Expand Down
34 changes: 12 additions & 22 deletions worlds/stardew_valley/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
from .logic.logic_event import all_events
from .mods.mod_data import ModNames
from .options import StardewValleyOptions, TrapItems, FestivalLocations, ExcludeGingerIsland, SpecialOrderLocations, SeasonRandomization, Museumsanity, \
BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression, ArcadeMachineLocations, Monstersanity, Goal, \
BuildingProgression, 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
from .strings.ap_names.buff_names import Buff
from .strings.ap_names.community_upgrade_names import CommunityUpgrade
from .strings.ap_names.mods.mod_items import SVEQuestItem
from .strings.currency_names import Currency
from .strings.tool_names import Tool
from .strings.wallet_item_names import Wallet

ITEM_CODE_OFFSET = 717000
Expand Down Expand Up @@ -119,11 +120,6 @@ def __call__(self, name: Union[str, ItemData], override_classification: ItemClas
raise NotImplementedError


class StardewItemDeleter(Protocol):
def __call__(self, item: Item):
raise NotImplementedError


def load_item_csv():
from importlib.resources import files

Expand Down Expand Up @@ -226,7 +222,7 @@ def create_unique_items(item_factory: StardewItemFactory, options: StardewValley
create_weapons(item_factory, options, items)
items.append(item_factory("Skull Key"))
create_elevators(item_factory, options, items)
create_tools(item_factory, options, content, items)
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)
Expand Down Expand Up @@ -316,23 +312,17 @@ def create_elevators(item_factory: StardewItemFactory, options: StardewValleyOpt
items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8])


def create_tools(item_factory: StardewItemFactory, options: StardewValleyOptions, content: StardewContent, items: List[Item]):
if options.tool_progression & ToolProgression.option_progressive:
for item_data in items_by_group[Group.PROGRESSIVE_TOOLS]:
name = item_data.name
if "Trash Can" in name:
items.extend([item_factory(item) for item in [item_data] * 3])
items.append(item_factory(item_data, ItemClassification.useful))
else:
items.extend([item_factory(item) for item in [item_data] * 4])
def create_tools(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
tool_progression = content.features.tool_progression
for tool, count in tool_progression.tool_distribution.items():
item = item_table[tool_progression.to_progressive_item(tool)]

if content.features.skill_progression.are_masteries_shuffled:
# Masteries add another tier to the scythe and the fishing rod
items.append(item_factory("Progressive Scythe"))
items.append(item_factory("Progressive Fishing Rod"))
# Trash can is only used in tool upgrade logic, so the last trash can is not progression because it basically does not unlock anything.
if tool == Tool.trash_can:
count -= 1
items.append(item_factory(item, ItemClassification.useful))

# The golden scythe is always randomized
items.append(item_factory("Progressive Scythe"))
items.extend([item_factory(item) for _ in range(count)])


def create_skills(item_factory: StardewItemFactory, content: StardewContent, items: List[Item]):
Expand Down
4 changes: 2 additions & 2 deletions worlds/stardew_valley/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .data.museum_data import all_museum_items
from .mods.mod_data import ModNames
from .options import ExcludeGingerIsland, ArcadeMachineLocations, SpecialOrderLocations, Museumsanity, \
FestivalLocations, BuildingProgression, ToolProgression, ElevatorProgression, BackpackProgression, FarmType
FestivalLocations, BuildingProgression, 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
Expand Down Expand Up @@ -473,7 +473,7 @@ def create_locations(location_collector: StardewLocationCollector,
extend_bundle_locations(randomized_locations, bundle_rooms)
extend_backpack_locations(randomized_locations, options)

if options.tool_progression & ToolProgression.option_progressive:
if content.features.tool_progression.is_progressive:
randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE])

extend_elevator_locations(randomized_locations, options)
Expand Down
1 change: 0 additions & 1 deletion worlds/stardew_valley/logic/action_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .tool_logic import ToolLogicMixin
from ..options import ToolProgression
from ..stardew_rule import StardewRule, True_
from ..strings.generic_names import Generic
from ..strings.geode_names import Geode
Expand Down
13 changes: 7 additions & 6 deletions worlds/stardew_valley/logic/mine_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
from .skill_logic import SkillLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..options import ToolProgression
from ..stardew_rule import StardewRule, True_
from ..strings.performance_names import Performance
from ..strings.region_names import Region
from ..strings.skill_names import Skill
from ..strings.tool_names import Tool, ToolMaterial
from ..strings.tool_names import ToolMaterial


class MineLogicMixin(BaseLogicMixin):
Expand Down Expand Up @@ -56,11 +55,12 @@ def get_weapon_rule_for_floor_tier(self, tier: int):
def can_progress_in_the_mines_from_floor(self, floor: int) -> StardewRule:
tier = floor // 40
rules = []

weapon_rule = self.logic.mine.get_weapon_rule_for_floor_tier(tier)
rules.append(weapon_rule)

if self.options.tool_progression & ToolProgression.option_progressive:
rules.append(self.logic.tool.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier]))
tool_rule = self.logic.tool.can_mine_using(ToolMaterial.tiers[tier])
rules.append(tool_rule)

# No alternative for vanilla because we assume that you will grind the levels in the mines.
if self.content.features.skill_progression.is_progressive:
Expand All @@ -85,11 +85,12 @@ def has_mine_elevator_to_floor(self, floor: int) -> StardewRule:
def can_progress_in_the_skull_cavern_from_floor(self, floor: int) -> StardewRule:
tier = floor // 50
rules = []

weapon_rule = self.logic.combat.has_great_weapon
rules.append(weapon_rule)

if self.options.tool_progression & ToolProgression.option_progressive:
rules.append(self.logic.received("Progressive Pickaxe", min(4, max(0, tier + 2))))
tool_rule = self.logic.tool.can_mine_using(ToolMaterial.tiers[min(4, max(0, tier + 2))])
rules.append(tool_rule)

# No alternative for vanilla because we assume that you will grind the levels in the mines.
if self.content.features.skill_progression.is_progressive:
Expand Down
27 changes: 20 additions & 7 deletions worlds/stardew_valley/logic/tool_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@
from .region_logic import RegionLogicMixin
from .season_logic import SeasonLogicMixin
from ..mods.logic.magic_logic import MagicLogicMixin
from ..options import ToolProgression
from ..stardew_rule import StardewRule, True_, False_
from ..strings.ap_names.skill_level_names import ModSkillLevel
from ..strings.region_names import Region
from ..strings.region_names import Region, LogicRegion
from ..strings.spells import MagicSpell
from ..strings.tool_names import ToolMaterial, Tool
from ..strings.tool_names import ToolMaterial, Tool, APTool

fishing_rod_prices = {
3: 1800,
Expand Down Expand Up @@ -57,10 +56,10 @@ def has_tool(self, tool: str, material: str = ToolMaterial.basic) -> StardewRule
if material == ToolMaterial.basic or tool == Tool.scythe:
return True_()

if self.options.tool_progression & ToolProgression.option_progressive:
if self.content.features.tool_progression.is_progressive:
return self.logic.received(f"Progressive {tool}", tool_materials[material])

can_upgrade_rule = self.logic.has(f"{material} Bar") & self.logic.money.can_spend_at(Region.blacksmith, tool_upgrade_prices[material])
can_upgrade_rule = self.logic.tool._can_purchase_upgrade(material)
if tool == Tool.pan:
has_base_pan = self.logic.received("Glittering Boulder Removed") & self.logic.region.can_reach(Region.mountain)
if material == ToolMaterial.copper:
Expand All @@ -69,15 +68,29 @@ def has_tool(self, tool: str, material: str = ToolMaterial.basic) -> StardewRule

return can_upgrade_rule

@cache_self1
def can_mine_using(self, material: str) -> StardewRule:
if material == ToolMaterial.basic:
return self.logic.true_

if self.content.features.tool_progression.is_progressive:
return self.logic.received(APTool.pickaxe, tool_materials[material])
else:
return self.logic.tool._can_purchase_upgrade(material)

@cache_self1
def _can_purchase_upgrade(self, material: str) -> StardewRule:
return self.logic.region.can_reach(LogicRegion.blacksmith_upgrade(material))

def can_use_tool_at(self, tool: str, material: str, region: str) -> StardewRule:
return self.has_tool(tool, material) & self.logic.region.can_reach(region)

@cache_self1
def has_fishing_rod(self, level: int) -> StardewRule:
assert 1 <= level <= 4, "Fishing rod 0 isn't real, it can't hurt you. Training is 1, Bamboo is 2, Fiberglass is 3 and Iridium is 4."

if self.options.tool_progression & ToolProgression.option_progressive:
return self.logic.received(f"Progressive {Tool.fishing_rod}", level)
if self.content.features.tool_progression.is_progressive:
return self.logic.received(APTool.fishing_rod, level)

if level <= 2:
# We assume you always have access to the Bamboo pole, because mod side there is a builtin way to get it back.
Expand Down
10 changes: 3 additions & 7 deletions worlds/stardew_valley/mods/logic/item_logic.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from typing import Dict, Union

from ..mod_data import ModNames
from ... import options
from ...data.craftable_data import all_crafting_recipes_by_name
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.combat_logic import CombatLogicMixin
from ...logic.cooking_logic import CookingLogicMixin
Expand All @@ -20,11 +18,9 @@
from ...logic.skill_logic import SkillLogicMixin
from ...logic.time_logic import TimeLogicMixin
from ...logic.tool_logic import ToolLogicMixin
from ...options import Cropsanity
from ...stardew_rule import StardewRule, True_
from ...stardew_rule import StardewRule
from ...strings.artisan_good_names import ModArtisanGood
from ...strings.craftable_names import ModCraftable, ModMachine
from ...strings.fish_names import ModTrash
from ...strings.craftable_names import ModCraftable
from ...strings.ingredient_names import Ingredient
from ...strings.material_names import Material
from ...strings.metal_names import all_fossils, all_artifacts, Ore, ModFossil
Expand Down Expand Up @@ -83,7 +79,7 @@ def get_modified_item_rules_for_deep_woods(self, items: Dict[str, StardewRule]):
# Gingerbread House
}

if self.options.tool_progression & options.ToolProgression.option_progressive:
if self.content.features.tool_progression.is_progressive:
options_to_update.update({
Ore.iridium: items[Ore.iridium] | self.logic.tool.can_use_tool_at(Tool.axe, ToolMaterial.iridium, DeepWoodsRegion.floor_50), # Iridium Tree
})
Expand Down
8 changes: 8 additions & 0 deletions worlds/stardew_valley/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ class ToolProgression(Choice):
option_progressive_cheap = 0b011 # 3
option_progressive_very_cheap = 0b101 # 5

@property
def is_vanilla(self):
return not self.is_progressive

@property
def is_progressive(self):
return bool(self.value & self.option_progressive)


class ElevatorProgression(Choice):
"""Shuffle the elevator?
Expand Down
Loading
Loading