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

* create tool progression feature and unwrap option

* replace option usage with calling feature

* add comment explaining why some logic is a weird place

* replace item creation logic with feature

* self review and add unit tests

* rename test cuz I named them too long

* add a test for the trash can useful stuff cuz I thought there was a bug but turns out it works

* self review again

* remove price_multiplier, turns out it's unused during generation

* damn it 3.11 why are you like this

* use blacksmith region when checking vanilla tools

* fix rule

* move can mine using in tool logic

* remove changes to performance test

* properly set the option I guess

* properly set options 2

* that's what happen when you code too late
This commit is contained in:
Jérémie Bolduc
2025-03-08 11:19:29 -05:00
committed by GitHub
parent b5269e9aa4
commit ee9bcb84b7
20 changed files with 262 additions and 121 deletions

View File

@@ -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
@@ -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),
)
@@ -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)}")

View File

@@ -3,3 +3,4 @@ from . import cropsanity
from . import fishsanity
from . import friendsanity
from . import skill_progression
from . import tool_progression

View File

@@ -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

View File

@@ -3,7 +3,7 @@ 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
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
@@ -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)

View File

@@ -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")

View File

@@ -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, 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
@@ -23,6 +23,7 @@ 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
@@ -119,11 +120,6 @@ class StardewItemFactory(Protocol):
raise NotImplementedError
class StardewItemDeleter(Protocol):
def __call__(self, item: Item):
raise NotImplementedError
def load_item_csv():
from importlib.resources import files
@@ -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)
@@ -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]):

View File

@@ -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, 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
@@ -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)

View File

@@ -10,12 +10,11 @@ from .region_logic import RegionLogicMixin
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):
@@ -56,11 +55,12 @@ SkillLogicMixin, CookingLogicMixin]]):
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:
@@ -85,11 +85,12 @@ SkillLogicMixin, CookingLogicMixin]]):
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:

View File

@@ -8,12 +8,11 @@ from .received_logic import ReceivedLogicMixin
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,
@@ -57,10 +56,10 @@ class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixi
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:
@@ -69,6 +68,20 @@ class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixi
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)
@@ -76,8 +89,8 @@ class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixi
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.

View File

@@ -1,7 +1,6 @@
from typing import Dict, Union
from ..mod_data import ModNames
from ... import options
from ...logic.base_logic import BaseLogicMixin, BaseLogic
from ...logic.combat_logic import CombatLogicMixin
from ...logic.cooking_logic import CookingLogicMixin
@@ -80,7 +79,7 @@ FarmingLogicMixin]]):
# 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
})

View File

@@ -247,6 +247,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?

View File

@@ -19,9 +19,8 @@ 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 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
@@ -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)
@@ -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),
@@ -281,13 +280,6 @@ def set_skull_cavern_floor_entrance_rules(logic, multiworld, player):
set_entrance_rule(multiworld, player, dig_to_skull_floor(floor), rule)
def set_blacksmith_entrance_rules(logic, multiworld, player):
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_copper, MetalBar.copper, ToolMaterial.copper)
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_iron, MetalBar.iron, ToolMaterial.iron)
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_gold, MetalBar.gold, ToolMaterial.gold)
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_iridium, MetalBar.iridium, ToolMaterial.iridium)
def set_skill_entrance_rules(logic, multiworld, player, world_options: StardewValleyOptions):
set_entrance_rule(multiworld, player, LogicEntrance.grow_spring_crops, logic.farming.has_farming_tools & logic.season.has_spring)
set_entrance_rule(multiworld, player, LogicEntrance.grow_summer_crops, logic.farming.has_farming_tools & logic.season.has_summer)
@@ -306,6 +298,13 @@ def set_skill_entrance_rules(logic, multiworld, player, world_options: StardewVa
set_entrance_rule(multiworld, player, LogicEntrance.fishing, logic.skill.can_get_fishing_xp)
def set_blacksmith_entrance_rules(logic, multiworld, player):
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_copper, MetalBar.copper, ToolMaterial.copper)
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_iron, MetalBar.iron, ToolMaterial.iron)
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_gold, MetalBar.gold, ToolMaterial.gold)
set_blacksmith_upgrade_rule(logic, multiworld, player, LogicEntrance.blacksmith_iridium, MetalBar.iridium, ToolMaterial.iridium)
def set_blacksmith_upgrade_rule(logic, multiworld, player, entrance_name: str, item_name: str, tool_material: str):
upgrade_rule = logic.has(item_name) & logic.money.can_spend(tool_upgrade_prices[tool_material])
set_entrance_rule(multiworld, player, entrance_name, upgrade_rule)

