Stardew Valley: 5.x.x - The Allsanity Update (#2764)
Major Content update for Stardew Valley, including the following features - Major performance improvements all across the Stardew Valley apworld, including a significant reduction in the test time - Randomized Farm Type - Bundles rework (Remixed Bundles and Missing Bundle!) - New Settings: * Shipsanity - Shipping individual items * Monstersanity - Slaying monsters * Cooksanity - Cooking individual recipes * Chefsanity - Learning individual recipes * Craftsanity - Crafting individual items - New Goals: * Protector of the Valley - Complete every monster slayer goal * Full Shipment - Ship every item * Craftmaster - Craft every item * Gourmet Chef - Cook every recipe * Legend - Earn 10 000 000g * Mystery of the Stardrops - Find every stardrop (Maguffin Hunt) * Allsanity - Complete every check in your slot - Building Shuffle: Cheaper options - Tool Shuffle: Cheaper options - Money rework - New traps - New isolated checks and items, including the farm cave, the movie theater, etc - Mod Support: SVE [Albrekka] - Mod Support: Distant Lands [Albrekka] - Mod Support: Hat Mouse Lacey [Albrekka] - Mod Support: Boarding House [Albrekka] Co-authored-by: Witchybun <elnendil@gmail.com> Co-authored-by: Witchybun <96719127+Witchybun@users.noreply.github.com> Co-authored-by: Jouramie <jouramie@hotmail.com> Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
This commit is contained in:
0
worlds/stardew_valley/logic/__init__.py
Normal file
0
worlds/stardew_valley/logic/__init__.py
Normal file
46
worlds/stardew_valley/logic/ability_logic.py
Normal file
46
worlds/stardew_valley/logic/ability_logic.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .mine_logic import MineLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from ..mods.logic.magic_logic import MagicLogicMixin
|
||||
from ..stardew_rule import StardewRule
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.skill_names import Skill, ModSkill
|
||||
from ..strings.tool_names import ToolMaterial, Tool
|
||||
|
||||
|
||||
class AbilityLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.ability = AbilityLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class AbilityLogic(BaseLogic[Union[AbilityLogicMixin, RegionLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, MineLogicMixin, MagicLogicMixin]]):
|
||||
def can_mine_perfectly(self) -> StardewRule:
|
||||
return self.logic.mine.can_progress_in_the_mines_from_floor(160)
|
||||
|
||||
def can_mine_perfectly_in_the_skull_cavern(self) -> StardewRule:
|
||||
return (self.logic.ability.can_mine_perfectly() &
|
||||
self.logic.region.can_reach(Region.skull_cavern))
|
||||
|
||||
def can_farm_perfectly(self) -> StardewRule:
|
||||
tool_rule = self.logic.tool.has_tool(Tool.hoe, ToolMaterial.iridium) & self.logic.tool.can_water(4)
|
||||
return tool_rule & self.logic.skill.has_farming_level(10)
|
||||
|
||||
def can_fish_perfectly(self) -> StardewRule:
|
||||
skill_rule = self.logic.skill.has_level(Skill.fishing, 10)
|
||||
return skill_rule & self.logic.tool.has_fishing_rod(4)
|
||||
|
||||
def can_chop_trees(self) -> StardewRule:
|
||||
return self.logic.tool.has_tool(Tool.axe) & self.logic.region.can_reach(Region.forest)
|
||||
|
||||
def can_chop_perfectly(self) -> StardewRule:
|
||||
magic_rule = (self.logic.magic.can_use_clear_debris_instead_of_tool_level(3)) & self.logic.mod.skill.has_mod_level(ModSkill.magic, 10)
|
||||
tool_rule = self.logic.tool.has_tool(Tool.axe, ToolMaterial.iridium)
|
||||
foraging_rule = self.logic.skill.has_level(Skill.foraging, 10)
|
||||
region_rule = self.logic.region.can_reach(Region.forest)
|
||||
return region_rule & ((tool_rule & foraging_rule) | magic_rule)
|
||||
40
worlds/stardew_valley/logic/action_logic.py
Normal file
40
worlds/stardew_valley/logic/action_logic.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from ..stardew_rule import StardewRule, True_, Or
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.geode_names import Geode
|
||||
from ..strings.region_names import Region
|
||||
|
||||
|
||||
class ActionLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.action = ActionLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class ActionLogic(BaseLogic[Union[ActionLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
|
||||
|
||||
def can_watch(self, channel: str = None):
|
||||
tv_rule = True_()
|
||||
if channel is None:
|
||||
return tv_rule
|
||||
return self.logic.received(channel) & tv_rule
|
||||
|
||||
def can_pan(self) -> StardewRule:
|
||||
return self.logic.received("Glittering Boulder Removed") & self.logic.region.can_reach(Region.mountain)
|
||||
|
||||
def can_pan_at(self, region: str) -> StardewRule:
|
||||
return self.logic.region.can_reach(region) & self.logic.action.can_pan()
|
||||
|
||||
@cache_self1
|
||||
def can_open_geode(self, geode: str) -> StardewRule:
|
||||
blacksmith_access = self.logic.region.can_reach(Region.blacksmith)
|
||||
geodes = [Geode.geode, Geode.frozen, Geode.magma, Geode.omni]
|
||||
if geode == Generic.any:
|
||||
return blacksmith_access & Or(*(self.logic.has(geode_type) for geode_type in geodes))
|
||||
return blacksmith_access & self.logic.has(geode)
|
||||
59
worlds/stardew_valley/logic/animal_logic.py
Normal file
59
worlds/stardew_valley/logic/animal_logic.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .building_logic import BuildingLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from ..stardew_rule import StardewRule, true_
|
||||
from ..strings.animal_names import Animal, coop_animals, barn_animals
|
||||
from ..strings.building_names import Building
|
||||
from ..strings.forageable_names import Forageable
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.region_names import Region
|
||||
|
||||
cost_and_building_by_animal = {
|
||||
Animal.chicken: (800, Building.coop),
|
||||
Animal.cow: (1500, Building.barn),
|
||||
Animal.goat: (4000, Building.big_barn),
|
||||
Animal.duck: (1200, Building.big_coop),
|
||||
Animal.sheep: (8000, Building.deluxe_barn),
|
||||
Animal.rabbit: (8000, Building.deluxe_coop),
|
||||
Animal.pig: (16000, Building.deluxe_barn)
|
||||
}
|
||||
|
||||
|
||||
class AnimalLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.animal = AnimalLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class AnimalLogic(BaseLogic[Union[HasLogicMixin, MoneyLogicMixin, BuildingLogicMixin]]):
|
||||
|
||||
def can_buy_animal(self, animal: str) -> StardewRule:
|
||||
try:
|
||||
price, building = cost_and_building_by_animal[animal]
|
||||
except KeyError:
|
||||
return true_
|
||||
return self.logic.money.can_spend_at(Region.ranch, price) & self.logic.building.has_building(building)
|
||||
|
||||
def has_animal(self, animal: str) -> StardewRule:
|
||||
if animal == Generic.any:
|
||||
return self.has_any_animal()
|
||||
elif animal == Building.coop:
|
||||
return self.has_any_coop_animal()
|
||||
elif animal == Building.barn:
|
||||
return self.has_any_barn_animal()
|
||||
return self.logic.has(animal)
|
||||
|
||||
def has_happy_animal(self, animal: str) -> StardewRule:
|
||||
return self.has_animal(animal) & self.logic.has(Forageable.hay)
|
||||
|
||||
def has_any_animal(self) -> StardewRule:
|
||||
return self.has_any_coop_animal() | self.has_any_barn_animal()
|
||||
|
||||
def has_any_coop_animal(self) -> StardewRule:
|
||||
return self.logic.has_any(*coop_animals)
|
||||
|
||||
def has_any_barn_animal(self) -> StardewRule:
|
||||
return self.logic.has_any(*barn_animals)
|
||||
34
worlds/stardew_valley/logic/arcade_logic.py
Normal file
34
worlds/stardew_valley/logic/arcade_logic.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .. import options
|
||||
from ..stardew_rule import StardewRule, True_
|
||||
from ..strings.region_names import Region
|
||||
|
||||
|
||||
class ArcadeLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.arcade = ArcadeLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class ArcadeLogic(BaseLogic[Union[ArcadeLogicMixin, RegionLogicMixin, ReceivedLogicMixin]]):
|
||||
|
||||
def has_jotpk_power_level(self, power_level: int) -> StardewRule:
|
||||
if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
return True_()
|
||||
jotpk_buffs = ("JotPK: Progressive Boots", "JotPK: Progressive Gun", "JotPK: Progressive Ammo", "JotPK: Extra Life", "JotPK: Increased Drop Rate")
|
||||
return self.logic.received_n(*jotpk_buffs, count=power_level)
|
||||
|
||||
def has_junimo_kart_power_level(self, power_level: int) -> StardewRule:
|
||||
if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
return True_()
|
||||
return self.logic.received("Junimo Kart: Extra Life", power_level)
|
||||
|
||||
def has_junimo_kart_max_level(self) -> StardewRule:
|
||||
play_rule = self.logic.region.can_reach(Region.junimo_kart_3)
|
||||
if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling:
|
||||
return play_rule
|
||||
return self.logic.arcade.has_junimo_kart_power_level(8)
|
||||
53
worlds/stardew_valley/logic/artisan_logic.py
Normal file
53
worlds/stardew_valley/logic/artisan_logic.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from ..stardew_rule import StardewRule
|
||||
from ..strings.crop_names import all_vegetables, all_fruits, Vegetable, Fruit
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.machine_names import Machine
|
||||
|
||||
|
||||
class ArtisanLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.artisan = ArtisanLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class ArtisanLogic(BaseLogic[Union[ArtisanLogicMixin, TimeLogicMixin, HasLogicMixin]]):
|
||||
|
||||
def has_jelly(self) -> StardewRule:
|
||||
return self.logic.artisan.can_preserves_jar(Fruit.any)
|
||||
|
||||
def has_pickle(self) -> StardewRule:
|
||||
return self.logic.artisan.can_preserves_jar(Vegetable.any)
|
||||
|
||||
def can_preserves_jar(self, item: str) -> StardewRule:
|
||||
machine_rule = self.logic.has(Machine.preserves_jar)
|
||||
if item == Generic.any:
|
||||
return machine_rule
|
||||
if item == Fruit.any:
|
||||
return machine_rule & self.logic.has_any(*all_fruits)
|
||||
if item == Vegetable.any:
|
||||
return machine_rule & self.logic.has_any(*all_vegetables)
|
||||
return machine_rule & self.logic.has(item)
|
||||
|
||||
def has_wine(self) -> StardewRule:
|
||||
return self.logic.artisan.can_keg(Fruit.any)
|
||||
|
||||
def has_juice(self) -> StardewRule:
|
||||
return self.logic.artisan.can_keg(Vegetable.any)
|
||||
|
||||
def can_keg(self, item: str) -> StardewRule:
|
||||
machine_rule = self.logic.has(Machine.keg)
|
||||
if item == Generic.any:
|
||||
return machine_rule
|
||||
if item == Fruit.any:
|
||||
return machine_rule & self.logic.has_any(*all_fruits)
|
||||
if item == Vegetable.any:
|
||||
return machine_rule & self.logic.has_any(*all_vegetables)
|
||||
return machine_rule & self.logic.has(item)
|
||||
|
||||
def can_mayonnaise(self, item: str) -> StardewRule:
|
||||
return self.logic.has(Machine.mayonnaise_machine) & self.logic.has(item)
|
||||
50
worlds/stardew_valley/logic/base_logic.py
Normal file
50
worlds/stardew_valley/logic/base_logic.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypeVar, Generic, Dict, Collection
|
||||
|
||||
from ..options import StardewValleyOptions
|
||||
from ..stardew_rule import StardewRule
|
||||
|
||||
|
||||
class LogicRegistry:
|
||||
|
||||
def __init__(self):
|
||||
self.item_rules: Dict[str, StardewRule] = {}
|
||||
self.sapling_rules: Dict[str, StardewRule] = {}
|
||||
self.tree_fruit_rules: Dict[str, StardewRule] = {}
|
||||
self.seed_rules: Dict[str, StardewRule] = {}
|
||||
self.cooking_rules: Dict[str, StardewRule] = {}
|
||||
self.crafting_rules: Dict[str, StardewRule] = {}
|
||||
self.crop_rules: Dict[str, StardewRule] = {}
|
||||
self.fish_rules: Dict[str, StardewRule] = {}
|
||||
self.museum_rules: Dict[str, StardewRule] = {}
|
||||
self.festival_rules: Dict[str, StardewRule] = {}
|
||||
self.quest_rules: Dict[str, StardewRule] = {}
|
||||
self.building_rules: Dict[str, StardewRule] = {}
|
||||
self.special_order_rules: Dict[str, StardewRule] = {}
|
||||
|
||||
self.sve_location_rules: Dict[str, StardewRule] = {}
|
||||
|
||||
|
||||
class BaseLogicMixin:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
T = TypeVar("T", bound=BaseLogicMixin)
|
||||
|
||||
|
||||
class BaseLogic(BaseLogicMixin, Generic[T]):
|
||||
player: int
|
||||
registry: LogicRegistry
|
||||
options: StardewValleyOptions
|
||||
regions: Collection[str]
|
||||
logic: T
|
||||
|
||||
def __init__(self, player: int, registry: LogicRegistry, options: StardewValleyOptions, regions: Collection[str], logic: T):
|
||||
super().__init__(player, registry, options, regions, logic)
|
||||
self.player = player
|
||||
self.registry = registry
|
||||
self.options = options
|
||||
self.regions = regions
|
||||
self.logic = logic
|
||||
23
worlds/stardew_valley/logic/buff_logic.py
Normal file
23
worlds/stardew_valley/logic/buff_logic.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from ..stardew_rule import StardewRule
|
||||
from ..strings.ap_names.buff_names import Buff
|
||||
|
||||
|
||||
class BuffLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.buff = BuffLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class BuffLogic(BaseLogic[Union[ReceivedLogicMixin]]):
|
||||
def has_max_buffs(self) -> StardewRule:
|
||||
return self.has_max_speed() & self.has_max_luck()
|
||||
|
||||
def has_max_speed(self) -> StardewRule:
|
||||
return self.logic.received(Buff.movement, self.options.movement_buff_number.value)
|
||||
|
||||
def has_max_luck(self) -> StardewRule:
|
||||
return self.logic.received(Buff.luck, self.options.luck_buff_number.value)
|
||||
95
worlds/stardew_valley/logic/building_logic.py
Normal file
95
worlds/stardew_valley/logic/building_logic.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from typing import Dict, Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from ..options import BuildingProgression
|
||||
from ..stardew_rule import StardewRule, True_, False_, Has
|
||||
from ..strings.ap_names.event_names import Event
|
||||
from ..strings.artisan_good_names import ArtisanGood
|
||||
from ..strings.building_names import Building
|
||||
from ..strings.fish_names import WaterItem
|
||||
from ..strings.material_names import Material
|
||||
from ..strings.metal_names import MetalBar
|
||||
|
||||
|
||||
class BuildingLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.building = BuildingLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, MoneyLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
|
||||
def initialize_rules(self):
|
||||
self.registry.building_rules.update({
|
||||
# @formatter:off
|
||||
Building.barn: self.logic.money.can_spend(6000) & self.logic.has_all(Material.wood, Material.stone),
|
||||
Building.big_barn: self.logic.money.can_spend(12000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.barn),
|
||||
Building.deluxe_barn: self.logic.money.can_spend(25000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.big_barn),
|
||||
Building.coop: self.logic.money.can_spend(4000) & self.logic.has_all(Material.wood, Material.stone),
|
||||
Building.big_coop: self.logic.money.can_spend(10000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.coop),
|
||||
Building.deluxe_coop: self.logic.money.can_spend(20000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.big_coop),
|
||||
Building.fish_pond: self.logic.money.can_spend(5000) & self.logic.has_all(Material.stone, WaterItem.seaweed, WaterItem.green_algae),
|
||||
Building.mill: self.logic.money.can_spend(2500) & self.logic.has_all(Material.stone, Material.wood, ArtisanGood.cloth),
|
||||
Building.shed: self.logic.money.can_spend(15000) & self.logic.has(Material.wood),
|
||||
Building.big_shed: self.logic.money.can_spend(20000) & self.logic.has_all(Material.wood, Material.stone) & self.logic.building.has_building(Building.shed),
|
||||
Building.silo: self.logic.money.can_spend(100) & self.logic.has_all(Material.stone, Material.clay, MetalBar.copper),
|
||||
Building.slime_hutch: self.logic.money.can_spend(10000) & self.logic.has_all(Material.stone, MetalBar.quartz, MetalBar.iridium),
|
||||
Building.stable: self.logic.money.can_spend(10000) & self.logic.has_all(Material.hardwood, MetalBar.iron),
|
||||
Building.well: self.logic.money.can_spend(1000) & self.logic.has(Material.stone),
|
||||
Building.shipping_bin: self.logic.money.can_spend(250) & self.logic.has(Material.wood),
|
||||
Building.kitchen: self.logic.money.can_spend(10000) & self.logic.has(Material.wood) & self.logic.building.has_house(0),
|
||||
Building.kids_room: self.logic.money.can_spend(50000) & self.logic.has(Material.hardwood) & self.logic.building.has_house(1),
|
||||
Building.cellar: self.logic.money.can_spend(100000) & self.logic.building.has_house(2),
|
||||
# @formatter:on
|
||||
})
|
||||
|
||||
def update_rules(self, new_rules: Dict[str, StardewRule]):
|
||||
self.registry.building_rules.update(new_rules)
|
||||
|
||||
@cache_self1
|
||||
def has_building(self, building: str) -> StardewRule:
|
||||
# Shipping bin is special. The mod auto-builds it when received, no need to go to Robin.
|
||||
if building is Building.shipping_bin:
|
||||
if not self.options.building_progression & BuildingProgression.option_progressive:
|
||||
return True_()
|
||||
return self.logic.received(building)
|
||||
|
||||
carpenter_rule = self.logic.received(Event.can_construct_buildings)
|
||||
if not self.options.building_progression & BuildingProgression.option_progressive:
|
||||
return Has(building, self.registry.building_rules) & carpenter_rule
|
||||
|
||||
count = 1
|
||||
if building in [Building.coop, Building.barn, Building.shed]:
|
||||
building = f"Progressive {building}"
|
||||
elif building.startswith("Big"):
|
||||
count = 2
|
||||
building = " ".join(["Progressive", *building.split(" ")[1:]])
|
||||
elif building.startswith("Deluxe"):
|
||||
count = 3
|
||||
building = " ".join(["Progressive", *building.split(" ")[1:]])
|
||||
return self.logic.received(building, count) & carpenter_rule
|
||||
|
||||
@cache_self1
|
||||
def has_house(self, upgrade_level: int) -> StardewRule:
|
||||
if upgrade_level < 1:
|
||||
return True_()
|
||||
|
||||
if upgrade_level > 3:
|
||||
return False_()
|
||||
|
||||
carpenter_rule = self.logic.received(Event.can_construct_buildings)
|
||||
if self.options.building_progression & BuildingProgression.option_progressive:
|
||||
return carpenter_rule & self.logic.received(f"Progressive House", upgrade_level)
|
||||
|
||||
if upgrade_level == 1:
|
||||
return carpenter_rule & Has(Building.kitchen, self.registry.building_rules)
|
||||
|
||||
if upgrade_level == 2:
|
||||
return carpenter_rule & Has(Building.kids_room, self.registry.building_rules)
|
||||
|
||||
# if upgrade_level == 3:
|
||||
return carpenter_rule & Has(Building.cellar, self.registry.building_rules)
|
||||
66
worlds/stardew_valley/logic/bundle_logic.py
Normal file
66
worlds/stardew_valley/logic/bundle_logic.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from functools import cached_property
|
||||
from typing import Union, List
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .farming_logic import FarmingLogicMixin
|
||||
from .fishing_logic import FishingLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from ..bundles.bundle import Bundle
|
||||
from ..stardew_rule import StardewRule, And, True_
|
||||
from ..strings.currency_names import Currency
|
||||
from ..strings.machine_names import Machine
|
||||
from ..strings.quality_names import CropQuality, ForageQuality, FishQuality, ArtisanQuality
|
||||
from ..strings.region_names import Region
|
||||
|
||||
|
||||
class BundleLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.bundle = BundleLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class BundleLogic(BaseLogic[Union[HasLogicMixin, RegionLogicMixin, MoneyLogicMixin, FarmingLogicMixin, FishingLogicMixin, SkillLogicMixin]]):
|
||||
# Should be cached
|
||||
def can_complete_bundle(self, bundle: Bundle) -> StardewRule:
|
||||
item_rules = []
|
||||
qualities = []
|
||||
can_speak_junimo = self.logic.region.can_reach(Region.wizard_tower)
|
||||
for bundle_item in bundle.items:
|
||||
if Currency.is_currency(bundle_item.item_name):
|
||||
return can_speak_junimo & self.logic.money.can_trade(bundle_item.item_name, bundle_item.amount)
|
||||
|
||||
item_rules.append(bundle_item.item_name)
|
||||
qualities.append(bundle_item.quality)
|
||||
quality_rules = self.get_quality_rules(qualities)
|
||||
item_rules = self.logic.has_n(*item_rules, count=bundle.number_required)
|
||||
return can_speak_junimo & item_rules & quality_rules
|
||||
|
||||
def get_quality_rules(self, qualities: List[str]) -> StardewRule:
|
||||
crop_quality = CropQuality.get_highest(qualities)
|
||||
fish_quality = FishQuality.get_highest(qualities)
|
||||
forage_quality = ForageQuality.get_highest(qualities)
|
||||
artisan_quality = ArtisanQuality.get_highest(qualities)
|
||||
quality_rules = []
|
||||
if crop_quality != CropQuality.basic:
|
||||
quality_rules.append(self.logic.farming.can_grow_crop_quality(crop_quality))
|
||||
if fish_quality != FishQuality.basic:
|
||||
quality_rules.append(self.logic.fishing.can_catch_quality_fish(fish_quality))
|
||||
if forage_quality != ForageQuality.basic:
|
||||
quality_rules.append(self.logic.skill.can_forage_quality(forage_quality))
|
||||
if artisan_quality != ArtisanQuality.basic:
|
||||
quality_rules.append(self.logic.has(Machine.cask))
|
||||
if not quality_rules:
|
||||
return True_()
|
||||
return And(*quality_rules)
|
||||
|
||||
@cached_property
|
||||
def can_complete_community_center(self) -> StardewRule:
|
||||
return (self.logic.region.can_reach_location("Complete Crafts Room") &
|
||||
self.logic.region.can_reach_location("Complete Pantry") &
|
||||
self.logic.region.can_reach_location("Complete Fish Tank") &
|
||||
self.logic.region.can_reach_location("Complete Bulletin Board") &
|
||||
self.logic.region.can_reach_location("Complete Vault") &
|
||||
self.logic.region.can_reach_location("Complete Boiler Room"))
|
||||
57
worlds/stardew_valley/logic/combat_logic.py
Normal file
57
worlds/stardew_valley/logic/combat_logic.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from functools import cached_property
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from ..mods.logic.magic_logic import MagicLogicMixin
|
||||
from ..stardew_rule import StardewRule, Or, False_
|
||||
from ..strings.ap_names.ap_weapon_names import APWeapon
|
||||
from ..strings.performance_names import Performance
|
||||
|
||||
valid_weapons = (APWeapon.weapon, APWeapon.sword, APWeapon.club, APWeapon.dagger)
|
||||
|
||||
|
||||
class CombatLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.combat = CombatLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class CombatLogic(BaseLogic[Union[CombatLogicMixin, RegionLogicMixin, ReceivedLogicMixin, MagicLogicMixin]]):
|
||||
@cache_self1
|
||||
def can_fight_at_level(self, level: str) -> StardewRule:
|
||||
if level == Performance.basic:
|
||||
return self.logic.combat.has_any_weapon | self.logic.magic.has_any_spell()
|
||||
if level == Performance.decent:
|
||||
return self.logic.combat.has_decent_weapon | self.logic.magic.has_decent_spells()
|
||||
if level == Performance.good:
|
||||
return self.logic.combat.has_good_weapon | self.logic.magic.has_good_spells()
|
||||
if level == Performance.great:
|
||||
return self.logic.combat.has_great_weapon | self.logic.magic.has_great_spells()
|
||||
if level == Performance.galaxy:
|
||||
return self.logic.combat.has_galaxy_weapon | self.logic.magic.has_amazing_spells()
|
||||
if level == Performance.maximum:
|
||||
return self.logic.combat.has_galaxy_weapon | self.logic.magic.has_amazing_spells() # Someday we will have the ascended weapons in AP
|
||||
return False_()
|
||||
|
||||
@cached_property
|
||||
def has_any_weapon(self) -> StardewRule:
|
||||
return self.logic.received_any(*valid_weapons)
|
||||
|
||||
@cached_property
|
||||
def has_decent_weapon(self) -> StardewRule:
|
||||
return Or(*(self.logic.received(weapon, 2) for weapon in valid_weapons))
|
||||
|
||||
@cached_property
|
||||
def has_good_weapon(self) -> StardewRule:
|
||||
return Or(*(self.logic.received(weapon, 3) for weapon in valid_weapons))
|
||||
|
||||
@cached_property
|
||||
def has_great_weapon(self) -> StardewRule:
|
||||
return Or(*(self.logic.received(weapon, 4) for weapon in valid_weapons))
|
||||
|
||||
@cached_property
|
||||
def has_galaxy_weapon(self) -> StardewRule:
|
||||
return Or(*(self.logic.received(weapon, 5) for weapon in valid_weapons))
|
||||
108
worlds/stardew_valley/logic/cooking_logic.py
Normal file
108
worlds/stardew_valley/logic/cooking_logic.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from functools import cached_property
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .action_logic import ActionLogicMixin
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .building_logic import BuildingLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .relationship_logic import RelationshipLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \
|
||||
QueenOfSauceSource, CookingRecipe, ShopFriendshipSource, \
|
||||
all_cooking_recipes_by_name
|
||||
from ..data.recipe_source import CutsceneSource, ShopTradeSource
|
||||
from ..locations import locations_by_tag, LocationTags
|
||||
from ..options import Chefsanity
|
||||
from ..options import ExcludeGingerIsland
|
||||
from ..stardew_rule import StardewRule, True_, False_, And
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.skill_names import Skill
|
||||
from ..strings.tv_channel_names import Channel
|
||||
|
||||
|
||||
class CookingLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.cooking = CookingLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class CookingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin,
|
||||
BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]):
|
||||
@cached_property
|
||||
def can_cook_in_kitchen(self) -> StardewRule:
|
||||
return self.logic.building.has_house(1) | self.logic.skill.has_level(Skill.foraging, 9)
|
||||
|
||||
# Should be cached
|
||||
def can_cook(self, recipe: CookingRecipe = None) -> StardewRule:
|
||||
cook_rule = self.logic.region.can_reach(Region.kitchen)
|
||||
if recipe is None:
|
||||
return cook_rule
|
||||
|
||||
recipe_rule = self.logic.cooking.knows_recipe(recipe.source, recipe.meal)
|
||||
ingredients_rule = self.logic.has_all(*sorted(recipe.ingredients))
|
||||
return cook_rule & recipe_rule & ingredients_rule
|
||||
|
||||
# Should be cached
|
||||
def knows_recipe(self, source: RecipeSource, meal_name: str) -> StardewRule:
|
||||
if self.options.chefsanity == Chefsanity.option_none:
|
||||
return self.logic.cooking.can_learn_recipe(source)
|
||||
if isinstance(source, StarterSource):
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, ShopTradeSource) and self.options.chefsanity & Chefsanity.option_purchases:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, ShopSource) and self.options.chefsanity & Chefsanity.option_purchases:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, SkillSource) and self.options.chefsanity & Chefsanity.option_skills:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, CutsceneSource) and self.options.chefsanity & Chefsanity.option_friendship:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, FriendshipSource) and self.options.chefsanity & Chefsanity.option_friendship:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, QueenOfSauceSource) and self.options.chefsanity & Chefsanity.option_queen_of_sauce:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
if isinstance(source, ShopFriendshipSource) and self.options.chefsanity & Chefsanity.option_friendship:
|
||||
return self.logic.cooking.received_recipe(meal_name)
|
||||
return self.logic.cooking.can_learn_recipe(source)
|
||||
|
||||
@cache_self1
|
||||
def can_learn_recipe(self, source: RecipeSource) -> StardewRule:
|
||||
if isinstance(source, StarterSource):
|
||||
return True_()
|
||||
if isinstance(source, ShopTradeSource):
|
||||
return self.logic.money.can_trade_at(source.region, source.currency, source.price)
|
||||
if isinstance(source, ShopSource):
|
||||
return self.logic.money.can_spend_at(source.region, source.price)
|
||||
if isinstance(source, SkillSource):
|
||||
return self.logic.skill.has_level(source.skill, source.level)
|
||||
if isinstance(source, CutsceneSource):
|
||||
return self.logic.region.can_reach(source.region) & self.logic.relationship.has_hearts(source.friend, source.hearts)
|
||||
if isinstance(source, FriendshipSource):
|
||||
return self.logic.relationship.has_hearts(source.friend, source.hearts)
|
||||
if isinstance(source, QueenOfSauceSource):
|
||||
return self.logic.action.can_watch(Channel.queen_of_sauce) & self.logic.season.has(source.season)
|
||||
if isinstance(source, ShopFriendshipSource):
|
||||
return self.logic.money.can_spend_at(source.region, source.price) & self.logic.relationship.has_hearts(source.friend, source.hearts)
|
||||
return False_()
|
||||
|
||||
@cache_self1
|
||||
def received_recipe(self, meal_name: str):
|
||||
return self.logic.received(f"{meal_name} Recipe")
|
||||
|
||||
@cached_property
|
||||
def can_cook_everything(self) -> StardewRule:
|
||||
cooksanity_prefix = "Cook "
|
||||
all_recipes_names = []
|
||||
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
for location in locations_by_tag[LocationTags.COOKSANITY]:
|
||||
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
||||
continue
|
||||
if location.mod_name and location.mod_name not in self.options.mods:
|
||||
continue
|
||||
all_recipes_names.append(location.name[len(cooksanity_prefix):])
|
||||
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
|
||||
return And(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))
|
||||
111
worlds/stardew_valley/logic/crafting_logic.py
Normal file
111
worlds/stardew_valley/logic/crafting_logic.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from functools import cached_property
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .quest_logic import QuestLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .relationship_logic import RelationshipLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from .special_order_logic import SpecialOrderLogicMixin
|
||||
from .. import options
|
||||
from ..data.craftable_data import CraftingRecipe, all_crafting_recipes_by_name
|
||||
from ..data.recipe_data import StarterSource, ShopSource, SkillSource, FriendshipSource
|
||||
from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \
|
||||
FestivalShopSource, QuestSource
|
||||
from ..locations import locations_by_tag, LocationTags
|
||||
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland
|
||||
from ..stardew_rule import StardewRule, True_, False_, And
|
||||
from ..strings.region_names import Region
|
||||
|
||||
|
||||
class CraftingLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.crafting = CraftingLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class CraftingLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, MoneyLogicMixin, RelationshipLogicMixin,
|
||||
SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
|
||||
@cache_self1
|
||||
def can_craft(self, recipe: CraftingRecipe = None) -> StardewRule:
|
||||
if recipe is None:
|
||||
return True_()
|
||||
|
||||
learn_rule = self.logic.crafting.knows_recipe(recipe)
|
||||
ingredients_rule = self.logic.has_all(*recipe.ingredients)
|
||||
return learn_rule & ingredients_rule
|
||||
|
||||
@cache_self1
|
||||
def knows_recipe(self, recipe: CraftingRecipe) -> StardewRule:
|
||||
if isinstance(recipe.source, ArchipelagoSource):
|
||||
return self.logic.received_all(*recipe.source.ap_item)
|
||||
if isinstance(recipe.source, FestivalShopSource):
|
||||
if self.options.festival_locations == options.FestivalLocations.option_disabled:
|
||||
return self.logic.crafting.can_learn_recipe(recipe)
|
||||
else:
|
||||
return self.logic.crafting.received_recipe(recipe.item)
|
||||
if isinstance(recipe.source, QuestSource):
|
||||
if self.options.quest_locations < 0:
|
||||
return self.logic.crafting.can_learn_recipe(recipe)
|
||||
else:
|
||||
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):
|
||||
return self.logic.crafting.received_recipe(recipe.item)
|
||||
if isinstance(recipe.source, SpecialOrderSource) and self.options.special_order_locations != SpecialOrderLocations.option_disabled:
|
||||
return self.logic.crafting.received_recipe(recipe.item)
|
||||
return self.logic.crafting.can_learn_recipe(recipe)
|
||||
|
||||
@cache_self1
|
||||
def can_learn_recipe(self, recipe: CraftingRecipe) -> StardewRule:
|
||||
if isinstance(recipe.source, StarterSource):
|
||||
return True_()
|
||||
if isinstance(recipe.source, ArchipelagoSource):
|
||||
return self.logic.received_all(*recipe.source.ap_item)
|
||||
if isinstance(recipe.source, ShopTradeSource):
|
||||
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, SkillSource):
|
||||
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level)
|
||||
if isinstance(recipe.source, CutsceneSource):
|
||||
return self.logic.region.can_reach(recipe.source.region) & self.logic.relationship.has_hearts(recipe.source.friend, recipe.source.hearts)
|
||||
if isinstance(recipe.source, FriendshipSource):
|
||||
return self.logic.relationship.has_hearts(recipe.source.friend, recipe.source.hearts)
|
||||
if isinstance(recipe.source, QuestSource):
|
||||
return self.logic.quest.can_complete_quest(recipe.source.quest)
|
||||
if isinstance(recipe.source, SpecialOrderSource):
|
||||
if self.options.special_order_locations == SpecialOrderLocations.option_disabled:
|
||||
return self.logic.special_order.can_complete_special_order(recipe.source.special_order)
|
||||
return self.logic.crafting.received_recipe(recipe.item)
|
||||
if isinstance(recipe.source, LogicSource):
|
||||
if recipe.source.logic_rule == "Cellar":
|
||||
return self.logic.region.can_reach(Region.cellar)
|
||||
|
||||
return False_()
|
||||
|
||||
@cache_self1
|
||||
def received_recipe(self, item_name: str):
|
||||
return self.logic.received(f"{item_name} Recipe")
|
||||
|
||||
@cached_property
|
||||
def can_craft_everything(self) -> StardewRule:
|
||||
craftsanity_prefix = "Craft "
|
||||
all_recipes_names = []
|
||||
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
for location in locations_by_tag[LocationTags.CRAFTSANITY]:
|
||||
if not location.name.startswith(craftsanity_prefix):
|
||||
continue
|
||||
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
||||
continue
|
||||
if location.mod_name and location.mod_name not in self.options.mods:
|
||||
continue
|
||||
all_recipes_names.append(location.name[len(craftsanity_prefix):])
|
||||
all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
|
||||
return And(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))
|
||||
72
worlds/stardew_valley/logic/crop_logic.py
Normal file
72
worlds/stardew_valley/logic/crop_logic.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from typing import Union, Iterable
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from .traveling_merchant_logic import TravelingMerchantLogicMixin
|
||||
from ..data import CropItem, SeedItem
|
||||
from ..options import Cropsanity, ExcludeGingerIsland
|
||||
from ..stardew_rule import StardewRule, True_, False_
|
||||
from ..strings.craftable_names import Craftable
|
||||
from ..strings.forageable_names import Forageable
|
||||
from ..strings.machine_names import Machine
|
||||
from ..strings.metal_names import Fossil
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.seed_names import Seed
|
||||
from ..strings.tool_names import Tool
|
||||
|
||||
|
||||
class CropLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.crop = CropLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class CropLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, TravelingMerchantLogicMixin, SeasonLogicMixin, MoneyLogicMixin,
|
||||
ToolLogicMixin, CropLogicMixin]]):
|
||||
@cache_self1
|
||||
def can_grow(self, crop: CropItem) -> StardewRule:
|
||||
season_rule = self.logic.season.has_any(crop.farm_growth_seasons)
|
||||
seed_rule = self.logic.has(crop.seed.name)
|
||||
farm_rule = self.logic.region.can_reach(Region.farm) & season_rule
|
||||
tool_rule = self.logic.tool.has_tool(Tool.hoe) & self.logic.tool.has_tool(Tool.watering_can)
|
||||
region_rule = farm_rule | self.logic.region.can_reach(Region.greenhouse) | self.logic.crop.has_island_farm()
|
||||
if crop.name == Forageable.cactus_fruit:
|
||||
region_rule = self.logic.region.can_reach(Region.greenhouse) | self.logic.has(Craftable.garden_pot)
|
||||
return seed_rule & region_rule & tool_rule
|
||||
|
||||
def can_plant_and_grow_item(self, seasons: Union[str, Iterable[str]]) -> StardewRule:
|
||||
if isinstance(seasons, str):
|
||||
seasons = [seasons]
|
||||
season_rule = self.logic.season.has_any(seasons) | self.logic.region.can_reach(Region.greenhouse) | self.logic.crop.has_island_farm()
|
||||
farm_rule = self.logic.region.can_reach(Region.farm) | self.logic.region.can_reach(Region.greenhouse) | self.logic.crop.has_island_farm()
|
||||
return season_rule & farm_rule
|
||||
|
||||
def has_island_farm(self) -> StardewRule:
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_false:
|
||||
return self.logic.region.can_reach(Region.island_west)
|
||||
return False_()
|
||||
|
||||
@cache_self1
|
||||
def can_buy_seed(self, seed: SeedItem) -> StardewRule:
|
||||
if seed.requires_island and self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return False_()
|
||||
if self.options.cropsanity == Cropsanity.option_disabled or seed.name == Seed.qi_bean:
|
||||
item_rule = True_()
|
||||
else:
|
||||
item_rule = self.logic.received(seed.name)
|
||||
if seed.name == Seed.coffee:
|
||||
item_rule = item_rule & self.logic.traveling_merchant.has_days(3)
|
||||
season_rule = self.logic.season.has_any(seed.seasons)
|
||||
region_rule = self.logic.region.can_reach_all(seed.regions)
|
||||
currency_rule = self.logic.money.can_spend(1000)
|
||||
if seed.name == Seed.pineapple:
|
||||
currency_rule = self.logic.has(Forageable.magma_cap)
|
||||
if seed.name == Seed.taro:
|
||||
currency_rule = self.logic.has(Fossil.bone_fragment)
|
||||
return season_rule & region_rule & item_rule & currency_rule
|
||||
41
worlds/stardew_valley/logic/farming_logic.py
Normal file
41
worlds/stardew_valley/logic/farming_logic.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .has_logic import HasLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from ..stardew_rule import StardewRule, True_, False_
|
||||
from ..strings.fertilizer_names import Fertilizer
|
||||
from ..strings.quality_names import CropQuality
|
||||
|
||||
|
||||
class FarmingLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.farming = FarmingLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class FarmingLogic(BaseLogic[Union[HasLogicMixin, SkillLogicMixin, FarmingLogicMixin]]):
|
||||
def has_fertilizer(self, tier: int) -> StardewRule:
|
||||
if tier <= 0:
|
||||
return True_()
|
||||
if tier == 1:
|
||||
return self.logic.has(Fertilizer.basic)
|
||||
if tier == 2:
|
||||
return self.logic.has(Fertilizer.quality)
|
||||
if tier >= 3:
|
||||
return self.logic.has(Fertilizer.deluxe)
|
||||
|
||||
def can_grow_crop_quality(self, quality: str) -> StardewRule:
|
||||
if quality == CropQuality.basic:
|
||||
return True_()
|
||||
if quality == CropQuality.silver:
|
||||
return self.logic.skill.has_farming_level(5) | (self.logic.farming.has_fertilizer(1) & self.logic.skill.has_farming_level(2)) | (
|
||||
self.logic.farming.has_fertilizer(2) & self.logic.skill.has_farming_level(1)) | self.logic.farming.has_fertilizer(3)
|
||||
if quality == CropQuality.gold:
|
||||
return self.logic.skill.has_farming_level(10) | (
|
||||
self.logic.farming.has_fertilizer(1) & self.logic.skill.has_farming_level(5)) | (
|
||||
self.logic.farming.has_fertilizer(2) & self.logic.skill.has_farming_level(3)) | (
|
||||
self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(2))
|
||||
if quality == CropQuality.iridium:
|
||||
return self.logic.farming.has_fertilizer(3) & self.logic.skill.has_farming_level(4)
|
||||
return False_()
|
||||
100
worlds/stardew_valley/logic/fishing_logic.py
Normal file
100
worlds/stardew_valley/logic/fishing_logic.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from typing import Union, List
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from ..data import FishItem, fish_data
|
||||
from ..locations import LocationTags, locations_by_tag
|
||||
from ..options import ExcludeGingerIsland, Fishsanity
|
||||
from ..options import SpecialOrderLocations
|
||||
from ..stardew_rule import StardewRule, True_, False_, And
|
||||
from ..strings.fish_names import SVEFish
|
||||
from ..strings.quality_names import FishQuality
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.skill_names import Skill
|
||||
|
||||
|
||||
class FishingLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fishing = FishingLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class FishingLogic(BaseLogic[Union[FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, SkillLogicMixin]]):
|
||||
def can_fish_in_freshwater(self) -> StardewRule:
|
||||
return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain))
|
||||
|
||||
def has_max_fishing(self) -> StardewRule:
|
||||
skill_rule = self.logic.skill.has_level(Skill.fishing, 10)
|
||||
return self.logic.tool.has_fishing_rod(4) & skill_rule
|
||||
|
||||
def can_fish_chests(self) -> StardewRule:
|
||||
skill_rule = self.logic.skill.has_level(Skill.fishing, 6)
|
||||
return self.logic.tool.has_fishing_rod(4) & skill_rule
|
||||
|
||||
def can_fish_at(self, region: str) -> StardewRule:
|
||||
return self.logic.skill.can_fish() & self.logic.region.can_reach(region)
|
||||
|
||||
@cache_self1
|
||||
def can_catch_fish(self, fish: FishItem) -> StardewRule:
|
||||
quest_rule = True_()
|
||||
if fish.extended_family:
|
||||
quest_rule = self.logic.fishing.can_start_extended_family_quest()
|
||||
region_rule = self.logic.region.can_reach_any(fish.locations)
|
||||
season_rule = self.logic.season.has_any(fish.seasons)
|
||||
if fish.difficulty == -1:
|
||||
difficulty_rule = self.logic.skill.can_crab_pot
|
||||
else:
|
||||
difficulty_rule = self.logic.skill.can_fish(difficulty=(120 if fish.legendary else fish.difficulty))
|
||||
if fish.name == SVEFish.kittyfish:
|
||||
item_rule = self.logic.received("Kittyfish Spell")
|
||||
else:
|
||||
item_rule = True_()
|
||||
return quest_rule & region_rule & season_rule & difficulty_rule & item_rule
|
||||
|
||||
def can_start_extended_family_quest(self) -> StardewRule:
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return False_()
|
||||
if self.options.special_order_locations != SpecialOrderLocations.option_board_qi:
|
||||
return False_()
|
||||
return self.logic.region.can_reach(Region.qi_walnut_room) & And(*(self.logic.fishing.can_catch_fish(fish) for fish in fish_data.legendary_fish))
|
||||
|
||||
def can_catch_quality_fish(self, fish_quality: str) -> StardewRule:
|
||||
if fish_quality == FishQuality.basic:
|
||||
return True_()
|
||||
rod_rule = self.logic.tool.has_fishing_rod(2)
|
||||
if fish_quality == FishQuality.silver:
|
||||
return rod_rule
|
||||
if fish_quality == FishQuality.gold:
|
||||
return rod_rule & self.logic.skill.has_level(Skill.fishing, 4)
|
||||
if fish_quality == FishQuality.iridium:
|
||||
return rod_rule & self.logic.skill.has_level(Skill.fishing, 10)
|
||||
return False_()
|
||||
|
||||
def can_catch_every_fish(self) -> StardewRule:
|
||||
rules = [self.has_max_fishing()]
|
||||
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
exclude_extended_family = self.options.special_order_locations != SpecialOrderLocations.option_board_qi
|
||||
for fish in fish_data.get_fish_for_mods(self.options.mods.value):
|
||||
if exclude_island and fish in fish_data.island_fish:
|
||||
continue
|
||||
if exclude_extended_family and fish in fish_data.extended_family:
|
||||
continue
|
||||
rules.append(self.logic.fishing.can_catch_fish(fish))
|
||||
return And(*rules)
|
||||
|
||||
def can_catch_every_fish_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
|
||||
if self.options.fishsanity == Fishsanity.option_none:
|
||||
return self.can_catch_every_fish()
|
||||
|
||||
rules = [self.has_max_fishing()]
|
||||
|
||||
for fishsanity_location in locations_by_tag[LocationTags.FISHSANITY]:
|
||||
if fishsanity_location.name not in all_location_names_in_slot:
|
||||
continue
|
||||
rules.append(self.logic.region.can_reach_location(fishsanity_location.name))
|
||||
return And(*rules)
|
||||
20
worlds/stardew_valley/logic/gift_logic.py
Normal file
20
worlds/stardew_valley/logic/gift_logic.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from functools import cached_property
|
||||
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from ..stardew_rule import StardewRule
|
||||
from ..strings.animal_product_names import AnimalProduct
|
||||
from ..strings.gift_names import Gift
|
||||
|
||||
|
||||
class GiftLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.gifts = GiftLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class GiftLogic(BaseLogic[HasLogicMixin]):
|
||||
|
||||
@cached_property
|
||||
def has_any_universal_love(self) -> StardewRule:
|
||||
return self.logic.has_any(Gift.golden_pumpkin, Gift.pearl, "Prismatic Shard", AnimalProduct.rabbit_foot)
|
||||
34
worlds/stardew_valley/logic/has_logic.py
Normal file
34
worlds/stardew_valley/logic/has_logic.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from .base_logic import BaseLogic
|
||||
from ..stardew_rule import StardewRule, And, Or, Has, Count
|
||||
|
||||
|
||||
class HasLogicMixin(BaseLogic[None]):
|
||||
# Should be cached
|
||||
def has(self, item: str) -> StardewRule:
|
||||
return Has(item, self.registry.item_rules)
|
||||
|
||||
def has_all(self, *items: str):
|
||||
assert items, "Can't have all of no items."
|
||||
|
||||
return And(*(self.has(item) for item in items))
|
||||
|
||||
def has_any(self, *items: str):
|
||||
assert items, "Can't have any of no items."
|
||||
|
||||
return Or(*(self.has(item) for item in items))
|
||||
|
||||
def has_n(self, *items: str, count: int):
|
||||
return self.count(count, *(self.has(item) for item in items))
|
||||
|
||||
@staticmethod
|
||||
def count(count: int, *rules: StardewRule) -> StardewRule:
|
||||
assert rules, "Can't create a Count conditions without rules"
|
||||
assert len(rules) >= count, "Count need at least as many rules as the count"
|
||||
|
||||
if count == 1:
|
||||
return Or(*rules)
|
||||
|
||||
if count == len(rules):
|
||||
return And(*rules)
|
||||
|
||||
return Count(list(rules), count)
|
||||
673
worlds/stardew_valley/logic/logic.py
Normal file
673
worlds/stardew_valley/logic/logic.py
Normal file
@@ -0,0 +1,673 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Collection
|
||||
|
||||
from .ability_logic import AbilityLogicMixin
|
||||
from .action_logic import ActionLogicMixin
|
||||
from .animal_logic import AnimalLogicMixin
|
||||
from .arcade_logic import ArcadeLogicMixin
|
||||
from .artisan_logic import ArtisanLogicMixin
|
||||
from .base_logic import LogicRegistry
|
||||
from .buff_logic import BuffLogicMixin
|
||||
from .building_logic import BuildingLogicMixin
|
||||
from .bundle_logic import BundleLogicMixin
|
||||
from .combat_logic import CombatLogicMixin
|
||||
from .cooking_logic import CookingLogicMixin
|
||||
from .crafting_logic import CraftingLogicMixin
|
||||
from .crop_logic import CropLogicMixin
|
||||
from .farming_logic import FarmingLogicMixin
|
||||
from .fishing_logic import FishingLogicMixin
|
||||
from .gift_logic import GiftLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .mine_logic import MineLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .monster_logic import MonsterLogicMixin
|
||||
from .museum_logic import MuseumLogicMixin
|
||||
from .pet_logic import PetLogicMixin
|
||||
from .quest_logic import QuestLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .relationship_logic import RelationshipLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .shipping_logic import ShippingLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from .special_order_logic import SpecialOrderLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from .traveling_merchant_logic import TravelingMerchantLogicMixin
|
||||
from .wallet_logic import WalletLogicMixin
|
||||
from ..data import all_purchasable_seeds, all_crops
|
||||
from ..data.craftable_data import all_crafting_recipes
|
||||
from ..data.crops_data import crops_by_name
|
||||
from ..data.fish_data import get_fish_for_mods
|
||||
from ..data.museum_data import all_museum_items
|
||||
from ..data.recipe_data import all_cooking_recipes
|
||||
from ..mods.logic.magic_logic import MagicLogicMixin
|
||||
from ..mods.logic.mod_logic import ModLogicMixin
|
||||
from ..mods.mod_data import ModNames
|
||||
from ..options import Cropsanity, SpecialOrderLocations, ExcludeGingerIsland, FestivalLocations, Fishsanity, Friendsanity, StardewValleyOptions
|
||||
from ..stardew_rule import False_, Or, True_, And, StardewRule
|
||||
from ..strings.animal_names import Animal
|
||||
from ..strings.animal_product_names import AnimalProduct
|
||||
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.artisan_good_names import ArtisanGood
|
||||
from ..strings.building_names import Building
|
||||
from ..strings.craftable_names import Consumable, Furniture, Ring, Fishing, Lighting, WildSeeds
|
||||
from ..strings.crop_names import Fruit, Vegetable
|
||||
from ..strings.currency_names import Currency
|
||||
from ..strings.decoration_names import Decoration
|
||||
from ..strings.fertilizer_names import Fertilizer, SpeedGro, RetainingSoil
|
||||
from ..strings.festival_check_names import FestivalCheck
|
||||
from ..strings.fish_names import Fish, Trash, WaterItem, WaterChest
|
||||
from ..strings.flower_names import Flower
|
||||
from ..strings.food_names import Meal, Beverage
|
||||
from ..strings.forageable_names import Forageable
|
||||
from ..strings.fruit_tree_names import Sapling
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.geode_names import Geode
|
||||
from ..strings.gift_names import Gift
|
||||
from ..strings.ingredient_names import Ingredient
|
||||
from ..strings.machine_names import Machine
|
||||
from ..strings.material_names import Material
|
||||
from ..strings.metal_names import Ore, MetalBar, Mineral, Fossil
|
||||
from ..strings.monster_drop_names import Loot
|
||||
from ..strings.monster_names import Monster
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.season_names import Season
|
||||
from ..strings.seed_names import Seed, TreeSeed
|
||||
from ..strings.skill_names import Skill
|
||||
from ..strings.tool_names import Tool, ToolMaterial
|
||||
from ..strings.villager_names import NPC
|
||||
from ..strings.wallet_item_names import Wallet
|
||||
|
||||
|
||||
@dataclass(frozen=False, repr=False)
|
||||
class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, BuffLogicMixin, TravelingMerchantLogicMixin, TimeLogicMixin,
|
||||
SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, GiftLogicMixin,
|
||||
BuildingLogicMixin, ShippingLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, WalletLogicMixin, AnimalLogicMixin,
|
||||
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, CropLogicMixin,
|
||||
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
|
||||
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin):
|
||||
player: int
|
||||
options: StardewValleyOptions
|
||||
regions: Collection[str]
|
||||
|
||||
def __init__(self, player: int, options: StardewValleyOptions, regions: Collection[str]):
|
||||
self.registry = LogicRegistry()
|
||||
super().__init__(player, self.registry, options, regions, self)
|
||||
|
||||
self.registry.fish_rules.update({fish.name: self.fishing.can_catch_fish(fish) for fish in get_fish_for_mods(self.options.mods.value)})
|
||||
self.registry.museum_rules.update({donation.item_name: self.museum.can_find_museum_item(donation) for donation in all_museum_items})
|
||||
|
||||
for recipe in all_cooking_recipes:
|
||||
if recipe.mod_name and recipe.mod_name not in self.options.mods:
|
||||
continue
|
||||
can_cook_rule = self.cooking.can_cook(recipe)
|
||||
if recipe.meal in self.registry.cooking_rules:
|
||||
can_cook_rule = can_cook_rule | self.registry.cooking_rules[recipe.meal]
|
||||
self.registry.cooking_rules[recipe.meal] = can_cook_rule
|
||||
|
||||
for recipe in all_crafting_recipes:
|
||||
if recipe.mod_name and recipe.mod_name not in self.options.mods:
|
||||
continue
|
||||
can_craft_rule = self.crafting.can_craft(recipe)
|
||||
if recipe.item in self.registry.crafting_rules:
|
||||
can_craft_rule = can_craft_rule | self.registry.crafting_rules[recipe.item]
|
||||
self.registry.crafting_rules[recipe.item] = can_craft_rule
|
||||
|
||||
self.registry.sapling_rules.update({
|
||||
Sapling.apple: self.can_buy_sapling(Fruit.apple),
|
||||
Sapling.apricot: self.can_buy_sapling(Fruit.apricot),
|
||||
Sapling.cherry: self.can_buy_sapling(Fruit.cherry),
|
||||
Sapling.orange: self.can_buy_sapling(Fruit.orange),
|
||||
Sapling.peach: self.can_buy_sapling(Fruit.peach),
|
||||
Sapling.pomegranate: self.can_buy_sapling(Fruit.pomegranate),
|
||||
Sapling.banana: self.can_buy_sapling(Fruit.banana),
|
||||
Sapling.mango: self.can_buy_sapling(Fruit.mango),
|
||||
})
|
||||
|
||||
self.registry.tree_fruit_rules.update({
|
||||
Fruit.apple: self.crop.can_plant_and_grow_item(Season.fall),
|
||||
Fruit.apricot: self.crop.can_plant_and_grow_item(Season.spring),
|
||||
Fruit.cherry: self.crop.can_plant_and_grow_item(Season.spring),
|
||||
Fruit.orange: self.crop.can_plant_and_grow_item(Season.summer),
|
||||
Fruit.peach: self.crop.can_plant_and_grow_item(Season.summer),
|
||||
Fruit.pomegranate: self.crop.can_plant_and_grow_item(Season.fall),
|
||||
Fruit.banana: self.crop.can_plant_and_grow_item(Season.summer),
|
||||
Fruit.mango: self.crop.can_plant_and_grow_item(Season.summer),
|
||||
})
|
||||
|
||||
for tree_fruit in self.registry.tree_fruit_rules:
|
||||
existing_rules = self.registry.tree_fruit_rules[tree_fruit]
|
||||
sapling = f"{tree_fruit} Sapling"
|
||||
self.registry.tree_fruit_rules[tree_fruit] = existing_rules & self.has(sapling) & self.time.has_lived_months(1)
|
||||
|
||||
self.registry.seed_rules.update({seed.name: self.crop.can_buy_seed(seed) for seed in all_purchasable_seeds})
|
||||
self.registry.crop_rules.update({crop.name: self.crop.can_grow(crop) for crop in all_crops})
|
||||
self.registry.crop_rules.update({
|
||||
Seed.coffee: (self.season.has(Season.spring) | self.season.has(Season.summer)) & self.crop.can_buy_seed(crops_by_name[Seed.coffee].seed),
|
||||
Fruit.ancient_fruit: (self.received("Ancient Seeds") | self.received("Ancient Seeds Recipe")) &
|
||||
self.region.can_reach(Region.greenhouse) & self.has(Machine.seed_maker),
|
||||
})
|
||||
|
||||
# @formatter:off
|
||||
self.registry.item_rules.update({
|
||||
"Energy Tonic": self.money.can_spend_at(Region.hospital, 1000),
|
||||
WaterChest.fishing_chest: self.fishing.can_fish_chests(),
|
||||
WaterChest.treasure: self.fishing.can_fish_chests(),
|
||||
Ring.hot_java_ring: self.region.can_reach(Region.volcano_floor_10),
|
||||
"Galaxy Soul": self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 40),
|
||||
"JotPK Big Buff": self.arcade.has_jotpk_power_level(7),
|
||||
"JotPK Max Buff": self.arcade.has_jotpk_power_level(9),
|
||||
"JotPK Medium Buff": self.arcade.has_jotpk_power_level(4),
|
||||
"JotPK Small Buff": self.arcade.has_jotpk_power_level(2),
|
||||
"Junimo Kart Big Buff": self.arcade.has_junimo_kart_power_level(6),
|
||||
"Junimo Kart Max Buff": self.arcade.has_junimo_kart_power_level(8),
|
||||
"Junimo Kart Medium Buff": self.arcade.has_junimo_kart_power_level(4),
|
||||
"Junimo Kart Small Buff": self.arcade.has_junimo_kart_power_level(2),
|
||||
"Magic Rock Candy": self.region.can_reach(Region.desert) & self.has("Prismatic Shard"),
|
||||
"Muscle Remedy": self.money.can_spend_at(Region.hospital, 1000),
|
||||
# self.has(Ingredient.vinegar)),
|
||||
# self.received("Deluxe Fertilizer Recipe") & self.has(MetalBar.iridium) & self.has(SVItem.sap),
|
||||
# | (self.ability.can_cook() & self.relationship.has_hearts(NPC.emily, 3) & self.has(Forageable.leek) & self.has(Forageable.dandelion) &
|
||||
# | (self.ability.can_cook() & self.relationship.has_hearts(NPC.jodi, 7) & self.has(AnimalProduct.cow_milk) & self.has(Ingredient.sugar)),
|
||||
Animal.chicken: self.animal.can_buy_animal(Animal.chicken),
|
||||
Animal.cow: self.animal.can_buy_animal(Animal.cow),
|
||||
Animal.dinosaur: self.building.has_building(Building.big_coop) & self.has(AnimalProduct.dinosaur_egg),
|
||||
Animal.duck: self.animal.can_buy_animal(Animal.duck),
|
||||
Animal.goat: self.animal.can_buy_animal(Animal.goat),
|
||||
Animal.ostrich: self.building.has_building(Building.barn) & self.has(AnimalProduct.ostrich_egg) & self.has(Machine.ostrich_incubator),
|
||||
Animal.pig: self.animal.can_buy_animal(Animal.pig),
|
||||
Animal.rabbit: self.animal.can_buy_animal(Animal.rabbit),
|
||||
Animal.sheep: self.animal.can_buy_animal(Animal.sheep),
|
||||
AnimalProduct.any_egg: self.has_any(AnimalProduct.chicken_egg, AnimalProduct.duck_egg),
|
||||
AnimalProduct.brown_egg: self.animal.has_animal(Animal.chicken),
|
||||
AnimalProduct.chicken_egg: self.has_any(AnimalProduct.egg, AnimalProduct.brown_egg, AnimalProduct.large_egg, AnimalProduct.large_brown_egg),
|
||||
AnimalProduct.cow_milk: self.has_any(AnimalProduct.milk, AnimalProduct.large_milk),
|
||||
AnimalProduct.duck_egg: self.animal.has_animal(Animal.duck),
|
||||
AnimalProduct.duck_feather: self.animal.has_happy_animal(Animal.duck),
|
||||
AnimalProduct.egg: self.animal.has_animal(Animal.chicken),
|
||||
AnimalProduct.goat_milk: self.has(Animal.goat),
|
||||
AnimalProduct.golden_egg: self.received(AnimalProduct.golden_egg) & (self.money.can_spend_at(Region.ranch, 100000) | self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100)),
|
||||
AnimalProduct.large_brown_egg: self.animal.has_happy_animal(Animal.chicken),
|
||||
AnimalProduct.large_egg: self.animal.has_happy_animal(Animal.chicken),
|
||||
AnimalProduct.large_goat_milk: self.animal.has_happy_animal(Animal.goat),
|
||||
AnimalProduct.large_milk: self.animal.has_happy_animal(Animal.cow),
|
||||
AnimalProduct.milk: self.animal.has_animal(Animal.cow),
|
||||
AnimalProduct.ostrich_egg: self.tool.can_forage(Generic.any, Region.island_north, True),
|
||||
AnimalProduct.rabbit_foot: self.animal.has_happy_animal(Animal.rabbit),
|
||||
AnimalProduct.roe: self.skill.can_fish() & self.building.has_building(Building.fish_pond),
|
||||
AnimalProduct.squid_ink: self.mine.can_mine_in_the_mines_floor_81_120() | (self.building.has_building(Building.fish_pond) & self.has(Fish.squid)),
|
||||
AnimalProduct.sturgeon_roe: self.has(Fish.sturgeon) & self.building.has_building(Building.fish_pond),
|
||||
AnimalProduct.truffle: self.animal.has_animal(Animal.pig) & self.season.has_any_not_winter(),
|
||||
AnimalProduct.void_egg: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)),
|
||||
AnimalProduct.wool: self.animal.has_animal(Animal.rabbit) | self.animal.has_animal(Animal.sheep),
|
||||
AnimalProduct.slime_egg_green: self.has(Machine.slime_egg_press) & self.has(Loot.slime),
|
||||
AnimalProduct.slime_egg_blue: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(3),
|
||||
AnimalProduct.slime_egg_red: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(6),
|
||||
AnimalProduct.slime_egg_purple: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(9),
|
||||
AnimalProduct.slime_egg_tiger: self.has(Fish.lionfish) & self.building.has_building(Building.fish_pond),
|
||||
ArtisanGood.aged_roe: self.artisan.can_preserves_jar(AnimalProduct.roe),
|
||||
ArtisanGood.battery_pack: (self.has(Machine.lightning_rod) & self.season.has_any_not_winter()) | self.has(Machine.solar_panel),
|
||||
ArtisanGood.caviar: self.artisan.can_preserves_jar(AnimalProduct.sturgeon_roe),
|
||||
ArtisanGood.cheese: (self.has(AnimalProduct.cow_milk) & self.has(Machine.cheese_press)) | (self.region.can_reach(Region.desert) & self.has(Mineral.emerald)),
|
||||
ArtisanGood.cloth: (self.has(AnimalProduct.wool) & self.has(Machine.loom)) | (self.region.can_reach(Region.desert) & self.has(Mineral.aquamarine)),
|
||||
ArtisanGood.dinosaur_mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.dinosaur_egg),
|
||||
ArtisanGood.duck_mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.duck_egg),
|
||||
ArtisanGood.goat_cheese: self.has(AnimalProduct.goat_milk) & self.has(Machine.cheese_press),
|
||||
ArtisanGood.green_tea: self.artisan.can_keg(Vegetable.tea_leaves),
|
||||
ArtisanGood.honey: self.money.can_spend_at(Region.oasis, 200) | (self.has(Machine.bee_house) & self.season.has_any_not_winter()),
|
||||
ArtisanGood.jelly: self.artisan.has_jelly(),
|
||||
ArtisanGood.juice: self.artisan.has_juice(),
|
||||
ArtisanGood.maple_syrup: self.has(Machine.tapper),
|
||||
ArtisanGood.mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.chicken_egg),
|
||||
ArtisanGood.mead: self.artisan.can_keg(ArtisanGood.honey),
|
||||
ArtisanGood.oak_resin: self.has(Machine.tapper),
|
||||
ArtisanGood.pale_ale: self.artisan.can_keg(Vegetable.hops),
|
||||
ArtisanGood.pickles: self.artisan.has_pickle(),
|
||||
ArtisanGood.pine_tar: self.has(Machine.tapper),
|
||||
ArtisanGood.truffle_oil: self.has(AnimalProduct.truffle) & self.has(Machine.oil_maker),
|
||||
ArtisanGood.void_mayonnaise: (self.skill.can_fish(Region.witch_swamp)) | (self.artisan.can_mayonnaise(AnimalProduct.void_egg)),
|
||||
ArtisanGood.wine: self.artisan.has_wine(),
|
||||
Beverage.beer: self.artisan.can_keg(Vegetable.wheat) | self.money.can_spend_at(Region.saloon, 400),
|
||||
Beverage.coffee: self.artisan.can_keg(Seed.coffee) | self.has(Machine.coffee_maker) | (self.money.can_spend_at(Region.saloon, 300)) | self.has("Hot Java Ring"),
|
||||
Beverage.pina_colada: self.money.can_spend_at(Region.island_resort, 600),
|
||||
Beverage.triple_shot_espresso: self.has("Hot Java Ring"),
|
||||
Decoration.rotten_plant: self.has(Lighting.jack_o_lantern) & self.season.has(Season.winter),
|
||||
Fertilizer.basic: self.money.can_spend_at(Region.pierre_store, 100),
|
||||
Fertilizer.quality: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
|
||||
Fertilizer.tree: self.skill.has_level(Skill.foraging, 7) & self.has(Material.fiber) & self.has(Material.stone),
|
||||
Fish.any: Or(*(self.fishing.can_catch_fish(fish) for fish in get_fish_for_mods(self.options.mods.value))),
|
||||
Fish.crab: self.skill.can_crab_pot_at(Region.beach),
|
||||
Fish.crayfish: self.skill.can_crab_pot_at(Region.town),
|
||||
Fish.lobster: self.skill.can_crab_pot_at(Region.beach),
|
||||
Fish.mussel: self.tool.can_forage(Generic.any, Region.beach) or self.has(Fish.mussel_node),
|
||||
Fish.mussel_node: self.region.can_reach(Region.island_west),
|
||||
Fish.oyster: self.tool.can_forage(Generic.any, Region.beach),
|
||||
Fish.periwinkle: self.skill.can_crab_pot_at(Region.town),
|
||||
Fish.shrimp: self.skill.can_crab_pot_at(Region.beach),
|
||||
Fish.snail: self.skill.can_crab_pot_at(Region.town),
|
||||
Fishing.curiosity_lure: self.monster.can_kill(self.monster.all_monsters_by_name[Monster.mummy]),
|
||||
Fishing.lead_bobber: self.skill.has_level(Skill.fishing, 6) & self.money.can_spend_at(Region.fish_shop, 200),
|
||||
Forageable.blackberry: self.tool.can_forage(Season.fall) | self.has_fruit_bats(),
|
||||
Forageable.cactus_fruit: self.tool.can_forage(Generic.any, Region.desert),
|
||||
Forageable.cave_carrot: self.tool.can_forage(Generic.any, Region.mines_floor_10, True),
|
||||
Forageable.chanterelle: self.tool.can_forage(Season.fall, Region.secret_woods) | self.has_mushroom_cave(),
|
||||
Forageable.coconut: self.tool.can_forage(Generic.any, Region.desert),
|
||||
Forageable.common_mushroom: self.tool.can_forage(Season.fall) | (self.tool.can_forage(Season.spring, Region.secret_woods)) | self.has_mushroom_cave(),
|
||||
Forageable.crocus: self.tool.can_forage(Season.winter),
|
||||
Forageable.crystal_fruit: self.tool.can_forage(Season.winter),
|
||||
Forageable.daffodil: self.tool.can_forage(Season.spring),
|
||||
Forageable.dandelion: self.tool.can_forage(Season.spring),
|
||||
Forageable.dragon_tooth: self.tool.can_forage(Generic.any, Region.volcano_floor_10),
|
||||
Forageable.fiddlehead_fern: self.tool.can_forage(Season.summer, Region.secret_woods),
|
||||
Forageable.ginger: self.tool.can_forage(Generic.any, Region.island_west, True),
|
||||
Forageable.hay: self.building.has_building(Building.silo) & self.tool.has_tool(Tool.scythe),
|
||||
Forageable.hazelnut: self.tool.can_forage(Season.fall),
|
||||
Forageable.holly: self.tool.can_forage(Season.winter),
|
||||
Forageable.journal_scrap: self.region.can_reach_all((Region.island_west, Region.island_north, Region.island_south, Region.volcano_floor_10)) & self.quest.has_magnifying_glass() & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),
|
||||
Forageable.leek: self.tool.can_forage(Season.spring),
|
||||
Forageable.magma_cap: self.tool.can_forage(Generic.any, Region.volcano_floor_5),
|
||||
Forageable.morel: self.tool.can_forage(Season.spring, Region.secret_woods) | self.has_mushroom_cave(),
|
||||
Forageable.purple_mushroom: self.tool.can_forage(Generic.any, Region.mines_floor_95) | self.tool.can_forage(Generic.any, Region.skull_cavern_25) | self.has_mushroom_cave(),
|
||||
Forageable.rainbow_shell: self.tool.can_forage(Season.summer, Region.beach),
|
||||
Forageable.red_mushroom: self.tool.can_forage(Season.summer, Region.secret_woods) | self.tool.can_forage(Season.fall, Region.secret_woods) | self.has_mushroom_cave(),
|
||||
Forageable.salmonberry: self.tool.can_forage(Season.spring) | self.has_fruit_bats(),
|
||||
Forageable.secret_note: self.quest.has_magnifying_glass() & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),
|
||||
Forageable.snow_yam: self.tool.can_forage(Season.winter, Region.beach, True),
|
||||
Forageable.spice_berry: self.tool.can_forage(Season.summer) | self.has_fruit_bats(),
|
||||
Forageable.spring_onion: self.tool.can_forage(Season.spring),
|
||||
Forageable.sweet_pea: self.tool.can_forage(Season.summer),
|
||||
Forageable.wild_horseradish: self.tool.can_forage(Season.spring),
|
||||
Forageable.wild_plum: self.tool.can_forage(Season.fall) | self.has_fruit_bats(),
|
||||
Forageable.winter_root: self.tool.can_forage(Season.winter, Region.forest, True),
|
||||
Fossil.bone_fragment: (self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.pickaxe)) | self.monster.can_kill(Monster.skeleton),
|
||||
Fossil.fossilized_leg: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.pickaxe),
|
||||
Fossil.fossilized_ribs: self.region.can_reach(Region.island_south) & self.tool.has_tool(Tool.hoe),
|
||||
Fossil.fossilized_skull: self.action.can_open_geode(Geode.golden_coconut),
|
||||
Fossil.fossilized_spine: self.skill.can_fish(Region.dig_site),
|
||||
Fossil.fossilized_tail: self.action.can_pan_at(Region.dig_site),
|
||||
Fossil.mummified_bat: self.region.can_reach(Region.volcano_floor_10),
|
||||
Fossil.mummified_frog: self.region.can_reach(Region.island_east) & self.tool.has_tool(Tool.scythe),
|
||||
Fossil.snake_skull: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.hoe),
|
||||
Fossil.snake_vertebrae: self.region.can_reach(Region.island_west) & self.tool.has_tool(Tool.hoe),
|
||||
Geode.artifact_trove: self.has(Geode.omni) & self.region.can_reach(Region.desert),
|
||||
Geode.frozen: self.mine.can_mine_in_the_mines_floor_41_80(),
|
||||
Geode.geode: self.mine.can_mine_in_the_mines_floor_1_40(),
|
||||
Geode.golden_coconut: self.region.can_reach(Region.island_north),
|
||||
Geode.magma: self.mine.can_mine_in_the_mines_floor_81_120() | (self.has(Fish.lava_eel) & self.building.has_building(Building.fish_pond)),
|
||||
Geode.omni: self.mine.can_mine_in_the_mines_floor_41_80() | self.region.can_reach(Region.desert) | self.action.can_pan() | self.received(Wallet.rusty_key) | (self.has(Fish.octopus) & self.building.has_building(Building.fish_pond)) | self.region.can_reach(Region.volcano_floor_10),
|
||||
Gift.bouquet: self.relationship.has_hearts(Generic.bachelor, 8) & self.money.can_spend_at(Region.pierre_store, 100),
|
||||
Gift.golden_pumpkin: self.season.has(Season.fall) | self.action.can_open_geode(Geode.artifact_trove),
|
||||
Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts(Generic.bachelor, 10) & self.building.has_house(1) & self.has(Consumable.rain_totem),
|
||||
Gift.movie_ticket: self.money.can_spend_at(Region.movie_ticket_stand, 1000),
|
||||
Gift.pearl: (self.has(Fish.blobfish) & self.building.has_building(Building.fish_pond)) | self.action.can_open_geode(Geode.artifact_trove),
|
||||
Gift.tea_set: self.season.has(Season.winter) & self.time.has_lived_max_months,
|
||||
Gift.void_ghost_pendant: self.money.can_trade_at(Region.desert, Loot.void_essence, 200) & self.relationship.has_hearts(NPC.krobus, 10),
|
||||
Gift.wilted_bouquet: self.has(Machine.furnace) & self.has(Gift.bouquet) & self.has(Material.coal),
|
||||
Ingredient.oil: self.money.can_spend_at(Region.pierre_store, 200) | (self.has(Machine.oil_maker) & (self.has(Vegetable.corn) | self.has(Flower.sunflower) | self.has(Seed.sunflower))),
|
||||
Ingredient.qi_seasoning: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 10),
|
||||
Ingredient.rice: self.money.can_spend_at(Region.pierre_store, 200) | (self.building.has_building(Building.mill) & self.has(Vegetable.unmilled_rice)),
|
||||
Ingredient.sugar: self.money.can_spend_at(Region.pierre_store, 100) | (self.building.has_building(Building.mill) & self.has(Vegetable.beet)),
|
||||
Ingredient.vinegar: self.money.can_spend_at(Region.pierre_store, 200),
|
||||
Ingredient.wheat_flour: self.money.can_spend_at(Region.pierre_store, 100) | (self.building.has_building(Building.mill) & self.has(Vegetable.wheat)),
|
||||
Loot.bat_wing: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern(),
|
||||
Loot.bug_meat: self.mine.can_mine_in_the_mines_floor_1_40(),
|
||||
Loot.slime: self.mine.can_mine_in_the_mines_floor_1_40(),
|
||||
Loot.solar_essence: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern(),
|
||||
Loot.void_essence: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern(),
|
||||
Machine.bee_house: self.skill.has_farming_level(3) & self.has(MetalBar.iron) & self.has(ArtisanGood.maple_syrup) & self.has(Material.coal) & self.has(Material.wood),
|
||||
Machine.cask: self.building.has_house(3) & self.region.can_reach(Region.cellar) & self.has(Material.wood) & self.has(Material.hardwood),
|
||||
Machine.cheese_press: self.skill.has_farming_level(6) & self.has(Material.wood) & self.has(Material.stone) & self.has(Material.hardwood) & self.has(MetalBar.copper),
|
||||
Machine.coffee_maker: self.received(Machine.coffee_maker),
|
||||
Machine.crab_pot: self.skill.has_level(Skill.fishing, 3) & (self.money.can_spend_at(Region.fish_shop, 1500) | (self.has(MetalBar.iron) & self.has(Material.wood))),
|
||||
Machine.furnace: self.has(Material.stone) & self.has(Ore.copper),
|
||||
Machine.keg: self.skill.has_farming_level(8) & self.has(Material.wood) & self.has(MetalBar.iron) & self.has(MetalBar.copper) & self.has(ArtisanGood.oak_resin),
|
||||
Machine.lightning_rod: self.skill.has_level(Skill.foraging, 6) & self.has(MetalBar.iron) & self.has(MetalBar.quartz) & self.has(Loot.bat_wing),
|
||||
Machine.loom: self.skill.has_farming_level(7) & self.has(Material.wood) & self.has(Material.fiber) & self.has(ArtisanGood.pine_tar),
|
||||
Machine.mayonnaise_machine: self.skill.has_farming_level(2) & self.has(Material.wood) & self.has(Material.stone) & self.has("Earth Crystal") & self.has(MetalBar.copper),
|
||||
Machine.ostrich_incubator: self.received("Ostrich Incubator Recipe") & self.has(Fossil.bone_fragment) & self.has(Material.hardwood) & self.has(Material.cinder_shard),
|
||||
Machine.preserves_jar: self.skill.has_farming_level(4) & self.has(Material.wood) & self.has(Material.stone) & self.has(Material.coal),
|
||||
Machine.recycling_machine: self.skill.has_level(Skill.fishing, 4) & self.has(Material.wood) & self.has(Material.stone) & self.has(MetalBar.iron),
|
||||
Machine.seed_maker: self.skill.has_farming_level(9) & self.has(Material.wood) & self.has(MetalBar.gold) & self.has(Material.coal),
|
||||
Machine.solar_panel: self.received("Solar Panel Recipe") & self.has(MetalBar.quartz) & self.has(MetalBar.iron) & self.has(MetalBar.gold),
|
||||
Machine.tapper: self.skill.has_level(Skill.foraging, 3) & self.has(Material.wood) & self.has(MetalBar.copper),
|
||||
Machine.worm_bin: self.skill.has_level(Skill.fishing, 8) & self.has(Material.hardwood) & self.has(MetalBar.gold) & self.has(MetalBar.iron) & self.has(Material.fiber),
|
||||
Machine.enricher: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20),
|
||||
Machine.pressure_nozzle: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20),
|
||||
Material.cinder_shard: self.region.can_reach(Region.volcano_floor_5),
|
||||
Material.clay: self.region.can_reach_any((Region.farm, Region.beach, Region.quarry)) & self.tool.has_tool(Tool.hoe),
|
||||
Material.coal: self.mine.can_mine_in_the_mines_floor_41_80() | self.action.can_pan(),
|
||||
Material.fiber: True_(),
|
||||
Material.hardwood: self.tool.has_tool(Tool.axe, ToolMaterial.copper) & (self.region.can_reach(Region.secret_woods) | self.region.can_reach(Region.island_west)),
|
||||
Material.sap: self.ability.can_chop_trees(),
|
||||
Material.stone: self.tool.has_tool(Tool.pickaxe),
|
||||
Material.wood: self.tool.has_tool(Tool.axe),
|
||||
Meal.bread: self.money.can_spend_at(Region.saloon, 120),
|
||||
Meal.ice_cream: (self.season.has(Season.summer) & self.money.can_spend_at(Region.town, 250)) | self.money.can_spend_at(Region.oasis, 240),
|
||||
Meal.pizza: self.money.can_spend_at(Region.saloon, 600),
|
||||
Meal.salad: self.money.can_spend_at(Region.saloon, 220),
|
||||
Meal.spaghetti: self.money.can_spend_at(Region.saloon, 240),
|
||||
Meal.strange_bun: self.relationship.has_hearts(NPC.shane, 7) & self.has(Ingredient.wheat_flour) & self.has(Fish.periwinkle) & self.has(ArtisanGood.void_mayonnaise),
|
||||
MetalBar.copper: self.can_smelt(Ore.copper),
|
||||
MetalBar.gold: self.can_smelt(Ore.gold),
|
||||
MetalBar.iridium: self.can_smelt(Ore.iridium),
|
||||
MetalBar.iron: self.can_smelt(Ore.iron),
|
||||
MetalBar.quartz: self.can_smelt(Mineral.quartz) | self.can_smelt("Fire Quartz") | (self.has(Machine.recycling_machine) & (self.has(Trash.broken_cd) | self.has(Trash.broken_glasses))),
|
||||
MetalBar.radioactive: self.can_smelt(Ore.radioactive),
|
||||
Ore.copper: self.mine.can_mine_in_the_mines_floor_1_40() | self.mine.can_mine_in_the_skull_cavern() | self.action.can_pan(),
|
||||
Ore.gold: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern() | self.action.can_pan(),
|
||||
Ore.iridium: self.mine.can_mine_in_the_skull_cavern() | self.can_fish_pond(Fish.super_cucumber),
|
||||
Ore.iron: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern() | self.action.can_pan(),
|
||||
Ore.radioactive: self.ability.can_mine_perfectly() & self.region.can_reach(Region.qi_walnut_room),
|
||||
RetainingSoil.basic: self.money.can_spend_at(Region.pierre_store, 100),
|
||||
RetainingSoil.quality: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
|
||||
Sapling.tea: self.relationship.has_hearts(NPC.caroline, 2) & self.has(Material.fiber) & self.has(Material.wood),
|
||||
Seed.mixed: self.tool.has_tool(Tool.scythe) & self.region.can_reach_all((Region.farm, Region.forest, Region.town)),
|
||||
SpeedGro.basic: self.money.can_spend_at(Region.pierre_store, 100),
|
||||
SpeedGro.deluxe: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
|
||||
Trash.broken_cd: self.skill.can_crab_pot,
|
||||
Trash.broken_glasses: self.skill.can_crab_pot,
|
||||
Trash.driftwood: self.skill.can_crab_pot,
|
||||
Trash.joja_cola: self.money.can_spend_at(Region.saloon, 75),
|
||||
Trash.soggy_newspaper: self.skill.can_crab_pot,
|
||||
Trash.trash: self.skill.can_crab_pot,
|
||||
TreeSeed.acorn: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
|
||||
TreeSeed.mahogany: self.region.can_reach(Region.secret_woods) & self.tool.has_tool(Tool.axe, ToolMaterial.iron) & self.skill.has_level(Skill.foraging, 1),
|
||||
TreeSeed.maple: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
|
||||
TreeSeed.mushroom: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 5),
|
||||
TreeSeed.pine: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
|
||||
Vegetable.tea_leaves: self.has(Sapling.tea) & self.time.has_lived_months(2) & self.season.has_any_not_winter(),
|
||||
Fish.clam: self.tool.can_forage(Generic.any, Region.beach),
|
||||
Fish.cockle: self.tool.can_forage(Generic.any, Region.beach),
|
||||
WaterItem.coral: self.tool.can_forage(Generic.any, Region.tide_pools) | self.tool.can_forage(Season.summer, Region.beach),
|
||||
WaterItem.green_algae: self.fishing.can_fish_in_freshwater(),
|
||||
WaterItem.nautilus_shell: self.tool.can_forage(Season.winter, Region.beach),
|
||||
WaterItem.sea_urchin: self.tool.can_forage(Generic.any, Region.tide_pools),
|
||||
WaterItem.seaweed: self.skill.can_fish(Region.tide_pools),
|
||||
WaterItem.white_algae: self.skill.can_fish(Region.mines_floor_20),
|
||||
WildSeeds.grass_starter: self.money.can_spend_at(Region.pierre_store, 100),
|
||||
})
|
||||
# @formatter:on
|
||||
self.registry.item_rules.update(self.registry.fish_rules)
|
||||
self.registry.item_rules.update(self.registry.museum_rules)
|
||||
self.registry.item_rules.update(self.registry.sapling_rules)
|
||||
self.registry.item_rules.update(self.registry.tree_fruit_rules)
|
||||
self.registry.item_rules.update(self.registry.seed_rules)
|
||||
self.registry.item_rules.update(self.registry.crop_rules)
|
||||
|
||||
self.registry.item_rules.update(self.mod.item.get_modded_item_rules())
|
||||
self.mod.item.modify_vanilla_item_rules_with_mod_additions(self.registry.item_rules) # New regions and content means new ways to obtain old items
|
||||
|
||||
# For some recipes, the cooked item can be obtained directly, so we either cook it or get it
|
||||
for recipe in self.registry.cooking_rules:
|
||||
cooking_rule = self.registry.cooking_rules[recipe]
|
||||
obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_()
|
||||
self.registry.item_rules[recipe] = obtention_rule | cooking_rule
|
||||
|
||||
# For some recipes, the crafted item can be obtained directly, so we either craft it or get it
|
||||
for recipe in self.registry.crafting_rules:
|
||||
crafting_rule = self.registry.crafting_rules[recipe]
|
||||
obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_()
|
||||
self.registry.item_rules[recipe] = obtention_rule | crafting_rule
|
||||
|
||||
self.building.initialize_rules()
|
||||
self.building.update_rules(self.mod.building.get_modded_building_rules())
|
||||
|
||||
self.quest.initialize_rules()
|
||||
self.quest.update_rules(self.mod.quest.get_modded_quest_rules())
|
||||
|
||||
self.registry.festival_rules.update({
|
||||
FestivalCheck.egg_hunt: self.can_win_egg_hunt(),
|
||||
FestivalCheck.strawberry_seeds: self.money.can_spend(1000),
|
||||
FestivalCheck.dance: self.relationship.has_hearts(Generic.bachelor, 4),
|
||||
FestivalCheck.tub_o_flowers: self.money.can_spend(2000),
|
||||
FestivalCheck.rarecrow_5: self.money.can_spend(2500),
|
||||
FestivalCheck.luau_soup: self.can_succeed_luau_soup(),
|
||||
FestivalCheck.moonlight_jellies: True_(),
|
||||
FestivalCheck.moonlight_jellies_banner: self.money.can_spend(800),
|
||||
FestivalCheck.starport_decal: self.money.can_spend(1000),
|
||||
FestivalCheck.smashing_stone: True_(),
|
||||
FestivalCheck.grange_display: self.can_succeed_grange_display(),
|
||||
FestivalCheck.rarecrow_1: True_(), # only cost star tokens
|
||||
FestivalCheck.fair_stardrop: True_(), # only cost star tokens
|
||||
FestivalCheck.spirit_eve_maze: True_(),
|
||||
FestivalCheck.jack_o_lantern: self.money.can_spend(2000),
|
||||
FestivalCheck.rarecrow_2: self.money.can_spend(5000),
|
||||
FestivalCheck.fishing_competition: self.can_win_fishing_competition(),
|
||||
FestivalCheck.rarecrow_4: self.money.can_spend(5000),
|
||||
FestivalCheck.mermaid_pearl: self.has(Forageable.secret_note),
|
||||
FestivalCheck.cone_hat: self.money.can_spend(2500),
|
||||
FestivalCheck.iridium_fireplace: self.money.can_spend(15000),
|
||||
FestivalCheck.rarecrow_7: self.money.can_spend(5000) & self.museum.can_donate_museum_artifacts(20),
|
||||
FestivalCheck.rarecrow_8: self.money.can_spend(5000) & self.museum.can_donate_museum_items(40),
|
||||
FestivalCheck.lupini_red_eagle: self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_portrait_mermaid: self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_solar_kingdom: self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_clouds: self.time.has_year_two & self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_1000_years: self.time.has_year_two & self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_three_trees: self.time.has_year_two & self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_the_serpent: self.time.has_year_three & self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_tropical_fish: self.time.has_year_three & self.money.can_spend(1200),
|
||||
FestivalCheck.lupini_land_of_clay: self.time.has_year_three & self.money.can_spend(1200),
|
||||
FestivalCheck.secret_santa: self.gifts.has_any_universal_love,
|
||||
FestivalCheck.legend_of_the_winter_star: True_(),
|
||||
FestivalCheck.rarecrow_3: True_(),
|
||||
FestivalCheck.all_rarecrows: self.region.can_reach(Region.farm) & self.has_all_rarecrows(),
|
||||
})
|
||||
|
||||
self.special_order.initialize_rules()
|
||||
self.special_order.update_rules(self.mod.special_order.get_modded_special_orders_rules())
|
||||
|
||||
def can_buy_sapling(self, fruit: str) -> StardewRule:
|
||||
sapling_prices = {Fruit.apple: 4000, Fruit.apricot: 2000, Fruit.cherry: 3400, Fruit.orange: 4000,
|
||||
Fruit.peach: 6000,
|
||||
Fruit.pomegranate: 6000, Fruit.banana: 0, Fruit.mango: 0}
|
||||
received_sapling = self.received(f"{fruit} Sapling")
|
||||
if self.options.cropsanity == Cropsanity.option_disabled:
|
||||
allowed_buy_sapling = True_()
|
||||
else:
|
||||
allowed_buy_sapling = received_sapling
|
||||
can_buy_sapling = self.money.can_spend_at(Region.pierre_store, sapling_prices[fruit])
|
||||
if fruit == Fruit.banana:
|
||||
can_buy_sapling = self.has_island_trader() & self.has(Forageable.dragon_tooth)
|
||||
elif fruit == Fruit.mango:
|
||||
can_buy_sapling = self.has_island_trader() & self.has(Fish.mussel_node)
|
||||
|
||||
return allowed_buy_sapling & can_buy_sapling
|
||||
|
||||
def can_smelt(self, item: str) -> StardewRule:
|
||||
return self.has(Machine.furnace) & self.has(item)
|
||||
|
||||
def can_complete_field_office(self) -> StardewRule:
|
||||
field_office = self.region.can_reach(Region.field_office)
|
||||
professor_snail = self.received("Open Professor Snail Cave")
|
||||
tools = self.tool.has_tool(Tool.pickaxe) & self.tool.has_tool(Tool.hoe) & self.tool.has_tool(Tool.scythe)
|
||||
leg_and_snake_skull = self.has_all(Fossil.fossilized_leg, Fossil.snake_skull)
|
||||
ribs_and_spine = self.has_all(Fossil.fossilized_ribs, Fossil.fossilized_spine)
|
||||
skull = self.has(Fossil.fossilized_skull)
|
||||
tail = self.has(Fossil.fossilized_tail)
|
||||
frog = self.has(Fossil.mummified_frog)
|
||||
bat = self.has(Fossil.mummified_bat)
|
||||
snake_vertebrae = self.has(Fossil.snake_vertebrae)
|
||||
return field_office & professor_snail & tools & leg_and_snake_skull & ribs_and_spine & skull & tail & frog & bat & snake_vertebrae
|
||||
|
||||
def can_finish_grandpa_evaluation(self) -> StardewRule:
|
||||
# https://stardewvalleywiki.com/Grandpa
|
||||
rules_worth_a_point = [
|
||||
self.money.can_have_earned_total(50000), # 50 000g
|
||||
self.money.can_have_earned_total(100000), # 100 000g
|
||||
self.money.can_have_earned_total(200000), # 200 000g
|
||||
self.money.can_have_earned_total(300000), # 300 000g
|
||||
self.money.can_have_earned_total(500000), # 500 000g
|
||||
self.money.can_have_earned_total(1000000), # 1 000 000g first point
|
||||
self.money.can_have_earned_total(1000000), # 1 000 000g second point
|
||||
self.skill.has_total_level(30), # Total Skills: 30
|
||||
self.skill.has_total_level(50), # Total Skills: 50
|
||||
self.museum.can_complete_museum(), # Completing the museum for a point
|
||||
# Catching every fish not expected
|
||||
# Shipping every item not expected
|
||||
self.relationship.can_get_married() & self.building.has_house(2),
|
||||
self.relationship.has_hearts("5", 8), # 5 Friends
|
||||
self.relationship.has_hearts("10", 8), # 10 friends
|
||||
self.pet.has_hearts(5), # Max Pet
|
||||
self.bundle.can_complete_community_center, # Community Center Completion
|
||||
self.bundle.can_complete_community_center, # CC Ceremony first point
|
||||
self.bundle.can_complete_community_center, # CC Ceremony second point
|
||||
self.received(Wallet.skull_key), # Skull Key obtained
|
||||
self.wallet.has_rusty_key(), # Rusty key obtained
|
||||
]
|
||||
return self.count(12, *rules_worth_a_point)
|
||||
|
||||
def can_win_egg_hunt(self) -> StardewRule:
|
||||
number_of_movement_buffs = self.options.movement_buff_number
|
||||
if self.options.festival_locations == FestivalLocations.option_hard or number_of_movement_buffs < 2:
|
||||
return True_()
|
||||
return self.received(Buff.movement, number_of_movement_buffs // 2)
|
||||
|
||||
def can_succeed_luau_soup(self) -> StardewRule:
|
||||
if self.options.festival_locations != FestivalLocations.option_hard:
|
||||
return True_()
|
||||
eligible_fish = [Fish.blobfish, Fish.crimsonfish, "Ice Pip", Fish.lava_eel, Fish.legend, Fish.angler, Fish.catfish, Fish.glacierfish, Fish.mutant_carp,
|
||||
Fish.spookfish, Fish.stingray, Fish.sturgeon, "Super Cucumber"]
|
||||
fish_rule = self.has_any(*eligible_fish)
|
||||
eligible_kegables = [Fruit.ancient_fruit, Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.melon,
|
||||
Fruit.orange, Fruit.peach, Fruit.pineapple, Fruit.pomegranate, Fruit.rhubarb, Fruit.starfruit, Fruit.strawberry,
|
||||
Forageable.cactus_fruit, Fruit.cherry, Fruit.cranberries, Fruit.grape, Forageable.spice_berry, Forageable.wild_plum,
|
||||
Vegetable.hops, Vegetable.wheat]
|
||||
keg_rules = [self.artisan.can_keg(kegable) for kegable in eligible_kegables]
|
||||
aged_rule = self.has(Machine.cask) & Or(*keg_rules)
|
||||
# There are a few other valid items, but I don't feel like coding them all
|
||||
return fish_rule | aged_rule
|
||||
|
||||
def can_succeed_grange_display(self) -> StardewRule:
|
||||
if self.options.festival_locations != FestivalLocations.option_hard:
|
||||
return True_()
|
||||
animal_rule = self.animal.has_animal(Generic.any)
|
||||
artisan_rule = self.artisan.can_keg(Generic.any) | self.artisan.can_preserves_jar(Generic.any)
|
||||
cooking_rule = self.money.can_spend_at(Region.saloon, 220) # Salads at the bar are good enough
|
||||
fish_rule = self.skill.can_fish(difficulty=50)
|
||||
forage_rule = self.region.can_reach_any((Region.forest, Region.backwoods)) # Hazelnut always available since the grange display is in fall
|
||||
mineral_rule = self.action.can_open_geode(Generic.any) # More than half the minerals are good enough
|
||||
good_fruits = [Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.orange, Fruit.peach, Fruit.pomegranate,
|
||||
Fruit.strawberry, Fruit.melon, Fruit.rhubarb, Fruit.pineapple, Fruit.ancient_fruit, Fruit.starfruit, ]
|
||||
fruit_rule = self.has_any(*good_fruits)
|
||||
good_vegetables = [Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.cauliflower, Forageable.fiddlehead_fern, Vegetable.kale,
|
||||
Vegetable.radish, Vegetable.taro_root, Vegetable.yam, Vegetable.red_cabbage, Vegetable.pumpkin]
|
||||
vegetable_rule = self.has_any(*good_vegetables)
|
||||
|
||||
return animal_rule & artisan_rule & cooking_rule & fish_rule & \
|
||||
forage_rule & fruit_rule & mineral_rule & vegetable_rule
|
||||
|
||||
def can_win_fishing_competition(self) -> StardewRule:
|
||||
return self.skill.can_fish(difficulty=60)
|
||||
|
||||
def has_island_trader(self) -> StardewRule:
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return False_()
|
||||
return self.region.can_reach(Region.island_trader)
|
||||
|
||||
def has_walnut(self, number: int) -> StardewRule:
|
||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||
return False_()
|
||||
if number <= 0:
|
||||
return True_()
|
||||
# https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations
|
||||
reach_south = self.region.can_reach(Region.island_south)
|
||||
reach_north = self.region.can_reach(Region.island_north)
|
||||
reach_west = self.region.can_reach(Region.island_west)
|
||||
reach_hut = self.region.can_reach(Region.leo_hut)
|
||||
reach_southeast = self.region.can_reach(Region.island_south_east)
|
||||
reach_field_office = self.region.can_reach(Region.field_office)
|
||||
reach_pirate_cove = self.region.can_reach(Region.pirate_cove)
|
||||
reach_outside_areas = And(reach_south, reach_north, reach_west, reach_hut)
|
||||
reach_volcano_regions = [self.region.can_reach(Region.volcano),
|
||||
self.region.can_reach(Region.volcano_secret_beach),
|
||||
self.region.can_reach(Region.volcano_floor_5),
|
||||
self.region.can_reach(Region.volcano_floor_10)]
|
||||
reach_volcano = Or(*reach_volcano_regions)
|
||||
reach_all_volcano = And(*reach_volcano_regions)
|
||||
reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office]
|
||||
reach_caves = And(self.region.can_reach(Region.qi_walnut_room), self.region.can_reach(Region.dig_site),
|
||||
self.region.can_reach(Region.gourmand_frog_cave),
|
||||
self.region.can_reach(Region.colored_crystals_cave),
|
||||
self.region.can_reach(Region.shipwreck), self.received(APWeapon.slingshot))
|
||||
reach_entire_island = And(reach_outside_areas, reach_all_volcano,
|
||||
reach_caves, reach_southeast, reach_field_office, reach_pirate_cove)
|
||||
if number <= 5:
|
||||
return Or(reach_south, reach_north, reach_west, reach_volcano)
|
||||
if number <= 10:
|
||||
return self.count(2, *reach_walnut_regions)
|
||||
if number <= 15:
|
||||
return self.count(3, *reach_walnut_regions)
|
||||
if number <= 20:
|
||||
return And(*reach_walnut_regions)
|
||||
if number <= 50:
|
||||
return reach_entire_island
|
||||
gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz)
|
||||
return reach_entire_island & self.has(Fruit.banana) & self.has_all(*gems) & self.ability.can_mine_perfectly() & \
|
||||
self.ability.can_fish_perfectly() & self.has(Furniture.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) & \
|
||||
self.can_complete_field_office()
|
||||
|
||||
def has_all_stardrops(self) -> StardewRule:
|
||||
other_rules = []
|
||||
number_of_stardrops_to_receive = 0
|
||||
number_of_stardrops_to_receive += 1 # The Mines level 100
|
||||
number_of_stardrops_to_receive += 1 # Old Master Cannoli
|
||||
number_of_stardrops_to_receive += 1 # Museum Stardrop
|
||||
number_of_stardrops_to_receive += 1 # Krobus Stardrop
|
||||
|
||||
if self.options.fishsanity == Fishsanity.option_none: # Master Angler Stardrop
|
||||
other_rules.append(self.fishing.can_catch_every_fish())
|
||||
else:
|
||||
number_of_stardrops_to_receive += 1
|
||||
|
||||
if self.options.festival_locations == FestivalLocations.option_disabled: # Fair Stardrop
|
||||
other_rules.append(self.season.has(Season.fall))
|
||||
else:
|
||||
number_of_stardrops_to_receive += 1
|
||||
|
||||
if self.options.friendsanity == Friendsanity.option_none: # Spouse Stardrop
|
||||
other_rules.append(self.relationship.has_hearts(Generic.bachelor, 13))
|
||||
else:
|
||||
number_of_stardrops_to_receive += 1
|
||||
|
||||
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
|
||||
number_of_stardrops_to_receive += 1
|
||||
|
||||
if not other_rules:
|
||||
return self.received("Stardrop", number_of_stardrops_to_receive)
|
||||
|
||||
return self.received("Stardrop", number_of_stardrops_to_receive) & And(*other_rules)
|
||||
|
||||
def has_prismatic_jelly_reward_access(self) -> StardewRule:
|
||||
if self.options.special_order_locations == SpecialOrderLocations.option_disabled:
|
||||
return self.special_order.can_complete_special_order("Prismatic Jelly")
|
||||
return self.received("Monster Musk Recipe")
|
||||
|
||||
def has_all_rarecrows(self) -> StardewRule:
|
||||
rules = []
|
||||
for rarecrow_number in range(1, 9):
|
||||
rules.append(self.received(f"Rarecrow #{rarecrow_number}"))
|
||||
return And(*rules)
|
||||
|
||||
def has_abandoned_jojamart(self) -> StardewRule:
|
||||
return self.received(CommunityUpgrade.movie_theater, 1)
|
||||
|
||||
def has_movie_theater(self) -> StardewRule:
|
||||
return self.received(CommunityUpgrade.movie_theater, 2)
|
||||
|
||||
def can_use_obelisk(self, obelisk: str) -> StardewRule:
|
||||
return self.region.can_reach(Region.farm) & self.received(obelisk)
|
||||
|
||||
def has_fruit_bats(self) -> StardewRule:
|
||||
return self.region.can_reach(Region.farm_cave) & self.received(CommunityUpgrade.fruit_bats)
|
||||
|
||||
def has_mushroom_cave(self) -> StardewRule:
|
||||
return self.region.can_reach(Region.farm_cave) & self.received(CommunityUpgrade.mushroom_boxes)
|
||||
|
||||
def can_fish_pond(self, fish: str) -> StardewRule:
|
||||
return self.building.has_building(Building.fish_pond) & self.has(fish)
|
||||
58
worlds/stardew_valley/logic/logic_and_mods_design.md
Normal file
58
worlds/stardew_valley/logic/logic_and_mods_design.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Logic mixin
|
||||
|
||||
Mixins are used to split the logic building methods in multiple classes, so it's more scoped and easier to extend specific methods.
|
||||
|
||||
One single instance of Logic is necessary so mods can change the logics. This means that, when calling itself, a `Logic` class has to call its instance in
|
||||
the `logic`, because it might have been overriden.
|
||||
|
||||
```python
|
||||
class TimeLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.time = TimeLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]):
|
||||
|
||||
def has_lived_months(self, number: int) -> StardewRule:
|
||||
return self.logic.received(Event.month_end, number)
|
||||
|
||||
def has_year_two(self) -> StardewRule:
|
||||
return self.logic.time.has_lived_months(4)
|
||||
|
||||
def has_year_three(self) -> StardewRule:
|
||||
return self.logic.time.has_lived_months(8)
|
||||
```
|
||||
|
||||
Creating the rules for actual items has to be outside the `logic` instance. Once the vanilla logic builder is created, mods will be able to replace the logic
|
||||
implementations by their own modified version. For instance, the `combat` logic can be replaced by the magic mod to extends its methods to add spells in the
|
||||
combat logic.
|
||||
|
||||
## Logic class created on the fly (idea)
|
||||
|
||||
The logic class could be created dynamically, based on the `LogicMixin` provided by the content packs. This would allow replacing completely mixins, instead of
|
||||
overriding their logic afterward. Might be too complicated for no real gain tho...
|
||||
|
||||
# Content pack (idea)
|
||||
|
||||
Instead of using modules to hold the data, and have each mod adding their data to existing content, each mod data should be in a `ContentPack`. Vanilla, Ginger
|
||||
Island, or anything that could be disabled would be in a content pack as well.
|
||||
|
||||
Eventually, Vanilla content could even be disabled (a split would be required for items that are necessary to all content packs) to have a Ginger Island only
|
||||
play through created without the heavy vanilla logic computation.
|
||||
|
||||
## Unpacking
|
||||
|
||||
Steps to unpack content follows the same steps has the world initialisation. Content pack however need to be unpacked in a specific order, based on their
|
||||
dependencies. Vanilla would always be first, then anything that depends only on Vanilla, etc.
|
||||
|
||||
1. In `generate_early`, content packs are selected. The logic builders are created and content packs are unpacked so all their content is in the proper
|
||||
item/npc/weapon lists.
|
||||
- `ContentPack` instances are shared across players. However, some mods need to modify content of other packs. In that case, an instance of the content is
|
||||
created specifically for that player (For instance, SVE changes the Wizard). This probably does not happen enough to require sharing those instances. If
|
||||
necessary, a FlyWeight design pattern could be used.
|
||||
2. In `create_regions`, AP regions and entrances are unpacked, and randomized if needed.
|
||||
3. In `create_items`, AP items are unpacked, and randomized.
|
||||
4. In `set_rules`, the rules are applied to the AP entrances and locations. Each content pack have to apply the proper rules for their entrances and locations.
|
||||
- (idea) To begin this step, sphere 0 could be simplified instantly as sphere 0 regions and items are already known.
|
||||
5. Nothing to do in `generate_basic`.
|
||||
86
worlds/stardew_valley/logic/mine_logic.py
Normal file
86
worlds/stardew_valley/logic/mine_logic.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .combat_logic import CombatLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
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, And, 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
|
||||
|
||||
|
||||
class MineLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.mine = MineLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class MineLogic(BaseLogic[Union[MineLogicMixin, RegionLogicMixin, ReceivedLogicMixin, CombatLogicMixin, ToolLogicMixin, SkillLogicMixin]]):
|
||||
# Regions
|
||||
def can_mine_in_the_mines_floor_1_40(self) -> StardewRule:
|
||||
return self.logic.region.can_reach(Region.mines_floor_5)
|
||||
|
||||
def can_mine_in_the_mines_floor_41_80(self) -> StardewRule:
|
||||
return self.logic.region.can_reach(Region.mines_floor_45)
|
||||
|
||||
def can_mine_in_the_mines_floor_81_120(self) -> StardewRule:
|
||||
return self.logic.region.can_reach(Region.mines_floor_85)
|
||||
|
||||
def can_mine_in_the_skull_cavern(self) -> StardewRule:
|
||||
return (self.logic.mine.can_progress_in_the_mines_from_floor(120) &
|
||||
self.logic.region.can_reach(Region.skull_cavern))
|
||||
|
||||
@cache_self1
|
||||
def get_weapon_rule_for_floor_tier(self, tier: int):
|
||||
if tier >= 4:
|
||||
return self.logic.combat.can_fight_at_level(Performance.galaxy)
|
||||
if tier >= 3:
|
||||
return self.logic.combat.can_fight_at_level(Performance.great)
|
||||
if tier >= 2:
|
||||
return self.logic.combat.can_fight_at_level(Performance.good)
|
||||
if tier >= 1:
|
||||
return self.logic.combat.can_fight_at_level(Performance.decent)
|
||||
return self.logic.combat.can_fight_at_level(Performance.basic)
|
||||
|
||||
@cache_self1
|
||||
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]))
|
||||
if self.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
skill_tier = min(10, max(0, tier * 2))
|
||||
rules.append(self.logic.skill.has_level(Skill.combat, skill_tier))
|
||||
rules.append(self.logic.skill.has_level(Skill.mining, skill_tier))
|
||||
return And(*rules)
|
||||
|
||||
@cache_self1
|
||||
def has_mine_elevator_to_floor(self, floor: int) -> StardewRule:
|
||||
if floor < 0:
|
||||
floor = 0
|
||||
if self.options.elevator_progression != options.ElevatorProgression.option_vanilla:
|
||||
return self.logic.received("Progressive Mine Elevator", floor // 5)
|
||||
return True_()
|
||||
|
||||
@cache_self1
|
||||
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))))
|
||||
if self.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
skill_tier = min(10, max(0, tier * 2 + 6))
|
||||
rules.extend({self.logic.skill.has_level(Skill.combat, skill_tier),
|
||||
self.logic.skill.has_level(Skill.mining, skill_tier)})
|
||||
return And(*rules)
|
||||
99
worlds/stardew_valley/logic/money_logic.py
Normal file
99
worlds/stardew_valley/logic/money_logic.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .buff_logic import BuffLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from ..options import SpecialOrderLocations
|
||||
from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_
|
||||
from ..strings.ap_names.event_names import Event
|
||||
from ..strings.currency_names import Currency
|
||||
from ..strings.region_names import Region
|
||||
|
||||
qi_gem_rewards = ("100 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems", "25 Qi Gems",
|
||||
"20 Qi Gems", "15 Qi Gems", "10 Qi Gems")
|
||||
|
||||
|
||||
class MoneyLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.money = MoneyLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, BuffLogicMixin]]):
|
||||
|
||||
@cache_self1
|
||||
def can_have_earned_total(self, amount: int) -> StardewRule:
|
||||
if amount < 1000:
|
||||
return True_()
|
||||
|
||||
pierre_rule = self.logic.region.can_reach_all((Region.pierre_store, Region.forest))
|
||||
willy_rule = self.logic.region.can_reach_all((Region.fish_shop, Region.fishing))
|
||||
clint_rule = self.logic.region.can_reach_all((Region.blacksmith, Region.mines_floor_5))
|
||||
robin_rule = self.logic.region.can_reach_all((Region.carpenter, Region.secret_woods))
|
||||
shipping_rule = self.logic.received(Event.can_ship_items)
|
||||
|
||||
if amount < 2000:
|
||||
selling_any_rule = pierre_rule | willy_rule | clint_rule | robin_rule | shipping_rule
|
||||
return selling_any_rule
|
||||
|
||||
if amount < 5000:
|
||||
selling_all_rule = (pierre_rule & willy_rule & clint_rule & robin_rule) | shipping_rule
|
||||
return selling_all_rule
|
||||
|
||||
if amount < 10000:
|
||||
return shipping_rule
|
||||
|
||||
seed_rules = self.logic.received(Event.can_shop_at_pierre)
|
||||
if amount < 40000:
|
||||
return shipping_rule & seed_rules
|
||||
|
||||
percent_progression_items_needed = min(90, amount // 20000)
|
||||
return shipping_rule & seed_rules & HasProgressionPercent(self.player, percent_progression_items_needed)
|
||||
|
||||
@cache_self1
|
||||
def can_spend(self, amount: int) -> StardewRule:
|
||||
if self.options.starting_money == -1:
|
||||
return True_()
|
||||
return self.logic.money.can_have_earned_total(amount * 5)
|
||||
|
||||
# Should be cached
|
||||
def can_spend_at(self, region: str, amount: int) -> StardewRule:
|
||||
return self.logic.region.can_reach(region) & self.logic.money.can_spend(amount)
|
||||
|
||||
# Should be cached
|
||||
def can_trade(self, currency: str, amount: int) -> StardewRule:
|
||||
if amount == 0:
|
||||
return True_()
|
||||
if currency == Currency.money:
|
||||
return self.can_spend(amount)
|
||||
if currency == Currency.star_token:
|
||||
return self.logic.region.can_reach(Region.fair)
|
||||
if currency == Currency.qi_coin:
|
||||
return self.logic.region.can_reach(Region.casino) & self.logic.buff.has_max_luck()
|
||||
if currency == Currency.qi_gem:
|
||||
if self.options.special_order_locations == SpecialOrderLocations.option_board_qi:
|
||||
number_rewards = min(len(qi_gem_rewards), max(1, (amount // 10)))
|
||||
return self.logic.received_n(*qi_gem_rewards, count=number_rewards)
|
||||
number_rewards = 2
|
||||
return self.logic.received_n(*qi_gem_rewards, count=number_rewards) & self.logic.region.can_reach(Region.qi_walnut_room) & \
|
||||
self.logic.region.can_reach(Region.saloon) & self.can_have_earned_total(5000)
|
||||
if currency == Currency.golden_walnut:
|
||||
return self.can_spend_walnut(amount)
|
||||
|
||||
return self.logic.has(currency) & self.logic.time.has_lived_months(amount)
|
||||
|
||||
# Should be cached
|
||||
def can_trade_at(self, region: str, currency: str, amount: int) -> StardewRule:
|
||||
if amount == 0:
|
||||
return True_()
|
||||
if currency == Currency.money:
|
||||
return self.logic.money.can_spend_at(region, amount)
|
||||
|
||||
return self.logic.region.can_reach(region) & self.can_trade(currency, amount)
|
||||
|
||||
def can_spend_walnut(self, amount: int) -> StardewRule:
|
||||
return False_()
|
||||
69
worlds/stardew_valley/logic/monster_logic.py
Normal file
69
worlds/stardew_valley/logic/monster_logic.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from functools import cached_property
|
||||
from typing import Iterable, Union, Hashable
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .combat_logic import CombatLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .time_logic import TimeLogicMixin, MAX_MONTHS
|
||||
from .. import options
|
||||
from ..data import monster_data
|
||||
from ..stardew_rule import StardewRule, Or, And
|
||||
from ..strings.region_names import Region
|
||||
|
||||
|
||||
class MonsterLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.monster = MonsterLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class MonsterLogic(BaseLogic[Union[MonsterLogicMixin, RegionLogicMixin, CombatLogicMixin, TimeLogicMixin]]):
|
||||
|
||||
@cached_property
|
||||
def all_monsters_by_name(self):
|
||||
return monster_data.all_monsters_by_name_given_mods(self.options.mods.value)
|
||||
|
||||
@cached_property
|
||||
def all_monsters_by_category(self):
|
||||
return monster_data.all_monsters_by_category_given_mods(self.options.mods.value)
|
||||
|
||||
def can_kill(self, monster: Union[str, monster_data.StardewMonster], amount_tier: int = 0) -> StardewRule:
|
||||
if isinstance(monster, str):
|
||||
monster = self.all_monsters_by_name[monster]
|
||||
region_rule = self.logic.region.can_reach_any(monster.locations)
|
||||
combat_rule = self.logic.combat.can_fight_at_level(monster.difficulty)
|
||||
if amount_tier <= 0:
|
||||
amount_tier = 0
|
||||
time_rule = self.logic.time.has_lived_months(amount_tier)
|
||||
return region_rule & combat_rule & time_rule
|
||||
|
||||
@cache_self1
|
||||
def can_kill_many(self, monster: monster_data.StardewMonster) -> StardewRule:
|
||||
return self.logic.monster.can_kill(monster, MAX_MONTHS / 3)
|
||||
|
||||
@cache_self1
|
||||
def can_kill_max(self, monster: monster_data.StardewMonster) -> StardewRule:
|
||||
return self.logic.monster.can_kill(monster, MAX_MONTHS)
|
||||
|
||||
# Should be cached
|
||||
def can_kill_any(self, monsters: (Iterable[monster_data.StardewMonster], Hashable), amount_tier: int = 0) -> StardewRule:
|
||||
rules = [self.logic.monster.can_kill(monster, amount_tier) for monster in monsters]
|
||||
return Or(*rules)
|
||||
|
||||
# Should be cached
|
||||
def can_kill_all(self, monsters: (Iterable[monster_data.StardewMonster], Hashable), amount_tier: int = 0) -> StardewRule:
|
||||
rules = [self.logic.monster.can_kill(monster, amount_tier) for monster in monsters]
|
||||
return And(*rules)
|
||||
|
||||
def can_complete_all_monster_slaying_goals(self) -> StardewRule:
|
||||
rules = [self.logic.time.has_lived_max_months]
|
||||
exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
|
||||
island_regions = [Region.volcano_floor_5, Region.volcano_floor_10, Region.island_west, Region.dangerous_skull_cavern]
|
||||
for category in self.all_monsters_by_category:
|
||||
if exclude_island and all(all(location in island_regions for location in monster.locations)
|
||||
for monster in self.all_monsters_by_category[category]):
|
||||
continue
|
||||
rules.append(self.logic.monster.can_kill_any(self.all_monsters_by_category[category]))
|
||||
|
||||
return And(*rules)
|
||||
80
worlds/stardew_valley/logic/museum_logic.py
Normal file
80
worlds/stardew_valley/logic/museum_logic.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .action_logic import ActionLogicMixin
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .. import options
|
||||
from ..data.museum_data import MuseumItem, all_museum_items, all_museum_artifacts, all_museum_minerals
|
||||
from ..stardew_rule import StardewRule, And, False_
|
||||
from ..strings.region_names import Region
|
||||
|
||||
|
||||
class MuseumLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.museum = MuseumLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, ActionLogicMixin, MuseumLogicMixin]]):
|
||||
|
||||
def can_donate_museum_items(self, number: int) -> StardewRule:
|
||||
return self.logic.region.can_reach(Region.museum) & self.logic.museum.can_find_museum_items(number)
|
||||
|
||||
def can_donate_museum_artifacts(self, number: int) -> StardewRule:
|
||||
return self.logic.region.can_reach(Region.museum) & self.logic.museum.can_find_museum_artifacts(number)
|
||||
|
||||
@cache_self1
|
||||
def can_find_museum_item(self, item: MuseumItem) -> StardewRule:
|
||||
if item.locations:
|
||||
region_rule = self.logic.region.can_reach_all_except_one(item.locations)
|
||||
else:
|
||||
region_rule = False_()
|
||||
if item.geodes:
|
||||
geodes_rule = And(*(self.logic.action.can_open_geode(geode) for geode in item.geodes))
|
||||
else:
|
||||
geodes_rule = False_()
|
||||
# monster_rule = self.can_farm_monster(item.monsters)
|
||||
# extra_rule = True_()
|
||||
pan_rule = False_()
|
||||
if item.item_name == "Earth Crystal" or item.item_name == "Fire Quartz" or item.item_name == "Frozen Tear":
|
||||
pan_rule = self.logic.action.can_pan()
|
||||
return pan_rule | region_rule | geodes_rule # & monster_rule & extra_rule
|
||||
|
||||
def can_find_museum_artifacts(self, number: int) -> StardewRule:
|
||||
rules = []
|
||||
for artifact in all_museum_artifacts:
|
||||
rules.append(self.logic.museum.can_find_museum_item(artifact))
|
||||
|
||||
return self.logic.count(number, *rules)
|
||||
|
||||
def can_find_museum_minerals(self, number: int) -> StardewRule:
|
||||
rules = []
|
||||
for mineral in all_museum_minerals:
|
||||
rules.append(self.logic.museum.can_find_museum_item(mineral))
|
||||
|
||||
return self.logic.count(number, *rules)
|
||||
|
||||
def can_find_museum_items(self, number: int) -> StardewRule:
|
||||
rules = []
|
||||
for donation in all_museum_items:
|
||||
rules.append(self.logic.museum.can_find_museum_item(donation))
|
||||
|
||||
return self.logic.count(number, *rules)
|
||||
|
||||
def can_complete_museum(self) -> StardewRule:
|
||||
rules = [self.logic.region.can_reach(Region.museum)]
|
||||
|
||||
if self.options.museumsanity == options.Museumsanity.option_none:
|
||||
rules.append(self.logic.received("Traveling Merchant Metal Detector", 2))
|
||||
else:
|
||||
rules.append(self.logic.received("Traveling Merchant Metal Detector", 3))
|
||||
|
||||
for donation in all_museum_items:
|
||||
rules.append(self.logic.museum.can_find_museum_item(donation))
|
||||
return And(*rules) & self.logic.region.can_reach(Region.museum)
|
||||
|
||||
def can_donate(self, item: str) -> StardewRule:
|
||||
return self.logic.has(item) & self.logic.region.can_reach(Region.museum)
|
||||
50
worlds/stardew_valley/logic/pet_logic.py
Normal file
50
worlds/stardew_valley/logic/pet_logic.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import math
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from ..data.villagers_data import Villager
|
||||
from ..options import Friendsanity
|
||||
from ..stardew_rule import StardewRule, True_
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.villager_names import NPC
|
||||
|
||||
|
||||
class PetLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.pet = PetLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class PetLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, TimeLogicMixin, ToolLogicMixin]]):
|
||||
def has_hearts(self, hearts: int = 1) -> StardewRule:
|
||||
if hearts <= 0:
|
||||
return True_()
|
||||
if self.options.friendsanity == Friendsanity.option_none or self.options.friendsanity == Friendsanity.option_bachelors:
|
||||
return self.can_befriend_pet(hearts)
|
||||
return self.received_hearts(NPC.pet, hearts)
|
||||
|
||||
def received_hearts(self, npc: Union[str, Villager], hearts: int) -> StardewRule:
|
||||
if isinstance(npc, Villager):
|
||||
return self.received_hearts(npc.name, hearts)
|
||||
return self.logic.received(self.heart(npc), math.ceil(hearts / self.options.friendsanity_heart_size))
|
||||
|
||||
def can_befriend_pet(self, hearts: int) -> StardewRule:
|
||||
if hearts <= 0:
|
||||
return True_()
|
||||
points = hearts * 200
|
||||
points_per_month = 12 * 14
|
||||
points_per_water_month = 18 * 14
|
||||
farm_rule = self.logic.region.can_reach(Region.farm)
|
||||
time_with_water_rule = self.logic.tool.can_water(0) & self.logic.time.has_lived_months(points // points_per_water_month)
|
||||
time_without_water_rule = self.logic.time.has_lived_months(points // points_per_month)
|
||||
time_rule = time_with_water_rule | time_without_water_rule
|
||||
return farm_rule & time_rule
|
||||
|
||||
def heart(self, npc: Union[str, Villager]) -> str:
|
||||
if isinstance(npc, str):
|
||||
return f"{npc} <3"
|
||||
return self.heart(npc.name)
|
||||
128
worlds/stardew_valley/logic/quest_logic.py
Normal file
128
worlds/stardew_valley/logic/quest_logic.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from typing import Dict, Union
|
||||
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .building_logic import BuildingLogicMixin
|
||||
from .combat_logic import CombatLogicMixin
|
||||
from .cooking_logic import CookingLogicMixin
|
||||
from .fishing_logic import FishingLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .mine_logic import MineLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .relationship_logic import RelationshipLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from .wallet_logic import WalletLogicMixin
|
||||
from ..stardew_rule import StardewRule, Has, True_
|
||||
from ..strings.artisan_good_names import ArtisanGood
|
||||
from ..strings.building_names import Building
|
||||
from ..strings.craftable_names import Craftable
|
||||
from ..strings.crop_names import Fruit, Vegetable
|
||||
from ..strings.fish_names import Fish
|
||||
from ..strings.food_names import Meal
|
||||
from ..strings.forageable_names import Forageable
|
||||
from ..strings.machine_names import Machine
|
||||
from ..strings.material_names import Material
|
||||
from ..strings.metal_names import MetalBar, Ore, Mineral
|
||||
from ..strings.monster_drop_names import Loot
|
||||
from ..strings.quest_names import Quest
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.season_names import Season
|
||||
from ..strings.tool_names import Tool
|
||||
from ..strings.villager_names import NPC
|
||||
from ..strings.wallet_item_names import Wallet
|
||||
|
||||
|
||||
class QuestLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.quest = QuestLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class QuestLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, MoneyLogicMixin, MineLogicMixin, RegionLogicMixin, RelationshipLogicMixin, ToolLogicMixin,
|
||||
FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillLogicMixin, WalletLogicMixin, QuestLogicMixin, BuildingLogicMixin, TimeLogicMixin]]):
|
||||
|
||||
def initialize_rules(self):
|
||||
self.update_rules({
|
||||
Quest.introductions: True_(),
|
||||
Quest.how_to_win_friends: self.logic.quest.can_complete_quest(Quest.introductions),
|
||||
Quest.getting_started: self.logic.has(Vegetable.parsnip),
|
||||
Quest.to_the_beach: self.logic.region.can_reach(Region.beach),
|
||||
Quest.raising_animals: self.logic.quest.can_complete_quest(Quest.getting_started) & self.logic.building.has_building(Building.coop),
|
||||
Quest.advancement: self.logic.quest.can_complete_quest(Quest.getting_started) & self.logic.has(Craftable.scarecrow),
|
||||
Quest.archaeology: self.logic.tool.has_tool(Tool.hoe) | self.logic.mine.can_mine_in_the_mines_floor_1_40() | self.logic.skill.can_fish(),
|
||||
Quest.rat_problem: self.logic.region.can_reach_all((Region.town, Region.community_center)),
|
||||
Quest.meet_the_wizard: self.logic.quest.can_complete_quest(Quest.rat_problem),
|
||||
Quest.forging_ahead: self.logic.has(Ore.copper) & self.logic.has(Machine.furnace),
|
||||
Quest.smelting: self.logic.has(MetalBar.copper),
|
||||
Quest.initiation: self.logic.mine.can_mine_in_the_mines_floor_1_40(),
|
||||
Quest.robins_lost_axe: self.logic.season.has(Season.spring) & self.logic.relationship.can_meet(NPC.robin),
|
||||
Quest.jodis_request: self.logic.season.has(Season.spring) & self.logic.has(Vegetable.cauliflower) & self.logic.relationship.can_meet(NPC.jodi),
|
||||
Quest.mayors_shorts: self.logic.season.has(Season.summer) & self.logic.relationship.has_hearts(NPC.marnie, 2) &
|
||||
self.logic.relationship.can_meet(NPC.lewis),
|
||||
Quest.blackberry_basket: self.logic.season.has(Season.fall) & self.logic.relationship.can_meet(NPC.linus),
|
||||
Quest.marnies_request: self.logic.relationship.has_hearts(NPC.marnie, 3) & self.logic.has(Forageable.cave_carrot),
|
||||
Quest.pam_is_thirsty: self.logic.season.has(Season.summer) & self.logic.has(ArtisanGood.pale_ale) & self.logic.relationship.can_meet(NPC.pam),
|
||||
Quest.a_dark_reagent: self.logic.season.has(Season.winter) & self.logic.has(Loot.void_essence) & self.logic.relationship.can_meet(NPC.wizard),
|
||||
Quest.cows_delight: self.logic.season.has(Season.fall) & self.logic.has(Vegetable.amaranth) & self.logic.relationship.can_meet(NPC.marnie),
|
||||
Quest.the_skull_key: self.logic.received(Wallet.skull_key),
|
||||
Quest.crop_research: self.logic.season.has(Season.summer) & self.logic.has(Fruit.melon) & self.logic.relationship.can_meet(NPC.demetrius),
|
||||
Quest.knee_therapy: self.logic.season.has(Season.summer) & self.logic.has(Fruit.hot_pepper) & self.logic.relationship.can_meet(NPC.george),
|
||||
Quest.robins_request: self.logic.season.has(Season.winter) & self.logic.has(Material.hardwood) & self.logic.relationship.can_meet(NPC.robin),
|
||||
Quest.qis_challenge: True_(), # The skull cavern floor 25 already has rules
|
||||
Quest.the_mysterious_qi: (self.logic.region.can_reach_all((Region.bus_tunnel, Region.railroad, Region.mayor_house)) &
|
||||
self.logic.has_all(ArtisanGood.battery_pack, Forageable.rainbow_shell, Vegetable.beet, Loot.solar_essence)),
|
||||
Quest.carving_pumpkins: self.logic.season.has(Season.fall) & self.logic.has(Vegetable.pumpkin) & self.logic.relationship.can_meet(NPC.caroline),
|
||||
Quest.a_winter_mystery: self.logic.season.has(Season.winter),
|
||||
Quest.strange_note: self.logic.has(Forageable.secret_note) & self.logic.has(ArtisanGood.maple_syrup),
|
||||
Quest.cryptic_note: self.logic.has(Forageable.secret_note),
|
||||
Quest.fresh_fruit: self.logic.season.has(Season.spring) & self.logic.has(Fruit.apricot) & self.logic.relationship.can_meet(NPC.emily),
|
||||
Quest.aquatic_research: self.logic.season.has(Season.summer) & self.logic.has(Fish.pufferfish) & self.logic.relationship.can_meet(NPC.demetrius),
|
||||
Quest.a_soldiers_star: (self.logic.season.has(Season.summer) & self.logic.time.has_year_two & self.logic.has(Fruit.starfruit) &
|
||||
self.logic.relationship.can_meet(NPC.kent)),
|
||||
Quest.mayors_need: self.logic.season.has(Season.summer) & self.logic.has(ArtisanGood.truffle_oil) & self.logic.relationship.can_meet(NPC.lewis),
|
||||
Quest.wanted_lobster: (self.logic.season.has(Season.fall) & self.logic.season.has(Season.fall) & self.logic.has(Fish.lobster) &
|
||||
self.logic.relationship.can_meet(NPC.gus)),
|
||||
Quest.pam_needs_juice: self.logic.season.has(Season.fall) & self.logic.has(ArtisanGood.battery_pack) & self.logic.relationship.can_meet(NPC.pam),
|
||||
Quest.fish_casserole: self.logic.relationship.has_hearts(NPC.jodi, 4) & self.logic.has(Fish.largemouth_bass),
|
||||
Quest.catch_a_squid: self.logic.season.has(Season.winter) & self.logic.has(Fish.squid) & self.logic.relationship.can_meet(NPC.willy),
|
||||
Quest.fish_stew: self.logic.season.has(Season.winter) & self.logic.has(Fish.albacore) & self.logic.relationship.can_meet(NPC.gus),
|
||||
Quest.pierres_notice: self.logic.season.has(Season.spring) & self.logic.has(Meal.sashimi) & self.logic.relationship.can_meet(NPC.pierre),
|
||||
Quest.clints_attempt: self.logic.season.has(Season.winter) & self.logic.has(Mineral.amethyst) & self.logic.relationship.can_meet(NPC.emily),
|
||||
Quest.a_favor_for_clint: self.logic.season.has(Season.winter) & self.logic.has(MetalBar.iron) & self.logic.relationship.can_meet(NPC.clint),
|
||||
Quest.staff_of_power: self.logic.season.has(Season.winter) & self.logic.has(MetalBar.iridium) & self.logic.relationship.can_meet(NPC.wizard),
|
||||
Quest.grannys_gift: self.logic.season.has(Season.spring) & self.logic.has(Forageable.leek) & self.logic.relationship.can_meet(NPC.evelyn),
|
||||
Quest.exotic_spirits: self.logic.season.has(Season.winter) & self.logic.has(Forageable.coconut) & self.logic.relationship.can_meet(NPC.gus),
|
||||
Quest.catch_a_lingcod: self.logic.season.has(Season.winter) & self.logic.has(Fish.lingcod) & self.logic.relationship.can_meet(NPC.willy),
|
||||
Quest.dark_talisman: self.logic.region.can_reach(Region.railroad) & self.logic.wallet.has_rusty_key() & self.logic.relationship.can_meet(
|
||||
NPC.krobus),
|
||||
Quest.goblin_problem: self.logic.region.can_reach(Region.witch_swamp),
|
||||
Quest.magic_ink: self.logic.relationship.can_meet(NPC.wizard),
|
||||
Quest.the_pirates_wife: self.logic.relationship.can_meet(NPC.kent) & self.logic.relationship.can_meet(NPC.gus) &
|
||||
self.logic.relationship.can_meet(NPC.sandy) & self.logic.relationship.can_meet(NPC.george) &
|
||||
self.logic.relationship.can_meet(NPC.wizard) & self.logic.relationship.can_meet(NPC.willy),
|
||||
})
|
||||
|
||||
def update_rules(self, new_rules: Dict[str, StardewRule]):
|
||||
self.registry.quest_rules.update(new_rules)
|
||||
|
||||
def can_complete_quest(self, quest: str) -> StardewRule:
|
||||
return Has(quest, self.registry.quest_rules)
|
||||
|
||||
def has_club_card(self) -> StardewRule:
|
||||
if self.options.quest_locations < 0:
|
||||
return self.logic.quest.can_complete_quest(Quest.the_mysterious_qi)
|
||||
return self.logic.received(Wallet.club_card)
|
||||
|
||||
def has_magnifying_glass(self) -> StardewRule:
|
||||
if self.options.quest_locations < 0:
|
||||
return self.logic.quest.can_complete_quest(Quest.a_winter_mystery)
|
||||
return self.logic.received(Wallet.magnifying_glass)
|
||||
|
||||
def has_dark_talisman(self) -> StardewRule:
|
||||
if self.options.quest_locations < 0:
|
||||
return self.logic.quest.can_complete_quest(Quest.dark_talisman)
|
||||
return self.logic.received(Wallet.dark_talisman)
|
||||
35
worlds/stardew_valley/logic/received_logic.py
Normal file
35
worlds/stardew_valley/logic/received_logic.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from typing import Optional
|
||||
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from ..stardew_rule import StardewRule, Received, And, Or, TotalReceived
|
||||
|
||||
|
||||
class ReceivedLogicMixin(BaseLogic[HasLogicMixin], BaseLogicMixin):
|
||||
# Should be cached
|
||||
def received(self, item: str, count: Optional[int] = 1) -> StardewRule:
|
||||
assert count >= 0, "Can't receive a negative amount of item."
|
||||
|
||||
return Received(item, self.player, count)
|
||||
|
||||
def received_all(self, *items: str):
|
||||
assert items, "Can't receive all of no items."
|
||||
|
||||
return And(*(self.received(item) for item in items))
|
||||
|
||||
def received_any(self, *items: str):
|
||||
assert items, "Can't receive any of no items."
|
||||
|
||||
return Or(*(self.received(item) for item in items))
|
||||
|
||||
def received_once(self, *items: str, count: int):
|
||||
assert items, "Can't receive once of no items."
|
||||
assert count >= 0, "Can't receive a negative amount of item."
|
||||
|
||||
return self.logic.count(count, *(self.received(item) for item in items))
|
||||
|
||||
def received_n(self, *items: str, count: int):
|
||||
assert items, "Can't receive n of no items."
|
||||
assert count >= 0, "Can't receive a negative amount of item."
|
||||
|
||||
return TotalReceived(count, items, self.player)
|
||||
65
worlds/stardew_valley/logic/region_logic.py
Normal file
65
worlds/stardew_valley/logic/region_logic.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from typing import Tuple, Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from ..options import EntranceRandomization
|
||||
from ..stardew_rule import StardewRule, And, Or, Reach, false_, true_
|
||||
from ..strings.region_names import Region
|
||||
|
||||
main_outside_area = {Region.menu, Region.stardew_valley, Region.farm_house, Region.farm, Region.town, Region.beach, Region.mountain, Region.forest,
|
||||
Region.bus_stop, Region.backwoods, Region.bus_tunnel, Region.tunnel_entrance}
|
||||
always_accessible_regions_without_er = {*main_outside_area, Region.community_center, Region.pantry, Region.crafts_room, Region.fish_tank, Region.boiler_room,
|
||||
Region.vault, Region.bulletin_board, Region.mines, Region.hospital, Region.carpenter, Region.alex_house,
|
||||
Region.elliott_house, Region.ranch, Region.farm_cave, Region.wizard_tower, Region.tent, Region.pierre_store,
|
||||
Region.saloon, Region.blacksmith, Region.trailer, Region.museum, Region.mayor_house, Region.haley_house,
|
||||
Region.sam_house, Region.jojamart, Region.fish_shop}
|
||||
|
||||
always_regions_by_setting = {EntranceRandomization.option_disabled: always_accessible_regions_without_er,
|
||||
EntranceRandomization.option_pelican_town: always_accessible_regions_without_er,
|
||||
EntranceRandomization.option_non_progression: always_accessible_regions_without_er,
|
||||
EntranceRandomization.option_buildings: main_outside_area,
|
||||
EntranceRandomization.option_chaos: always_accessible_regions_without_er}
|
||||
|
||||
|
||||
class RegionLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.region = RegionLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class RegionLogic(BaseLogic[Union[RegionLogicMixin, HasLogicMixin]]):
|
||||
|
||||
@cache_self1
|
||||
def can_reach(self, region_name: str) -> StardewRule:
|
||||
if region_name in always_regions_by_setting[self.options.entrance_randomization]:
|
||||
return true_
|
||||
|
||||
if region_name not in self.regions:
|
||||
return false_
|
||||
|
||||
return Reach(region_name, "Region", self.player)
|
||||
|
||||
@cache_self1
|
||||
def can_reach_any(self, region_names: Tuple[str, ...]) -> StardewRule:
|
||||
return Or(*(self.logic.region.can_reach(spot) for spot in region_names))
|
||||
|
||||
@cache_self1
|
||||
def can_reach_all(self, region_names: Tuple[str, ...]) -> StardewRule:
|
||||
return And(*(self.logic.region.can_reach(spot) for spot in region_names))
|
||||
|
||||
@cache_self1
|
||||
def can_reach_all_except_one(self, region_names: Tuple[str, ...]) -> StardewRule:
|
||||
region_names = list(region_names)
|
||||
num_required = len(region_names) - 1
|
||||
if num_required <= 0:
|
||||
num_required = len(region_names)
|
||||
return self.logic.count(num_required, *(self.logic.region.can_reach(spot) for spot in region_names))
|
||||
|
||||
@cache_self1
|
||||
def can_reach_location(self, location_name: str) -> StardewRule:
|
||||
return Reach(location_name, "Location", self.player)
|
||||
|
||||
# @cache_self1
|
||||
# def can_reach_entrance(self, entrance_name: str) -> StardewRule:
|
||||
# return Reach(entrance_name, "Entrance", self.player)
|
||||
185
worlds/stardew_valley/logic/relationship_logic.py
Normal file
185
worlds/stardew_valley/logic/relationship_logic.py
Normal file
@@ -0,0 +1,185 @@
|
||||
import math
|
||||
from functools import cached_property
|
||||
from typing import Union, List
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .building_logic import BuildingLogicMixin
|
||||
from .gift_logic import GiftLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from ..data.villagers_data import all_villagers_by_name, Villager, get_villagers_for_mods
|
||||
from ..options import Friendsanity
|
||||
from ..stardew_rule import StardewRule, True_, And, Or
|
||||
from ..strings.ap_names.mods.mod_items import SVEQuestItem
|
||||
from ..strings.crop_names import Fruit
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.gift_names import Gift
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.season_names import Season
|
||||
from ..strings.villager_names import NPC, ModNPC
|
||||
|
||||
possible_kids = ("Cute Baby", "Ugly Baby")
|
||||
|
||||
|
||||
def heart_item_name(npc: Union[str, Villager]) -> str:
|
||||
if isinstance(npc, Villager):
|
||||
npc = npc.name
|
||||
|
||||
return f"{npc} <3"
|
||||
|
||||
|
||||
class RelationshipLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.relationship = RelationshipLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class RelationshipLogic(BaseLogic[Union[
|
||||
RelationshipLogicMixin, BuildingLogicMixin, SeasonLogicMixin, TimeLogicMixin, GiftLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]):
|
||||
|
||||
@cached_property
|
||||
def all_villagers_given_mods(self) -> List[Villager]:
|
||||
return get_villagers_for_mods(self.options.mods.value)
|
||||
|
||||
def can_date(self, npc: str) -> StardewRule:
|
||||
return self.logic.relationship.has_hearts(npc, 8) & self.logic.has(Gift.bouquet)
|
||||
|
||||
def can_marry(self, npc: str) -> StardewRule:
|
||||
return self.logic.relationship.has_hearts(npc, 10) & self.logic.has(Gift.mermaid_pendant)
|
||||
|
||||
def can_get_married(self) -> StardewRule:
|
||||
return self.logic.relationship.has_hearts(Generic.bachelor, 10) & self.logic.has(Gift.mermaid_pendant)
|
||||
|
||||
def has_children(self, number_children: int) -> StardewRule:
|
||||
if number_children <= 0:
|
||||
return True_()
|
||||
if self.options.friendsanity == Friendsanity.option_none:
|
||||
return self.logic.relationship.can_reproduce(number_children)
|
||||
return self.logic.received_n(*possible_kids, count=number_children) & self.logic.building.has_house(2)
|
||||
|
||||
def can_reproduce(self, number_children: int = 1) -> StardewRule:
|
||||
if number_children <= 0:
|
||||
return True_()
|
||||
baby_rules = [self.logic.relationship.can_get_married(), self.logic.building.has_house(2), self.logic.relationship.has_hearts(Generic.bachelor, 12),
|
||||
self.logic.relationship.has_children(number_children - 1)]
|
||||
return And(*baby_rules)
|
||||
|
||||
# Should be cached
|
||||
def has_hearts(self, npc: str, hearts: int = 1) -> StardewRule:
|
||||
if hearts <= 0:
|
||||
return True_()
|
||||
if self.options.friendsanity == Friendsanity.option_none:
|
||||
return self.logic.relationship.can_earn_relationship(npc, hearts)
|
||||
if npc not in all_villagers_by_name:
|
||||
if npc == Generic.any or npc == Generic.bachelor:
|
||||
possible_friends = []
|
||||
for name in all_villagers_by_name:
|
||||
if not self.npc_is_in_current_slot(name):
|
||||
continue
|
||||
if npc == Generic.any or all_villagers_by_name[name].bachelor:
|
||||
possible_friends.append(self.logic.relationship.has_hearts(name, hearts))
|
||||
return Or(*possible_friends)
|
||||
if npc == Generic.all:
|
||||
mandatory_friends = []
|
||||
for name in all_villagers_by_name:
|
||||
if not self.npc_is_in_current_slot(name):
|
||||
continue
|
||||
mandatory_friends.append(self.logic.relationship.has_hearts(name, hearts))
|
||||
return And(*mandatory_friends)
|
||||
if npc.isnumeric():
|
||||
possible_friends = []
|
||||
for name in all_villagers_by_name:
|
||||
if not self.npc_is_in_current_slot(name):
|
||||
continue
|
||||
possible_friends.append(self.logic.relationship.has_hearts(name, hearts))
|
||||
return self.logic.count(int(npc), *possible_friends)
|
||||
return self.can_earn_relationship(npc, hearts)
|
||||
|
||||
if not self.npc_is_in_current_slot(npc):
|
||||
return True_()
|
||||
villager = all_villagers_by_name[npc]
|
||||
if self.options.friendsanity == Friendsanity.option_bachelors and not villager.bachelor:
|
||||
return self.logic.relationship.can_earn_relationship(npc, hearts)
|
||||
if self.options.friendsanity == Friendsanity.option_starting_npcs and not villager.available:
|
||||
return self.logic.relationship.can_earn_relationship(npc, hearts)
|
||||
is_capped_at_8 = villager.bachelor and self.options.friendsanity != Friendsanity.option_all_with_marriage
|
||||
if is_capped_at_8 and hearts > 8:
|
||||
return self.logic.relationship.received_hearts(villager.name, 8) & self.logic.relationship.can_earn_relationship(npc, hearts)
|
||||
return self.logic.relationship.received_hearts(villager.name, hearts)
|
||||
|
||||
# Should be cached
|
||||
def received_hearts(self, npc: str, hearts: int) -> StardewRule:
|
||||
heart_item = heart_item_name(npc)
|
||||
number_required = math.ceil(hearts / self.options.friendsanity_heart_size)
|
||||
return self.logic.received(heart_item, number_required)
|
||||
|
||||
@cache_self1
|
||||
def can_meet(self, npc: str) -> StardewRule:
|
||||
if npc not in all_villagers_by_name or not self.npc_is_in_current_slot(npc):
|
||||
return True_()
|
||||
villager = all_villagers_by_name[npc]
|
||||
rules = [self.logic.region.can_reach_any(villager.locations)]
|
||||
if npc == NPC.kent:
|
||||
rules.append(self.logic.time.has_year_two)
|
||||
elif npc == NPC.leo:
|
||||
rules.append(self.logic.received("Island West Turtle"))
|
||||
elif npc == ModNPC.lance:
|
||||
rules.append(self.logic.region.can_reach(Region.volcano_floor_10))
|
||||
elif npc == ModNPC.apples:
|
||||
rules.append(self.logic.has(Fruit.starfruit))
|
||||
elif npc == ModNPC.scarlett:
|
||||
scarlett_job = self.logic.received(SVEQuestItem.scarlett_job_offer)
|
||||
scarlett_spring = self.logic.season.has(Season.spring) & self.can_meet(ModNPC.andy)
|
||||
scarlett_summer = self.logic.season.has(Season.summer) & self.can_meet(ModNPC.susan)
|
||||
scarlett_fall = self.logic.season.has(Season.fall) & self.can_meet(ModNPC.sophia)
|
||||
rules.append(scarlett_job & (scarlett_spring | scarlett_summer | scarlett_fall))
|
||||
elif npc == ModNPC.morgan:
|
||||
rules.append(self.logic.received(SVEQuestItem.morgan_schooling))
|
||||
elif npc == ModNPC.goblin:
|
||||
rules.append(self.logic.region.can_reach_all((Region.witch_hut, Region.wizard_tower)))
|
||||
|
||||
return And(*rules)
|
||||
|
||||
def can_give_loved_gifts_to_everyone(self) -> StardewRule:
|
||||
rules = []
|
||||
for npc in all_villagers_by_name:
|
||||
if not self.npc_is_in_current_slot(npc):
|
||||
continue
|
||||
meet_rule = self.logic.relationship.can_meet(npc)
|
||||
rules.append(meet_rule)
|
||||
rules.append(self.logic.gifts.has_any_universal_love)
|
||||
return And(*rules)
|
||||
|
||||
# Should be cached
|
||||
def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule:
|
||||
if hearts <= 0:
|
||||
return True_()
|
||||
|
||||
previous_heart = hearts - self.options.friendsanity_heart_size
|
||||
previous_heart_rule = self.logic.relationship.has_hearts(npc, previous_heart)
|
||||
|
||||
if npc not in all_villagers_by_name or not self.npc_is_in_current_slot(npc):
|
||||
return previous_heart_rule
|
||||
|
||||
rules = [previous_heart_rule, self.logic.relationship.can_meet(npc)]
|
||||
villager = all_villagers_by_name[npc]
|
||||
if hearts > 2 or hearts > self.options.friendsanity_heart_size:
|
||||
rules.append(self.logic.season.has(villager.birthday))
|
||||
if villager.birthday == Generic.any:
|
||||
rules.append(self.logic.season.has_all() | self.logic.time.has_year_three) # push logic back for any birthday-less villager
|
||||
if villager.bachelor:
|
||||
if hearts > 8:
|
||||
rules.append(self.logic.relationship.can_date(npc))
|
||||
if hearts > 10:
|
||||
rules.append(self.logic.relationship.can_marry(npc))
|
||||
|
||||
return And(*rules)
|
||||
|
||||
@cache_self1
|
||||
def npc_is_in_current_slot(self, name: str) -> bool:
|
||||
npc = all_villagers_by_name[name]
|
||||
return npc in self.all_villagers_given_mods
|
||||
44
worlds/stardew_valley/logic/season_logic.py
Normal file
44
worlds/stardew_valley/logic/season_logic.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from typing import Iterable, Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from ..options import SeasonRandomization
|
||||
from ..stardew_rule import StardewRule, True_, Or, And
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.season_names import Season
|
||||
|
||||
|
||||
class SeasonLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.season = SeasonLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class SeasonLogic(BaseLogic[Union[SeasonLogicMixin, TimeLogicMixin, ReceivedLogicMixin]]):
|
||||
|
||||
@cache_self1
|
||||
def has(self, season: str) -> StardewRule:
|
||||
if season == Generic.any:
|
||||
return True_()
|
||||
seasons_order = [Season.spring, Season.summer, Season.fall, Season.winter]
|
||||
if self.options.season_randomization == SeasonRandomization.option_progressive:
|
||||
return self.logic.received(Season.progressive, seasons_order.index(season))
|
||||
if self.options.season_randomization == SeasonRandomization.option_disabled:
|
||||
if season == Season.spring:
|
||||
return True_()
|
||||
return self.logic.time.has_lived_months(1)
|
||||
return self.logic.received(season)
|
||||
|
||||
def has_any(self, seasons: Iterable[str]):
|
||||
if not seasons:
|
||||
return True_()
|
||||
return Or(*(self.logic.season.has(season) for season in seasons))
|
||||
|
||||
def has_any_not_winter(self):
|
||||
return self.logic.season.has_any([Season.spring, Season.summer, Season.fall])
|
||||
|
||||
def has_all(self):
|
||||
seasons = [Season.spring, Season.summer, Season.fall, Season.winter]
|
||||
return And(*(self.logic.season.has(season) for season in seasons))
|
||||
60
worlds/stardew_valley/logic/shipping_logic.py
Normal file
60
worlds/stardew_valley/logic/shipping_logic.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from functools import cached_property
|
||||
from typing import Union, List
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .building_logic import BuildingLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from ..locations import LocationTags, locations_by_tag
|
||||
from ..options import ExcludeGingerIsland, Shipsanity
|
||||
from ..options import SpecialOrderLocations
|
||||
from ..stardew_rule import StardewRule, And
|
||||
from ..strings.ap_names.event_names import Event
|
||||
from ..strings.building_names import Building
|
||||
|
||||
|
||||
class ShippingLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.shipping = ShippingLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, BuildingLogicMixin, RegionLogicMixin, HasLogicMixin]]):
|
||||
|
||||
@cached_property
|
||||
def can_use_shipping_bin(self) -> StardewRule:
|
||||
return self.logic.building.has_building(Building.shipping_bin)
|
||||
|
||||
@cache_self1
|
||||
def can_ship(self, item: str) -> StardewRule:
|
||||
return self.logic.received(Event.can_ship_items) & self.logic.has(item)
|
||||
|
||||
def can_ship_everything(self) -> StardewRule:
|
||||
shipsanity_prefix = "Shipsanity: "
|
||||
all_items_to_ship = []
|
||||
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||
exclude_qi = self.options.special_order_locations != SpecialOrderLocations.option_board_qi
|
||||
mod_list = self.options.mods.value
|
||||
for location in locations_by_tag[LocationTags.SHIPSANITY_FULL_SHIPMENT]:
|
||||
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
||||
continue
|
||||
if exclude_qi and LocationTags.REQUIRES_QI_ORDERS in location.tags:
|
||||
continue
|
||||
if location.mod_name and location.mod_name not in mod_list:
|
||||
continue
|
||||
all_items_to_ship.append(location.name[len(shipsanity_prefix):])
|
||||
return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship)
|
||||
|
||||
def can_ship_everything_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
|
||||
if self.options.shipsanity == Shipsanity.option_none:
|
||||
return self.can_ship_everything()
|
||||
|
||||
rules = [self.logic.building.has_building(Building.shipping_bin)]
|
||||
|
||||
for shipsanity_location in locations_by_tag[LocationTags.SHIPSANITY]:
|
||||
if shipsanity_location.name not in all_location_names_in_slot:
|
||||
continue
|
||||
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
|
||||
return And(*rules)
|
||||
172
worlds/stardew_valley/logic/skill_logic.py
Normal file
172
worlds/stardew_valley/logic/skill_logic.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from functools import cached_property
|
||||
from typing import Union, Tuple
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .combat_logic import CombatLogicMixin
|
||||
from .crop_logic import CropLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from .. import options
|
||||
from ..data import all_crops
|
||||
from ..mods.logic.magic_logic import MagicLogicMixin
|
||||
from ..mods.logic.mod_skills_levels import get_mod_skill_levels
|
||||
from ..stardew_rule import StardewRule, True_, Or, False_
|
||||
from ..strings.craftable_names import Fishing
|
||||
from ..strings.machine_names import Machine
|
||||
from ..strings.performance_names import Performance
|
||||
from ..strings.quality_names import ForageQuality
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.skill_names import Skill, all_mod_skills
|
||||
from ..strings.tool_names import ToolMaterial, Tool
|
||||
|
||||
fishing_regions = (Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west)
|
||||
|
||||
|
||||
class SkillLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.skill = SkillLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class SkillLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, ToolLogicMixin, SkillLogicMixin,
|
||||
CombatLogicMixin, CropLogicMixin, MagicLogicMixin]]):
|
||||
# Should be cached
|
||||
def can_earn_level(self, skill: str, level: int) -> StardewRule:
|
||||
if level <= 0:
|
||||
return True_()
|
||||
|
||||
tool_level = (level - 1) // 2
|
||||
tool_material = ToolMaterial.tiers[tool_level]
|
||||
months = max(1, level - 1)
|
||||
months_rule = self.logic.time.has_lived_months(months)
|
||||
previous_level_rule = self.logic.skill.has_level(skill, level - 1)
|
||||
|
||||
if skill == Skill.fishing:
|
||||
xp_rule = self.logic.tool.has_tool(Tool.fishing_rod, ToolMaterial.tiers[max(tool_level, 3)])
|
||||
elif skill == Skill.farming:
|
||||
xp_rule = self.logic.tool.has_tool(Tool.hoe, tool_material) & self.logic.tool.can_water(tool_level)
|
||||
elif skill == Skill.foraging:
|
||||
xp_rule = self.logic.tool.has_tool(Tool.axe, tool_material) | self.logic.magic.can_use_clear_debris_instead_of_tool_level(tool_level)
|
||||
elif skill == Skill.mining:
|
||||
xp_rule = self.logic.tool.has_tool(Tool.pickaxe, tool_material) | \
|
||||
self.logic.magic.can_use_clear_debris_instead_of_tool_level(tool_level)
|
||||
xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5)
|
||||
elif skill == Skill.combat:
|
||||
combat_tier = Performance.tiers[tool_level]
|
||||
xp_rule = self.logic.combat.can_fight_at_level(combat_tier)
|
||||
xp_rule = xp_rule & self.logic.region.can_reach(Region.mines_floor_5)
|
||||
elif skill in all_mod_skills:
|
||||
# Ideal solution would be to add a logic registry, but I'm too lazy.
|
||||
return self.logic.mod.skill.can_earn_mod_skill_level(skill, level)
|
||||
else:
|
||||
raise Exception(f"Unknown skill: {skill}")
|
||||
|
||||
return previous_level_rule & months_rule & xp_rule
|
||||
|
||||
# Should be cached
|
||||
def has_level(self, skill: str, level: int) -> StardewRule:
|
||||
if level <= 0:
|
||||
return True_()
|
||||
|
||||
if self.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
return self.logic.received(f"{skill} Level", level)
|
||||
|
||||
return self.logic.skill.can_earn_level(skill, level)
|
||||
|
||||
@cache_self1
|
||||
def has_farming_level(self, level: int) -> StardewRule:
|
||||
return self.logic.skill.has_level(Skill.farming, level)
|
||||
|
||||
# Should be cached
|
||||
def has_total_level(self, level: int, allow_modded_skills: bool = False) -> StardewRule:
|
||||
if level <= 0:
|
||||
return True_()
|
||||
|
||||
if self.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
skills_items = ("Farming Level", "Mining Level", "Foraging Level", "Fishing Level", "Combat Level")
|
||||
if allow_modded_skills:
|
||||
skills_items += get_mod_skill_levels(self.options.mods)
|
||||
return self.logic.received_n(*skills_items, count=level)
|
||||
|
||||
months_with_4_skills = max(1, (level // 4) - 1)
|
||||
months_with_5_skills = max(1, (level // 5) - 1)
|
||||
rule_with_fishing = self.logic.time.has_lived_months(months_with_5_skills) & self.logic.skill.can_get_fishing_xp
|
||||
if level > 40:
|
||||
return rule_with_fishing
|
||||
return self.logic.time.has_lived_months(months_with_4_skills) | rule_with_fishing
|
||||
|
||||
@cached_property
|
||||
def can_get_farming_xp(self) -> StardewRule:
|
||||
crop_rules = []
|
||||
for crop in all_crops:
|
||||
crop_rules.append(self.logic.crop.can_grow(crop))
|
||||
return Or(*crop_rules)
|
||||
|
||||
@cached_property
|
||||
def can_get_foraging_xp(self) -> StardewRule:
|
||||
tool_rule = self.logic.tool.has_tool(Tool.axe)
|
||||
tree_rule = self.logic.region.can_reach(Region.forest) & self.logic.season.has_any_not_winter()
|
||||
stump_rule = self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.axe, ToolMaterial.copper)
|
||||
return tool_rule & (tree_rule | stump_rule)
|
||||
|
||||
@cached_property
|
||||
def can_get_mining_xp(self) -> StardewRule:
|
||||
tool_rule = self.logic.tool.has_tool(Tool.pickaxe)
|
||||
stone_rule = self.logic.region.can_reach_any((Region.mines_floor_5, Region.quarry, Region.skull_cavern_25, Region.volcano_floor_5))
|
||||
return tool_rule & stone_rule
|
||||
|
||||
@cached_property
|
||||
def can_get_combat_xp(self) -> StardewRule:
|
||||
tool_rule = self.logic.combat.has_any_weapon()
|
||||
enemy_rule = self.logic.region.can_reach_any((Region.mines_floor_5, Region.skull_cavern_25, Region.volcano_floor_5))
|
||||
return tool_rule & enemy_rule
|
||||
|
||||
@cached_property
|
||||
def can_get_fishing_xp(self) -> StardewRule:
|
||||
if self.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
return self.logic.skill.can_fish() | self.logic.skill.can_crab_pot
|
||||
|
||||
return self.logic.skill.can_fish()
|
||||
|
||||
# Should be cached
|
||||
def can_fish(self, regions: Union[str, Tuple[str, ...]] = None, difficulty: int = 0) -> StardewRule:
|
||||
if isinstance(regions, str):
|
||||
regions = regions,
|
||||
if regions is None or len(regions) == 0:
|
||||
regions = fishing_regions
|
||||
skill_required = min(10, max(0, int((difficulty / 10) - 1)))
|
||||
if difficulty <= 40:
|
||||
skill_required = 0
|
||||
skill_rule = self.logic.skill.has_level(Skill.fishing, skill_required)
|
||||
region_rule = self.logic.region.can_reach_any(regions)
|
||||
number_fishing_rod_required = 1 if difficulty < 50 else (2 if difficulty < 80 else 4)
|
||||
return self.logic.tool.has_fishing_rod(number_fishing_rod_required) & skill_rule & region_rule
|
||||
|
||||
@cache_self1
|
||||
def can_crab_pot_at(self, region: str) -> StardewRule:
|
||||
return self.logic.skill.can_crab_pot & self.logic.region.can_reach(region)
|
||||
|
||||
@cached_property
|
||||
def can_crab_pot(self) -> StardewRule:
|
||||
crab_pot_rule = self.logic.has(Fishing.bait)
|
||||
if self.options.skill_progression == options.SkillProgression.option_progressive:
|
||||
crab_pot_rule = crab_pot_rule & self.logic.has(Machine.crab_pot)
|
||||
else:
|
||||
crab_pot_rule = crab_pot_rule & self.logic.skill.can_get_fishing_xp
|
||||
|
||||
water_region_rules = self.logic.region.can_reach_any(fishing_regions)
|
||||
return crab_pot_rule & water_region_rules
|
||||
|
||||
def can_forage_quality(self, quality: str) -> StardewRule:
|
||||
if quality == ForageQuality.basic:
|
||||
return True_()
|
||||
if quality == ForageQuality.silver:
|
||||
return self.has_level(Skill.foraging, 5)
|
||||
if quality == ForageQuality.gold:
|
||||
return self.has_level(Skill.foraging, 9)
|
||||
return False_()
|
||||
111
worlds/stardew_valley/logic/special_order_logic.py
Normal file
111
worlds/stardew_valley/logic/special_order_logic.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from typing import Dict, Union
|
||||
|
||||
from .ability_logic import AbilityLogicMixin
|
||||
from .arcade_logic import ArcadeLogicMixin
|
||||
from .artisan_logic import ArtisanLogicMixin
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .buff_logic import BuffLogicMixin
|
||||
from .cooking_logic import CookingLogicMixin
|
||||
from .has_logic import HasLogicMixin
|
||||
from .mine_logic import MineLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
from .monster_logic import MonsterLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .relationship_logic import RelationshipLogicMixin
|
||||
from .season_logic import SeasonLogicMixin
|
||||
from .shipping_logic import ShippingLogicMixin
|
||||
from .skill_logic import SkillLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from ..stardew_rule import StardewRule, Has
|
||||
from ..strings.animal_product_names import AnimalProduct
|
||||
from ..strings.ap_names.event_names import Event
|
||||
from ..strings.ap_names.transport_names import Transportation
|
||||
from ..strings.artisan_good_names import ArtisanGood
|
||||
from ..strings.crop_names import Vegetable, Fruit
|
||||
from ..strings.fertilizer_names import Fertilizer
|
||||
from ..strings.fish_names import Fish
|
||||
from ..strings.forageable_names import Forageable
|
||||
from ..strings.machine_names import Machine
|
||||
from ..strings.material_names import Material
|
||||
from ..strings.metal_names import Mineral
|
||||
from ..strings.monster_drop_names import Loot
|
||||
from ..strings.monster_names import Monster
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.season_names import Season
|
||||
from ..strings.special_order_names import SpecialOrder
|
||||
from ..strings.tool_names import Tool
|
||||
from ..strings.villager_names import NPC
|
||||
|
||||
|
||||
class SpecialOrderLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.special_order = SpecialOrderLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class SpecialOrderLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, MoneyLogicMixin,
|
||||
ShippingLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, RelationshipLogicMixin, ToolLogicMixin, SkillLogicMixin,
|
||||
MineLogicMixin, CookingLogicMixin, BuffLogicMixin,
|
||||
AbilityLogicMixin, SpecialOrderLogicMixin, MonsterLogicMixin]]):
|
||||
|
||||
def initialize_rules(self):
|
||||
self.update_rules({
|
||||
SpecialOrder.island_ingredients: self.logic.relationship.can_meet(NPC.caroline) & self.logic.special_order.has_island_transport() &
|
||||
self.logic.ability.can_farm_perfectly() & self.logic.shipping.can_ship(Vegetable.taro_root) &
|
||||
self.logic.shipping.can_ship(Fruit.pineapple) & self.logic.shipping.can_ship(Forageable.ginger),
|
||||
SpecialOrder.cave_patrol: self.logic.relationship.can_meet(NPC.clint),
|
||||
SpecialOrder.aquatic_overpopulation: self.logic.relationship.can_meet(NPC.demetrius) & self.logic.ability.can_fish_perfectly(),
|
||||
SpecialOrder.biome_balance: self.logic.relationship.can_meet(NPC.demetrius) & self.logic.ability.can_fish_perfectly(),
|
||||
SpecialOrder.rock_rejuivenation: (self.logic.relationship.has_hearts(NPC.emily, 4) &
|
||||
self.logic.has_all(Mineral.ruby, Mineral.topaz, Mineral.emerald, Mineral.jade, Mineral.amethyst,
|
||||
ArtisanGood.cloth)),
|
||||
SpecialOrder.gifts_for_george: self.logic.season.has(Season.spring) & self.logic.has(Forageable.leek),
|
||||
SpecialOrder.fragments_of_the_past: self.logic.monster.can_kill(Monster.skeleton),
|
||||
SpecialOrder.gus_famous_omelet: self.logic.has(AnimalProduct.any_egg),
|
||||
SpecialOrder.crop_order: self.logic.ability.can_farm_perfectly() & self.logic.received(Event.can_ship_items),
|
||||
SpecialOrder.community_cleanup: self.logic.skill.can_crab_pot,
|
||||
SpecialOrder.the_strong_stuff: self.logic.artisan.can_keg(Vegetable.potato),
|
||||
SpecialOrder.pierres_prime_produce: self.logic.ability.can_farm_perfectly(),
|
||||
SpecialOrder.robins_project: self.logic.relationship.can_meet(NPC.robin) & self.logic.ability.can_chop_perfectly() &
|
||||
self.logic.has(Material.hardwood),
|
||||
SpecialOrder.robins_resource_rush: self.logic.relationship.can_meet(NPC.robin) & self.logic.ability.can_chop_perfectly() &
|
||||
self.logic.has(Fertilizer.tree) & self.logic.ability.can_mine_perfectly(),
|
||||
SpecialOrder.juicy_bugs_wanted: self.logic.has(Loot.bug_meat),
|
||||
SpecialOrder.tropical_fish: self.logic.relationship.can_meet(NPC.willy) & self.logic.received("Island Resort") &
|
||||
self.logic.special_order.has_island_transport() &
|
||||
self.logic.has(Fish.stingray) & self.logic.has(Fish.blue_discus) & self.logic.has(Fish.lionfish),
|
||||
SpecialOrder.a_curious_substance: self.logic.region.can_reach(Region.wizard_tower),
|
||||
SpecialOrder.prismatic_jelly: self.logic.region.can_reach(Region.wizard_tower),
|
||||
SpecialOrder.qis_crop: self.logic.ability.can_farm_perfectly() & self.logic.region.can_reach(Region.greenhouse) &
|
||||
self.logic.region.can_reach(Region.island_west) & self.logic.skill.has_total_level(50) &
|
||||
self.logic.has(Machine.seed_maker) & self.logic.received(Event.can_ship_items),
|
||||
SpecialOrder.lets_play_a_game: self.logic.arcade.has_junimo_kart_max_level(),
|
||||
SpecialOrder.four_precious_stones: self.logic.time.has_lived_max_months & self.logic.has("Prismatic Shard") &
|
||||
self.logic.ability.can_mine_perfectly_in_the_skull_cavern(),
|
||||
SpecialOrder.qis_hungry_challenge: self.logic.ability.can_mine_perfectly_in_the_skull_cavern() & self.logic.buff.has_max_buffs(),
|
||||
SpecialOrder.qis_cuisine: self.logic.cooking.can_cook() & self.logic.received(Event.can_ship_items) &
|
||||
(self.logic.money.can_spend_at(Region.saloon, 205000) | self.logic.money.can_spend_at(Region.pierre_store, 170000)),
|
||||
SpecialOrder.qis_kindness: self.logic.relationship.can_give_loved_gifts_to_everyone(),
|
||||
SpecialOrder.extended_family: self.logic.ability.can_fish_perfectly() & self.logic.has(Fish.angler) & self.logic.has(Fish.glacierfish) &
|
||||
self.logic.has(Fish.crimsonfish) & self.logic.has(Fish.mutant_carp) & self.logic.has(Fish.legend),
|
||||
SpecialOrder.danger_in_the_deep: self.logic.ability.can_mine_perfectly() & self.logic.mine.has_mine_elevator_to_floor(120),
|
||||
SpecialOrder.skull_cavern_invasion: self.logic.ability.can_mine_perfectly_in_the_skull_cavern() & self.logic.buff.has_max_buffs(),
|
||||
SpecialOrder.qis_prismatic_grange: self.logic.has(Loot.bug_meat) & # 100 Bug Meat
|
||||
self.logic.money.can_spend_at(Region.saloon, 24000) & # 100 Spaghetti
|
||||
self.logic.money.can_spend_at(Region.blacksmith, 15000) & # 100 Copper Ore
|
||||
self.logic.money.can_spend_at(Region.ranch, 5000) & # 100 Hay
|
||||
self.logic.money.can_spend_at(Region.saloon, 22000) & # 100 Salads
|
||||
self.logic.money.can_spend_at(Region.saloon, 7500) & # 100 Joja Cola
|
||||
self.logic.money.can_spend(80000), # I need this extra rule because money rules aren't additive...
|
||||
})
|
||||
|
||||
def update_rules(self, new_rules: Dict[str, StardewRule]):
|
||||
self.registry.special_order_rules.update(new_rules)
|
||||
|
||||
def can_complete_special_order(self, special_order: str) -> StardewRule:
|
||||
return Has(special_order, self.registry.special_order_rules)
|
||||
|
||||
def has_island_transport(self) -> StardewRule:
|
||||
return self.logic.received(Transportation.island_obelisk) | self.logic.received(Transportation.boat_repair)
|
||||
38
worlds/stardew_valley/logic/time_logic.py
Normal file
38
worlds/stardew_valley/logic/time_logic.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from functools import cached_property
|
||||
from typing import Union
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from ..stardew_rule import StardewRule, HasProgressionPercent, True_
|
||||
|
||||
MAX_MONTHS = 12
|
||||
MONTH_COEFFICIENT = 24 // MAX_MONTHS
|
||||
|
||||
|
||||
class TimeLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.time = TimeLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]):
|
||||
|
||||
@cache_self1
|
||||
def has_lived_months(self, number: int) -> StardewRule:
|
||||
if number <= 0:
|
||||
return True_()
|
||||
number = min(number, MAX_MONTHS)
|
||||
return HasProgressionPercent(self.player, number * MONTH_COEFFICIENT)
|
||||
|
||||
@cached_property
|
||||
def has_lived_max_months(self) -> StardewRule:
|
||||
return self.logic.time.has_lived_months(MAX_MONTHS)
|
||||
|
||||
@cached_property
|
||||
def has_year_two(self) -> StardewRule:
|
||||
return self.logic.time.has_lived_months(4)
|
||||
|
||||
@cached_property
|
||||
def has_year_three(self) -> StardewRule:
|
||||
return self.logic.time.has_lived_months(8)
|
||||
81
worlds/stardew_valley/logic/tool_logic.py
Normal file
81
worlds/stardew_valley/logic/tool_logic.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from typing import Union, Iterable
|
||||
|
||||
from Utils import cache_self1
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .has_logic import HasLogicMixin
|
||||
from .money_logic import MoneyLogicMixin
|
||||
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.skill_names import ModSkill
|
||||
from ..strings.spells import MagicSpell
|
||||
from ..strings.tool_names import ToolMaterial, Tool
|
||||
|
||||
tool_materials = {
|
||||
ToolMaterial.copper: 1,
|
||||
ToolMaterial.iron: 2,
|
||||
ToolMaterial.gold: 3,
|
||||
ToolMaterial.iridium: 4
|
||||
}
|
||||
|
||||
tool_upgrade_prices = {
|
||||
ToolMaterial.copper: 2000,
|
||||
ToolMaterial.iron: 5000,
|
||||
ToolMaterial.gold: 10000,
|
||||
ToolMaterial.iridium: 25000
|
||||
}
|
||||
|
||||
|
||||
class ToolLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.tool = ToolLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, MagicLogicMixin]]):
|
||||
# Should be cached
|
||||
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:
|
||||
return self.logic.received(f"Progressive {tool}", tool_materials[material])
|
||||
|
||||
return self.logic.has(f"{material} Bar") & self.logic.money.can_spend(tool_upgrade_prices[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:
|
||||
if self.options.tool_progression & ToolProgression.option_progressive:
|
||||
return self.logic.received(f"Progressive {Tool.fishing_rod}", level)
|
||||
|
||||
if level <= 1:
|
||||
return self.logic.region.can_reach(Region.beach)
|
||||
prices = {2: 500, 3: 1800, 4: 7500}
|
||||
level = min(level, 4)
|
||||
return self.logic.money.can_spend_at(Region.fish_shop, prices[level])
|
||||
|
||||
# Should be cached
|
||||
def can_forage(self, season: Union[str, Iterable[str]], region: str = Region.forest, need_hoe: bool = False) -> StardewRule:
|
||||
season_rule = False_()
|
||||
if isinstance(season, str):
|
||||
season_rule = self.logic.season.has(season)
|
||||
elif isinstance(season, Iterable):
|
||||
season_rule = self.logic.season.has_any(season)
|
||||
region_rule = self.logic.region.can_reach(region)
|
||||
if need_hoe:
|
||||
return season_rule & region_rule & self.logic.tool.has_tool(Tool.hoe)
|
||||
return season_rule & region_rule
|
||||
|
||||
@cache_self1
|
||||
def can_water(self, level: int) -> StardewRule:
|
||||
tool_rule = self.logic.tool.has_tool(Tool.watering_can, ToolMaterial.tiers[level])
|
||||
spell_rule = self.logic.received(MagicSpell.water) & self.logic.magic.can_use_altar() & self.logic.received(ModSkillLevel.magic_level, level)
|
||||
return tool_rule | spell_rule
|
||||
26
worlds/stardew_valley/logic/traveling_merchant_logic.py
Normal file
26
worlds/stardew_valley/logic/traveling_merchant_logic.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from typing import Union
|
||||
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from ..stardew_rule import True_
|
||||
from ..strings.calendar_names import Weekday
|
||||
|
||||
|
||||
class TravelingMerchantLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.traveling_merchant = TravelingMerchantLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class TravelingMerchantLogic(BaseLogic[Union[TravelingMerchantLogicMixin, ReceivedLogicMixin]]):
|
||||
|
||||
def has_days(self, number_days: int = 1):
|
||||
if number_days <= 0:
|
||||
return True_()
|
||||
|
||||
traveling_merchant_days = tuple(f"Traveling Merchant: {day}" for day in Weekday.all_days)
|
||||
if number_days == 1:
|
||||
return self.logic.received_any(*traveling_merchant_days)
|
||||
|
||||
tier = min(7, max(1, number_days))
|
||||
return self.logic.received_n(*traveling_merchant_days, count=tier)
|
||||
19
worlds/stardew_valley/logic/wallet_logic.py
Normal file
19
worlds/stardew_valley/logic/wallet_logic.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from .base_logic import BaseLogic, BaseLogicMixin
|
||||
from .received_logic import ReceivedLogicMixin
|
||||
from ..stardew_rule import StardewRule
|
||||
from ..strings.wallet_item_names import Wallet
|
||||
|
||||
|
||||
class WalletLogicMixin(BaseLogicMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.wallet = WalletLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class WalletLogic(BaseLogic[ReceivedLogicMixin]):
|
||||
|
||||
def can_speak_dwarf(self) -> StardewRule:
|
||||
return self.logic.received(Wallet.dwarvish_translation_guide)
|
||||
|
||||
def has_rusty_key(self) -> StardewRule:
|
||||
return self.logic.received(Wallet.rusty_key)
|
||||
Reference in New Issue
Block a user