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 10 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
22 changes: 21 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,22 @@ 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 == options.ToolProgression.option_vanilla
Jouramie marked this conversation as resolved.
Show resolved Hide resolved
or tool_option == options.ToolProgression.option_vanilla_cheap
or tool_option == options.ToolProgression.option_vanilla_very_cheap):
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 == options.ToolProgression.option_progressive
or tool_option == options.ToolProgression.option_progressive_cheap
or tool_option == options.ToolProgression.option_progressive_very_cheap):
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 @@ -471,7 +471,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
7 changes: 4 additions & 3 deletions worlds/stardew_valley/logic/mine_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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
Expand Down Expand Up @@ -59,7 +58,8 @@ def can_progress_in_the_mines_from_floor(self, floor: int) -> StardewRule:
weapon_rule = self.logic.mine.get_weapon_rule_for_floor_tier(tier)
rules.append(weapon_rule)

if self.options.tool_progression & ToolProgression.option_progressive:
# No alternative for vanilla because we assume you will get ores to upgrade your tools in the mines.
Jouramie marked this conversation as resolved.
Show resolved Hide resolved
if self.content.features.tool_progression.is_progressive:
rules.append(self.logic.tool.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier]))

# No alternative for vanilla because we assume that you will grind the levels in the mines.
Expand Down Expand Up @@ -88,7 +88,8 @@ def can_progress_in_the_skull_cavern_from_floor(self, floor: int) -> StardewRule
weapon_rule = self.logic.combat.has_great_weapon
rules.append(weapon_rule)

if self.options.tool_progression & ToolProgression.option_progressive:
# No alternative for vanilla because we assume you will get ores to upgrade your tools in the mines.
if self.content.features.tool_progression.is_progressive:
rules.append(self.logic.received("Progressive Pickaxe", min(4, max(0, tier + 2))))

# No alternative for vanilla because we assume that you will grind the levels in the mines.
Expand Down
5 changes: 2 additions & 3 deletions worlds/stardew_valley/logic/tool_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
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
Expand Down Expand Up @@ -57,7 +56,7 @@ 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])
Expand All @@ -76,7 +75,7 @@ def can_use_tool_at(self, tool: str, material: str, region: str) -> StardewRule:
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:
if self.content.features.tool_progression.is_progressive:
return self.logic.received(f"Progressive {Tool.fishing_rod}", level)

if level <= 2:
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
11 changes: 5 additions & 6 deletions worlds/stardew_valley/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
from .logic.time_logic import MAX_MONTHS
from .logic.tool_logic import tool_upgrade_prices
from .mods.mod_data import ModNames
from .options import StardewValleyOptions, Walnutsanity
from .options import ToolProgression, BuildingProgression, ExcludeGingerIsland, SpecialOrderLocations, Museumsanity, BackpackProgression, Shipsanity, \
Monstersanity, Chefsanity, Craftsanity, ArcadeMachineLocations, Cooksanity
from .options import BuildingProgression, 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
from .stardew_rule.rule_explain import explain
Expand Down Expand Up @@ -69,7 +68,7 @@ def set_rules(world):
set_entrance_rules(logic, multiworld, player, world_options)
set_ginger_island_rules(logic, multiworld, player, world_options)

set_tool_rules(logic, multiworld, player, world_options)
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)
Expand Down Expand Up @@ -111,8 +110,8 @@ def set_isolated_locations_rules(logic: StardewLogic, multiworld, player):
logic.season.has(Season.spring))


def set_tool_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions):
if not world_options.tool_progression & ToolProgression.option_progressive:
def set_tool_rules(logic: StardewLogic, multiworld, player, content: StardewContent):
if not content.features.tool_progression.is_progressive:
return

MultiWorldRules.add_rule(multiworld.get_location("Purchase Fiberglass Rod", player),
Expand Down
6 changes: 3 additions & 3 deletions worlds/stardew_valley/test/TestGeneration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from .. import items, location_table, options
from ..items import Group
from ..locations import LocationTags
from ..options import Friendsanity, SpecialOrderLocations, Shipsanity, Chefsanity, SeasonRandomization, Craftsanity, ExcludeGingerIsland, ToolProgression, \
SkillProgression, Booksanity, Walnutsanity
from ..options import Friendsanity, SpecialOrderLocations, Shipsanity, Chefsanity, SeasonRandomization, Craftsanity, ExcludeGingerIsland, SkillProgression, \
Booksanity, Walnutsanity
from ..strings.region_names import Region


Expand Down Expand Up @@ -320,7 +320,7 @@ def generate_items_for_extra_mine_levels(self, weapon_name: str) -> List[Item]:
class TestSkullCavernLogic(SVTestBase):
options = {
options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla,
ToolProgression.internal_name: ToolProgression.option_progressive,
options.ToolProgression.internal_name: options.ToolProgression.option_progressive,
options.SkillProgression.internal_name: options.SkillProgression.option_progressive,
}

Expand Down
Loading
Loading