View File

@@ -194,10 +194,15 @@ class LogicEntrance:
island_cooking = "Island Cooking"
shipping = "Use Shipping Bin"
watch_queen_of_sauce = "Watch Queen of Sauce"
blacksmith_copper = "Upgrade Copper Tools"
blacksmith_iron = "Upgrade Iron Tools"
blacksmith_gold = "Upgrade Gold Tools"
blacksmith_iridium = "Upgrade Iridium Tools"
@staticmethod
def blacksmith_upgrade(material: str) -> str:
return f"Upgrade {material} Tools"
blacksmith_copper = blacksmith_upgrade("Copper")
blacksmith_iron = blacksmith_upgrade("Iron")
blacksmith_gold = blacksmith_upgrade("Gold")
blacksmith_iridium = blacksmith_upgrade("Iridium")
grow_spring_crops = "Grow Spring Crops"
grow_summer_crops = "Grow Summer Crops"

View File

@@ -159,10 +159,15 @@ class LogicRegion:
kitchen = "Kitchen"
shipping = "Shipping"
queen_of_sauce = "The Queen of Sauce"
blacksmith_copper = "Blacksmith Copper Upgrades"
blacksmith_iron = "Blacksmith Iron Upgrades"
blacksmith_gold = "Blacksmith Gold Upgrades"
blacksmith_iridium = "Blacksmith Iridium Upgrades"
@staticmethod
def blacksmith_upgrade(material: str) -> str:
return f"Blacksmith {material} Upgrades"
blacksmith_copper = blacksmith_upgrade("Copper")
blacksmith_iron = blacksmith_upgrade("Iron")
blacksmith_gold = blacksmith_upgrade("Gold")
blacksmith_iridium = blacksmith_upgrade("Iridium")
spring_farming = "Spring Farming"
summer_farming = "Summer Farming"

View File

@@ -5,8 +5,8 @@ from . import SVTestBase
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
@@ -320,7 +320,7 @@ class TestProgressiveElevator(SVTestBase):
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,
}

View File

@@ -9,7 +9,7 @@ class TestBitFlagsVanilla(SVTestBase):
def test_options_are_not_detected_as_progressive(self):
world_options = self.world.options
tool_progressive = world_options.tool_progression & ToolProgression.option_progressive
tool_progressive = self.world.content.features.tool_progression.is_progressive
building_progressive = world_options.building_progression & BuildingProgression.option_progressive
self.assertFalse(tool_progressive)
self.assertFalse(building_progressive)
@@ -26,7 +26,7 @@ class TestBitFlagsVanillaCheap(SVTestBase):
def test_options_are_not_detected_as_progressive(self):
world_options = self.world.options
tool_progressive = world_options.tool_progression & ToolProgression.option_progressive
tool_progressive = self.world.content.features.tool_progression.is_progressive
building_progressive = world_options.building_progression & BuildingProgression.option_progressive
self.assertFalse(tool_progressive)
self.assertFalse(building_progressive)
@@ -43,7 +43,7 @@ class TestBitFlagsVanillaVeryCheap(SVTestBase):
def test_options_are_not_detected_as_progressive(self):
world_options = self.world.options
tool_progressive = world_options.tool_progression & ToolProgression.option_progressive
tool_progressive = self.world.content.features.tool_progression.is_progressive
building_progressive = world_options.building_progression & BuildingProgression.option_progressive
self.assertFalse(tool_progressive)
self.assertFalse(building_progressive)
@@ -60,7 +60,7 @@ class TestBitFlagsProgressive(SVTestBase):
def test_options_are_detected_as_progressive(self):
world_options = self.world.options
tool_progressive = world_options.tool_progression & ToolProgression.option_progressive
tool_progressive = self.world.content.features.tool_progression.is_progressive
building_progressive = world_options.building_progression & BuildingProgression.option_progressive
self.assertTrue(tool_progressive)
self.assertTrue(building_progressive)
@@ -77,7 +77,7 @@ class TestBitFlagsProgressiveCheap(SVTestBase):
def test_options_are_detected_as_progressive(self):
world_options = self.world.options
tool_progressive = world_options.tool_progression & ToolProgression.option_progressive
tool_progressive = self.world.content.features.tool_progression.is_progressive
building_progressive = world_options.building_progression & BuildingProgression.option_progressive
self.assertTrue(tool_progressive)
self.assertTrue(building_progressive)
@@ -94,7 +94,7 @@ class TestBitFlagsProgressiveVeryCheap(SVTestBase):
def test_options_are_detected_as_progressive(self):
world_options = self.world.options
tool_progressive = world_options.tool_progression & ToolProgression.option_progressive
tool_progressive = self.world.content.features.tool_progression.is_progressive
building_progressive = world_options.building_progression & BuildingProgression.option_progressive
self.assertTrue(tool_progressive)
self.assertTrue(building_progressive)

