Stardew valley: Fixed furnace logic bug (#4163)
This commit is contained in:
parent
74f922ea37
commit
17f03bb5f8
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue