mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Stardew Valley: Refactor Animals to use Content Packs (#4320)
This commit is contained in:
@@ -1,25 +1,15 @@
|
||||
from typing import Union
|
||||
import typing
|
||||
|
||||
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 ..stardew_rule import StardewRule
|
||||
from ..strings.building_names import Building
|
||||
from ..strings.forageable_names import Forageable
|
||||
from ..strings.generic_names import Generic
|
||||
from ..strings.region_names import Region
|
||||
from ..strings.machine_names import Machine
|
||||
|
||||
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)
|
||||
}
|
||||
if typing.TYPE_CHECKING:
|
||||
from .logic import StardewLogic
|
||||
else:
|
||||
StardewLogic = object
|
||||
|
||||
|
||||
class AnimalLogicMixin(BaseLogicMixin):
|
||||
@@ -28,32 +18,19 @@ class AnimalLogicMixin(BaseLogicMixin):
|
||||
self.animal = AnimalLogic(*args, **kwargs)
|
||||
|
||||
|
||||
class AnimalLogic(BaseLogic[Union[HasLogicMixin, MoneyLogicMixin, BuildingLogicMixin]]):
|
||||
class AnimalLogic(BaseLogic[StardewLogic]):
|
||||
|
||||
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 can_incubate(self, egg_item: str) -> StardewRule:
|
||||
return self.logic.building.has_building(Building.coop) & self.logic.has(egg_item)
|
||||
|
||||
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 can_ostrich_incubate(self, egg_item: str) -> StardewRule:
|
||||
return self.logic.building.has_building(Building.barn) & self.logic.has(Machine.ostrich_incubator) & self.logic.has(egg_item)
|
||||
|
||||
def has_happy_animal(self, animal: str) -> StardewRule:
|
||||
return self.has_animal(animal) & self.logic.has(Forageable.hay)
|
||||
def has_animal(self, animal_name: str) -> StardewRule:
|
||||
animal = self.content.animals.get(animal_name)
|
||||
assert animal is not None, f"Animal {animal_name} not found."
|
||||
|
||||
def has_any_animal(self) -> StardewRule:
|
||||
return self.has_any_coop_animal() | self.has_any_barn_animal()
|
||||
return self.logic.source.has_access_to_any(animal.sources) & self.logic.building.has_building(animal.required_building)
|
||||
|
||||
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)
|
||||
def has_happy_animal(self, animal_name: str) -> StardewRule:
|
||||
return self.logic.animal.has_animal(animal_name) & self.logic.has(Forageable.hay)
|
||||
|
||||
@@ -17,6 +17,7 @@ from .skill_logic import SkillLogicMixin
|
||||
from .time_logic import TimeLogicMixin
|
||||
from ..options import FestivalLocations
|
||||
from ..stardew_rule import StardewRule
|
||||
from ..strings.animal_product_names import AnimalProduct
|
||||
from ..strings.book_names import Book
|
||||
from ..strings.craftable_names import Fishing
|
||||
from ..strings.crop_names import Fruit, Vegetable
|
||||
@@ -154,18 +155,37 @@ SkillLogicMixin, RegionLogicMixin, ActionLogicMixin, MonsterLogicMixin, Relation
|
||||
if self.options.festival_locations != FestivalLocations.option_hard:
|
||||
return self.logic.true_
|
||||
|
||||
animal_rule = self.logic.animal.has_animal(Generic.any)
|
||||
# Other animal products are not counted in the animal product category
|
||||
good_animal_products = [
|
||||
AnimalProduct.duck_egg, AnimalProduct.duck_feather, AnimalProduct.egg, AnimalProduct.goat_milk, AnimalProduct.golden_egg, AnimalProduct.large_egg,
|
||||
AnimalProduct.large_goat_milk, AnimalProduct.large_milk, AnimalProduct.milk, AnimalProduct.ostrich_egg, AnimalProduct.rabbit_foot,
|
||||
AnimalProduct.void_egg, AnimalProduct.wool
|
||||
]
|
||||
if AnimalProduct.ostrich_egg not in self.content.game_items:
|
||||
# When ginger island is excluded, ostrich egg is not available
|
||||
good_animal_products.remove(AnimalProduct.ostrich_egg)
|
||||
animal_rule = self.logic.has_any(*good_animal_products)
|
||||
|
||||
artisan_rule = self.logic.artisan.can_keg(Generic.any) | self.logic.artisan.can_preserves_jar(Generic.any)
|
||||
cooking_rule = self.logic.money.can_spend_at(Region.saloon, 220) # Salads at the bar are good enough
|
||||
|
||||
# Salads at the bar are good enough
|
||||
cooking_rule = self.logic.money.can_spend_at(Region.saloon, 220)
|
||||
|
||||
fish_rule = self.logic.skill.can_fish(difficulty=50)
|
||||
forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods)) # Hazelnut always available since the grange display is in fall
|
||||
mineral_rule = self.logic.action.can_open_geode(Generic.any) # More than half the minerals are good enough
|
||||
|
||||
# Hazelnut always available since the grange display is in fall
|
||||
forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods))
|
||||
|
||||
# More than half the minerals are good enough
|
||||
mineral_rule = self.logic.action.can_open_geode(Generic.any)
|
||||
|
||||
good_fruits = (fruit
|
||||
for fruit in
|
||||
(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)
|
||||
if fruit in self.content.game_items)
|
||||
fruit_rule = self.logic.has_any(*good_fruits)
|
||||
|
||||
good_vegetables = (vegeteable
|
||||
for vegeteable in
|
||||
(Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.cauliflower, Forageable.fiddlehead_fern, Vegetable.kale,
|
||||
@@ -173,8 +193,7 @@ SkillLogicMixin, RegionLogicMixin, ActionLogicMixin, MonsterLogicMixin, Relation
|
||||
if vegeteable in self.content.game_items)
|
||||
vegetable_rule = self.logic.has_any(*good_vegetables)
|
||||
|
||||
return animal_rule & artisan_rule & cooking_rule & fish_rule & \
|
||||
forage_rule & fruit_rule & mineral_rule & vegetable_rule
|
||||
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.logic.skill.can_fish(difficulty=60)
|
||||
|
||||
@@ -149,42 +149,37 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
|
||||
# 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_egg: self.animal.has_animal(Animal.duck), # Should also check starter
|
||||
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.egg: self.animal.has_animal(Animal.chicken), # Should also check starter
|
||||
AnimalProduct.goat_milk: self.animal.has_animal(Animal.goat),
|
||||
AnimalProduct.golden_egg: self.has(AnimalProduct.golden_egg_starter), # Should also check golden chicken if there was an alternative to obtain it without golden egg
|
||||
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) & self.has(Forageable.journal_scrap) & self.region.can_reach(Region.volcano_floor_5),
|
||||
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.void_egg: self.has(AnimalProduct.void_egg_starter), # Should also check void chicken if there was an alternative to obtain it without void egg
|
||||
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),
|
||||
AnimalProduct.duck_egg_starter: self.logic.false_, # It could be purchased at the Feast of the Winter Star, but it's random every year, so not considering it yet...
|
||||
AnimalProduct.dinosaur_egg_starter: self.logic.false_, # Dinosaur eggs are also part of the museum rules, and I don't want to touch them yet.
|
||||
AnimalProduct.egg_starter: self.logic.false_, # It could be purchased at the Desert Festival, but festival logic is quite a mess, so not considering it yet...
|
||||
AnimalProduct.golden_egg_starter: 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.void_egg_starter: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)),
|
||||
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),
|
||||
|
||||
@@ -72,4 +72,16 @@ of source (Monster drop and fish can have foraging sources).
|
||||
if easy logic is disabled. For instance, anything that requires money could be accessible as soon as you can sell something to someone (even wood).
|
||||
|
||||
Items are classified by their source. An item with a fishing or a crab pot source is considered a fish, an item dropping from a monster is a monster drop. An
|
||||
item with a foraging source is a forageable. Items can fit in multiple categories.
|
||||
item with a foraging source is a forageable. Items can fit in multiple categories.
|
||||
|
||||
## Prefer rich class to anemic list of sources
|
||||
|
||||
For game mechanic that might need more logic/interaction than a simple game item, prefer creating a class than just listing the sources and adding generic
|
||||
requirements to them. This will simplify the implementation of more complex mechanics and increase cohesion.
|
||||
|
||||
For instance, `Building` can be upgraded. Instead of having a simple source for the `Big Coop` being a shop source with an additional requirement being having
|
||||
the previous building, the `Building` class has knowledge of the upgrade system and know from which building it can be upgraded.
|
||||
|
||||
Another example is `Animal`. Instead of a shopping source with a requirement of having a `Coop`, the `Chicken` knows that a building is required. This way, a
|
||||
potential source of chicken from incubating an egg would not require an additional requirement of having a coop (assuming the incubator could be obtained
|
||||
without a big coop).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import functools
|
||||
from typing import Union, Any, Iterable
|
||||
|
||||
from .animal_logic import AnimalLogicMixin
|
||||
from .artisan_logic import ArtisanLogicMixin
|
||||
from .base_logic import BaseLogicMixin, BaseLogic
|
||||
from .grind_logic import GrindLogicMixin
|
||||
@@ -11,6 +12,7 @@ from .received_logic import ReceivedLogicMixin
|
||||
from .region_logic import RegionLogicMixin
|
||||
from .requirement_logic import RequirementLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from ..data.animal import IncubatorSource, OstrichIncubatorSource
|
||||
from ..data.artisan import MachineSource
|
||||
from ..data.game_item import GenericSource, Source, GameItem, CustomRuleSource
|
||||
from ..data.harvest import ForagingSource, FruitBatsSource, MushroomCaveSource, SeasonalForagingSource, \
|
||||
@@ -25,7 +27,7 @@ class SourceLogicMixin(BaseLogicMixin):
|
||||
|
||||
|
||||
class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogicMixin, HarvestingLogicMixin, MoneyLogicMixin, RegionLogicMixin,
|
||||
ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
|
||||
ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin, AnimalLogicMixin]]):
|
||||
|
||||
def has_access_to_item(self, item: GameItem):
|
||||
rules = []
|
||||
@@ -81,6 +83,14 @@ ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
|
||||
def _(self, source: HarvestCropSource):
|
||||
return self.logic.harvesting.can_harvest_crop_from(source)
|
||||
|
||||
@has_access_to.register
|
||||
def _(self, source: IncubatorSource):
|
||||
return self.logic.animal.can_incubate(source.egg_item)
|
||||
|
||||
@has_access_to.register
|
||||
def _(self, source: OstrichIncubatorSource):
|
||||
return self.logic.animal.can_ostrich_incubate(source.egg_item)
|
||||
|
||||
@has_access_to.register
|
||||
def _(self, source: MachineSource):
|
||||
return self.logic.artisan.can_produce_from(source)
|
||||
|
||||
Reference in New Issue
Block a user