View File

@@ -1,17 +1,17 @@
import itertools
from BaseClasses import ItemClassification
from Options import NamedRange
from . import SVTestCase, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x, solo_multiworld
from . import SVTestCase, allsanity_no_mods_6_x_x, allsanity_mods_6_x_x, solo_multiworld, SVTestBase
from .assertion import WorldAssertMixin
from .long.option_names import all_option_choices
from .. import items_by_group, Group, StardewValleyWorld
from ..locations import locations_by_tag, LocationTags, location_table
from ..options import ExcludeGingerIsland, ToolProgression, Goal, SeasonRandomization, TrapItems, SpecialOrderLocations, ArcadeMachineLocations, \
SkillProgression
from ..options import ExcludeGingerIsland, ToolProgression, Goal, SeasonRandomization, TrapItems, SpecialOrderLocations, ArcadeMachineLocations
from ..strings.goal_names import Goal as GoalName
from ..strings.season_names import Season
from ..strings.special_order_names import SpecialOrder
from ..strings.tool_names import ToolMaterial, Tool
from ..strings.tool_names import ToolMaterial, Tool, APTool
SEASONS = {Season.spring, Season.summer, Season.fall, Season.winter}
TOOLS = {"Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can", "Fishing Rod"}
@@ -77,43 +77,13 @@ class TestSeasonRandomization(SVTestCase):
self.assertEqual(items.count(Season.progressive), 3)
class TestToolProgression(SVTestCase):
def test_given_vanilla_when_generate_then_no_tool_in_pool(self):
world_options = {ToolProgression.internal_name: ToolProgression.option_vanilla}
with solo_multiworld(world_options) as (multi_world, _):
items = {item.name for item in multi_world.get_items()}
for tool in TOOLS:
self.assertNotIn(tool, items)
def test_given_progressive_when_generate_then_each_tool_is_in_pool_4_times(self):
world_options = {ToolProgression.internal_name: ToolProgression.option_progressive,
SkillProgression.internal_name: SkillProgression.option_progressive}
with solo_multiworld(world_options) as (multi_world, _):
items = [item.name for item in multi_world.get_items()]
for tool in TOOLS:
count = items.count("Progressive " + tool)
self.assertEqual(count, 4, f"Progressive {tool} was there {count} times")
scythe_count = items.count("Progressive Scythe")
self.assertEqual(scythe_count, 1, f"Progressive Scythe was there {scythe_count} times")
self.assertEqual(items.count("Golden Scythe"), 0, f"Golden Scythe is deprecated")
def test_given_progressive_with_masteries_when_generate_then_fishing_rod_is_in_the_pool_5_times(self):
world_options = {ToolProgression.internal_name: ToolProgression.option_progressive,
SkillProgression.internal_name: SkillProgression.option_progressive_with_masteries}
with solo_multiworld(world_options) as (multi_world, _):
items = [item.name for item in multi_world.get_items()]
for tool in TOOLS:
count = items.count("Progressive " + tool)
expected_count = 5 if tool == "Fishing Rod" else 4
self.assertEqual(count, expected_count, f"Progressive {tool} was there {count} times")
scythe_count = items.count("Progressive Scythe")
self.assertEqual(scythe_count, 2, f"Progressive Scythe was there {scythe_count} times")
self.assertEqual(items.count("Golden Scythe"), 0, f"Golden Scythe is deprecated")
class TestToolProgression(SVTestBase):
options = {
ToolProgression.internal_name: ToolProgression.option_progressive,
}
def test_given_progressive_when_generate_then_tool_upgrades_are_locations(self):
world_options = {ToolProgression.internal_name: ToolProgression.option_progressive}
with solo_multiworld(world_options) as (multi_world, _):
locations = {locations.name for locations in multi_world.get_locations(1)}
locations = set(self.get_real_location_names())
for material, tool in itertools.product(ToolMaterial.tiers.values(),
[Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can]):
if material == ToolMaterial.basic:
@@ -124,6 +94,14 @@ class TestToolProgression(SVTestCase):
self.assertIn("Purchase Fiberglass Rod", locations)
self.assertIn("Purchase Iridium Rod", locations)
def test_given_progressive_when_generate_then_only_3_trash_can_are_progressive(self):
trash_cans = self.get_items_by_name(APTool.trash_can)
progressive_count = sum([1 for item in trash_cans if item.classification == ItemClassification.progression])
useful_count = sum([1 for item in trash_cans if item.classification == ItemClassification.useful])
self.assertEqual(progressive_count, 3)
self.assertEqual(useful_count, 1)
class TestGenerateAllOptionsWithExcludeGingerIsland(WorldAssertMixin, SVTestCase):

