Stardew valley: Fixed furnace logic bug (#4163)

This commit is contained in:
agilbert1412 2024-11-11 23:27:43 -05:00 committed by GitHub
parent 74f922ea37
commit 17f03bb5f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 62 additions and 5 deletions

View File

@ -1,7 +1,7 @@
from typing import Dict, List, Optional
from .recipe_source import RecipeSource, StarterSource, QueenOfSauceSource, ShopSource, SkillSource, FriendshipSource, ShopTradeSource, CutsceneSource, \
ArchipelagoSource, LogicSource, SpecialOrderSource, FestivalShopSource, QuestSource, MasterySource
ArchipelagoSource, LogicSource, SpecialOrderSource, FestivalShopSource, QuestSource, MasterySource, SkillCraftsanitySource
from ..mods.mod_data import ModNames
from ..strings.animal_product_names import AnimalProduct
from ..strings.artisan_good_names import ArtisanGood
@ -64,6 +64,11 @@ def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int],
return create_recipe(name, ingredients, source, mod_name)
def skill_craftsanity_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe:
source = SkillCraftsanitySource(skill, level)
return create_recipe(name, ingredients, source, mod_name)
def mastery_recipe(name: str, skill: str, ingredients: Dict[str, int], mod_name: Optional[str] = None) -> CraftingRecipe:
source = MasterySource(skill)
return create_recipe(name, ingredients, source, mod_name)
@ -249,7 +254,9 @@ bait_maker = skill_recipe(Machine.bait_maker, Skill.fishing, 6, {MetalBar.iron:
charcoal_kiln = skill_recipe(Machine.charcoal_kiln, Skill.foraging, 2, {Material.wood: 20, MetalBar.copper: 2})
crystalarium = skill_recipe(Machine.crystalarium, Skill.mining, 9, {Material.stone: 99, MetalBar.gold: 5, MetalBar.iridium: 2, ArtisanGood.battery_pack: 1})
furnace = skill_recipe(Machine.furnace, Skill.mining, 1, {Ore.copper: 20, Material.stone: 25})
# In-Game, the Furnace recipe is completely unique. It is the only recipe that is obtained in a cutscene after doing a skill-related action.
# So it has a custom source that needs both the craftsanity item from AP and the skill, if craftsanity is enabled.
furnace = skill_craftsanity_recipe(Machine.furnace, Skill.mining, 1, {Ore.copper: 20, Material.stone: 25})
geode_crusher = special_order_recipe(Machine.geode_crusher, SpecialOrder.cave_patrol, {MetalBar.gold: 2, Material.stone: 50, Mineral.diamond: 1})
mushroom_log = skill_recipe(Machine.mushroom_log, Skill.foraging, 4, {Material.hardwood: 10, Material.moss: 10})
heavy_tapper = ap_recipe(Machine.heavy_tapper, {Material.hardwood: 30, MetalBar.radioactive: 1})

View File

@ -94,6 +94,11 @@ class SkillSource(RecipeSource):
return f"SkillSource at level {self.level} {self.skill}"
class SkillCraftsanitySource(SkillSource):
def __repr__(self):
return f"SkillCraftsanitySource at level {self.level} {self.skill}"
class MasterySource(RecipeSource):
skill: str

View File

@ -14,7 +14,7 @@ from .special_order_logic import SpecialOrderLogicMixin
from .. import options
from ..data.craftable_data import CraftingRecipe, all_crafting_recipes_by_name
from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \
FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource
FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource
from ..locations import locations_by_tag, LocationTags
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland, SkillProgression
from ..stardew_rule import StardewRule, True_, False_
@ -54,8 +54,7 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
return self.logic.crafting.received_recipe(recipe.item)
if self.options.craftsanity == Craftsanity.option_none:
return self.logic.crafting.can_learn_recipe(recipe)
if isinstance(recipe.source, StarterSource) or isinstance(recipe.source, ShopTradeSource) or isinstance(
recipe.source, ShopSource):
if isinstance(recipe.source, (StarterSource, ShopTradeSource, ShopSource, SkillCraftsanitySource)):
return self.logic.crafting.received_recipe(recipe.item)
if isinstance(recipe.source, SpecialOrderSource) and self.options.special_order_locations & SpecialOrderLocations.option_board:
return self.logic.crafting.received_recipe(recipe.item)
@ -71,6 +70,8 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
return self.logic.money.can_trade_at(recipe.source.region, recipe.source.currency, recipe.source.price)
if isinstance(recipe.source, ShopSource):
return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price)
if isinstance(recipe.source, SkillCraftsanitySource):
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level) & self.logic.skill.can_earn_level(recipe.source.skill, recipe.source.level)
if isinstance(recipe.source, SkillSource):
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level)
if isinstance(recipe.source, MasterySource):