View File

@@ -1,5 +1,5 @@
from . import SVTestBase
from ..options import ExcludeGingerIsland, Walnutsanity
from ..options import ExcludeGingerIsland, Walnutsanity, ToolProgression, SkillProgression
from ..strings.ap_names.ap_option_names import WalnutsanityOptionName
@@ -7,6 +7,8 @@ class TestWalnutsanityNone(SVTestBase):
options = {
ExcludeGingerIsland: ExcludeGingerIsland.option_false,
Walnutsanity: Walnutsanity.preset_none,
SkillProgression: ToolProgression.option_progressive,
ToolProgression: ToolProgression.option_progressive,
}
def test_no_walnut_locations(self):
@@ -50,6 +52,8 @@ class TestWalnutsanityPuzzles(SVTestBase):
options = {
ExcludeGingerIsland: ExcludeGingerIsland.option_false,
Walnutsanity: frozenset({WalnutsanityOptionName.puzzles}),
SkillProgression: ToolProgression.option_progressive,
ToolProgression: ToolProgression.option_progressive,
}
def test_only_puzzle_walnut_locations(self):

View File

@@ -9,6 +9,7 @@ default_features = StardewFeatures(
feature.fishsanity.FishsanityNone(),
feature.friendsanity.FriendsanityNone(),
feature.skill_progression.SkillProgressionVanilla(),
feature.tool_progression.ToolProgressionVanilla()
)

View File

@@ -0,0 +1,52 @@
import unittest
from ....content import choose_tool_progression
from ....options import ToolProgression, SkillProgression
from ....strings.tool_names import Tool
class TestToolDistribution(unittest.TestCase):
def test_given_vanilla_tool_progression_when_create_feature_then_only_one_scythe_is_randomized(self):
tool_progression = ToolProgression(ToolProgression.option_vanilla)
skill_progression = SkillProgression.from_text("random")
feature = choose_tool_progression(tool_progression, skill_progression)
self.assertEqual(feature.tool_distribution, {
Tool.scythe: 1,
})
def test_given_progressive_tool_when_create_feature_then_all_tool_upgrades_are_randomized(self):
tool_progression = ToolProgression(ToolProgression.option_progressive)
skill_progression = SkillProgression(SkillProgression.option_progressive)
feature = choose_tool_progression(tool_progression, skill_progression)
self.assertEqual(feature.tool_distribution, {
Tool.scythe: 1,
Tool.pickaxe: 4,
Tool.axe: 4,
Tool.hoe: 4,
Tool.watering_can: 4,
Tool.trash_can: 4,
Tool.pan: 4,
Tool.fishing_rod: 4,
})
def test_given_progressive_tool_and_skill_masteries_when_create_feature_then_additional_scythe_and_fishing_rod_are_randomized(self):
tool_progression = ToolProgression(ToolProgression.option_progressive)
skill_progression = SkillProgression(SkillProgression.option_progressive_with_masteries)
feature = choose_tool_progression(tool_progression, skill_progression)
self.assertEqual(feature.tool_distribution, {
Tool.scythe: 2,
Tool.pickaxe: 4,
Tool.axe: 4,
Tool.hoe: 4,
Tool.watering_can: 4,
Tool.trash_can: 4,
Tool.pan: 4,
Tool.fishing_rod: 5,
})