View File

@ -1,3 +1,4 @@
from typing import List
from unittest import TestCase
from BaseClasses import CollectionState, Location
@ -14,6 +15,10 @@ class RuleAssertMixin(TestCase):
raise AssertionError(f"Error while checking rule {rule}: {e}"
f"\nExplanation: {expl}")
def assert_rules_true(self, rules: List[StardewRule], state: CollectionState):
for rule in rules:
self.assert_rule_true(rule, state)
def assert_rule_false(self, rule: StardewRule, state: CollectionState):
expl = explain(rule, state, expected=False)
try:
@ -22,6 +27,10 @@ class RuleAssertMixin(TestCase):
raise AssertionError(f"Error while checking rule {rule}: {e}"
f"\nExplanation: {expl}")
def assert_rules_false(self, rules: List[StardewRule], state: CollectionState):
for rule in rules:
self.assert_rule_false(rule, state)
def assert_rule_can_be_resolved(self, rule: StardewRule, complete_state: CollectionState):
expl = explain(rule, complete_state)
try:

View File

@ -56,6 +56,7 @@ class TestRaccoonBundlesLogic(SVTestBase):
self.collect("Mushroom Boxes")
self.collect("Progressive Fishing Rod", 4)
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))

View File

@ -50,6 +50,23 @@ class TestCraftsanityLogic(SVTestBase):
self.multiworld.state.collect(self.create_item("Jack-O-Lantern Recipe"), prevent_sweep=False)
self.assert_rule_true(rule, self.multiworld.state)
def test_require_furnace_recipe_for_smelting_checks(self):
locations = ["Craft Furnace", "Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"]
rules = [self.world.logic.region.can_reach_location(location) for location in locations]
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)
self.collect([self.create_item("Progressive Mine Elevator")] * 24)
self.collect([self.create_item("Progressive Trash Can")] * 2)
self.collect([self.create_item("Mining Level")] * 10)
self.collect([self.create_item("Combat Level")] * 10)
self.collect([self.create_item("Fishing Level")] * 10)
self.collect_all_the_money()
self.assert_rules_false(rules, self.multiworld.state)
self.multiworld.state.collect(self.create_item("Furnace Recipe"), prevent_sweep=False)
self.assert_rules_true(rules, self.multiworld.state)
class TestCraftsanityWithFestivalsLogic(SVTestBase):
options = {
@ -101,6 +118,23 @@ class TestNoCraftsanityLogic(SVTestBase):
self.collect([self.create_item("Progressive Season")] * 2)
self.assert_rule_true(rule, self.multiworld.state)
def test_requires_mining_levels_for_smelting_checks(self):
locations = ["Smelting", "Copper Pickaxe Upgrade", "Gold Trash Can Upgrade"]
rules = [self.world.logic.region.can_reach_location(location) for location in locations]
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)
self.collect([self.create_item("Progressive Mine Elevator")] * 24)
self.collect([self.create_item("Progressive Trash Can")] * 2)
self.multiworld.state.collect(self.create_item("Furnace Recipe"), prevent_sweep=False)
self.collect([self.create_item("Combat Level")] * 10)
self.collect([self.create_item("Fishing Level")] * 10)
self.collect_all_the_money()
self.assert_rules_false(rules, self.multiworld.state)
self.collect([self.create_item("Mining Level")] * 10)
self.assert_rules_true(rules, self.multiworld.state)
class TestNoCraftsanityWithFestivalsLogic(SVTestBase):
options = {