From 62657df3fb081482bdc89022b102794f24ba3992 Mon Sep 17 00:00:00 2001 From: agilbert1412 Date: Wed, 19 Jul 2023 14:26:38 -0400 Subject: [PATCH] Stardew Valley: 4.x.x - The Ginger Update (#1931) ## What is this fixing or adding? Major content update for Stardew Valley ## How was this tested? One large-scale public Beta on the archipelago server, plus several smaller private asyncs and test runs You can go to https://github.com/agilbert1412/StardewArchipelago/releases to grab the mod (latest 4.x.x version), the supported mods and the apworld, to test this PR ## New Features: - Festival Checks [Easy mode or Hard Mode] - Special Orders [Both Board and Qi] - Willy's Boat - Ginger Island Parrots - TV Channels - Trap Items [Available in various difficulty levels] - Entrance Randomizer: Buildings and Chaos - New Fishsanity options: Exclude Legendaries, Exclude Hard fish, Only easy fish - Resource Pack overhaul [Resource packs are now more enjoyable and varied] - Goal: Greatest Walnut Hunter [Find every single Golden Walnut] - Goal: Perfection [Achieve Perfection] - Option: Profit Margin [Multiplier over all earnings] - Option: Friendsanity Heart Size [Reduce clutter from friendsanity hearts] - Option: Exclude Ginger Island - will exclude many locations and items to generate a playthrough that does not go to the island - Mod Support [Curated list of mods] ## New Contributors: @Witchybun for the mod support --------- Co-authored-by: Witchybun Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Fabian Dill --- test/TestBase.py | 3 +- worlds/stardew_valley/__init__.py | 69 +- worlds/stardew_valley/data/bundle_data.py | 4 +- worlds/stardew_valley/data/crops.csv | 10 +- worlds/stardew_valley/data/crops_data.py | 1 + worlds/stardew_valley/data/entrance_data.py | 2 - worlds/stardew_valley/data/fish_data.py | 52 +- worlds/stardew_valley/data/items.csv | 989 +++++--- worlds/stardew_valley/data/locations.csv | 2200 ++++++++++------- worlds/stardew_valley/data/museum_data.py | 295 ++- worlds/stardew_valley/data/recipe_data.py | 190 ++ worlds/stardew_valley/data/resource_packs.csv | 39 - worlds/stardew_valley/data/villagers_data.py | 203 +- .../stardew_valley/docs/en_Stardew Valley.md | 76 +- worlds/stardew_valley/docs/setup_en.md | 51 +- worlds/stardew_valley/items.py | 382 +-- worlds/stardew_valley/locations.py | 209 +- worlds/stardew_valley/logic.py | 1690 +++++++++---- worlds/stardew_valley/mods/__init__.py | 0 worlds/stardew_valley/mods/logic/__init__.py | 0 worlds/stardew_valley/mods/logic/buildings.py | 16 + worlds/stardew_valley/mods/logic/deepwoods.py | 35 + worlds/stardew_valley/mods/logic/magic.py | 80 + worlds/stardew_valley/mods/logic/quests.py | 31 + worlds/stardew_valley/mods/logic/skills.py | 94 + .../mods/logic/skullcavernelevator.py | 10 + .../mods/logic/special_orders.py | 24 + worlds/stardew_valley/mods/mod_data.py | 48 + worlds/stardew_valley/mods/mod_regions.py | 144 ++ worlds/stardew_valley/options.py | 233 +- worlds/stardew_valley/region_classes.py | 58 + worlds/stardew_valley/regions.py | 837 +++++-- worlds/stardew_valley/rules.py | 525 +++- worlds/stardew_valley/scripts/update_data.py | 21 +- worlds/stardew_valley/stardew_rule.py | 5 +- worlds/stardew_valley/strings/__init__.py | 25 + worlds/stardew_valley/strings/animal_names.py | 13 + .../strings/animal_product_names.py | 24 + .../strings/ap_names/__init__.py | 0 .../strings/ap_names/buff_names.py | 3 + .../strings/ap_names/skill_level_names.py | 2 + .../strings/ap_names/transport_names.py | 10 + .../strings/artisan_good_names.py | 23 + .../stardew_valley/strings/building_names.py | 23 + .../stardew_valley/strings/calendar_names.py | 11 + .../stardew_valley/strings/craftable_names.py | 16 + worlds/stardew_valley/strings/crop_names.py | 59 + .../stardew_valley/strings/entrance_names.py | 217 ++ .../strings/fertilizer_names.py | 17 + .../strings/festival_check_names.py | 32 + worlds/stardew_valley/strings/fish_names.py | 62 + worlds/stardew_valley/strings/flower_names.py | 3 + worlds/stardew_valley/strings/food_names.py | 67 + .../strings/forageable_names.py | 35 + .../strings/fruit_tree_names.py | 10 + .../stardew_valley/strings/generic_names.py | 4 + worlds/stardew_valley/strings/geode_names.py | 7 + worlds/stardew_valley/strings/gift_names.py | 6 + worlds/stardew_valley/strings/goal_names.py | 10 + .../strings/ingredient_names.py | 6 + .../stardew_valley/strings/machine_names.py | 22 + .../stardew_valley/strings/material_names.py | 9 + worlds/stardew_valley/strings/metal_names.py | 35 + .../strings/monster_drop_names.py | 6 + .../strings/performance_names.py | 13 + worlds/stardew_valley/strings/quest_names.py | 57 + .../region_names.py} | 99 +- worlds/stardew_valley/strings/season_names.py | 6 + worlds/stardew_valley/strings/seed_names.py | 9 + worlds/stardew_valley/strings/skill_names.py | 15 + .../strings/special_order_names.py | 33 + worlds/stardew_valley/strings/spells.py | 22 + worlds/stardew_valley/strings/tool_names.py | 31 + .../strings/tv_channel_names.py | 2 + .../stardew_valley/strings/villager_names.py | 49 + .../strings/wallet_item_names.py | 5 + worlds/stardew_valley/strings/weapon_names.py | 4 + worlds/stardew_valley/test/TestGeneration.py | 417 +++- worlds/stardew_valley/test/TestItems.py | 96 +- worlds/stardew_valley/test/TestLogic.py | 52 +- .../test/TestLogicSimplification.py | 8 - worlds/stardew_valley/test/TestMods.py | 192 ++ worlds/stardew_valley/test/TestOptions.py | 253 +- worlds/stardew_valley/test/TestRegions.py | 76 +- worlds/stardew_valley/test/TestRules.py | 198 +- worlds/stardew_valley/test/__init__.py | 79 +- worlds/stardew_valley/test/checks/__init__.py | 0 .../stardew_valley/test/checks/goal_checks.py | 55 + .../test/checks/option_checks.py | 90 + .../test/checks/world_checks.py | 33 + .../test/long/TestOptionsLong.py | 41 + .../test/long/TestRandomWorlds.py | 99 + worlds/stardew_valley/test/long/__init__.py | 0 .../stardew_valley/test/long/option_names.py | 7 + 94 files changed, 8320 insertions(+), 3104 deletions(-) delete mode 100644 worlds/stardew_valley/data/entrance_data.py create mode 100644 worlds/stardew_valley/data/recipe_data.py delete mode 100644 worlds/stardew_valley/data/resource_packs.csv create mode 100644 worlds/stardew_valley/mods/__init__.py create mode 100644 worlds/stardew_valley/mods/logic/__init__.py create mode 100644 worlds/stardew_valley/mods/logic/buildings.py create mode 100644 worlds/stardew_valley/mods/logic/deepwoods.py create mode 100644 worlds/stardew_valley/mods/logic/magic.py create mode 100644 worlds/stardew_valley/mods/logic/quests.py create mode 100644 worlds/stardew_valley/mods/logic/skills.py create mode 100644 worlds/stardew_valley/mods/logic/skullcavernelevator.py create mode 100644 worlds/stardew_valley/mods/logic/special_orders.py create mode 100644 worlds/stardew_valley/mods/mod_data.py create mode 100644 worlds/stardew_valley/mods/mod_regions.py create mode 100644 worlds/stardew_valley/region_classes.py create mode 100644 worlds/stardew_valley/strings/__init__.py create mode 100644 worlds/stardew_valley/strings/animal_names.py create mode 100644 worlds/stardew_valley/strings/animal_product_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/__init__.py create mode 100644 worlds/stardew_valley/strings/ap_names/buff_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/skill_level_names.py create mode 100644 worlds/stardew_valley/strings/ap_names/transport_names.py create mode 100644 worlds/stardew_valley/strings/artisan_good_names.py create mode 100644 worlds/stardew_valley/strings/building_names.py create mode 100644 worlds/stardew_valley/strings/calendar_names.py create mode 100644 worlds/stardew_valley/strings/craftable_names.py create mode 100644 worlds/stardew_valley/strings/crop_names.py create mode 100644 worlds/stardew_valley/strings/entrance_names.py create mode 100644 worlds/stardew_valley/strings/fertilizer_names.py create mode 100644 worlds/stardew_valley/strings/festival_check_names.py create mode 100644 worlds/stardew_valley/strings/fish_names.py create mode 100644 worlds/stardew_valley/strings/flower_names.py create mode 100644 worlds/stardew_valley/strings/food_names.py create mode 100644 worlds/stardew_valley/strings/forageable_names.py create mode 100644 worlds/stardew_valley/strings/fruit_tree_names.py create mode 100644 worlds/stardew_valley/strings/generic_names.py create mode 100644 worlds/stardew_valley/strings/geode_names.py create mode 100644 worlds/stardew_valley/strings/gift_names.py create mode 100644 worlds/stardew_valley/strings/goal_names.py create mode 100644 worlds/stardew_valley/strings/ingredient_names.py create mode 100644 worlds/stardew_valley/strings/machine_names.py create mode 100644 worlds/stardew_valley/strings/material_names.py create mode 100644 worlds/stardew_valley/strings/metal_names.py create mode 100644 worlds/stardew_valley/strings/monster_drop_names.py create mode 100644 worlds/stardew_valley/strings/performance_names.py create mode 100644 worlds/stardew_valley/strings/quest_names.py rename worlds/stardew_valley/{data/region_data.py => strings/region_names.py} (54%) create mode 100644 worlds/stardew_valley/strings/season_names.py create mode 100644 worlds/stardew_valley/strings/seed_names.py create mode 100644 worlds/stardew_valley/strings/skill_names.py create mode 100644 worlds/stardew_valley/strings/special_order_names.py create mode 100644 worlds/stardew_valley/strings/spells.py create mode 100644 worlds/stardew_valley/strings/tool_names.py create mode 100644 worlds/stardew_valley/strings/tv_channel_names.py create mode 100644 worlds/stardew_valley/strings/villager_names.py create mode 100644 worlds/stardew_valley/strings/wallet_item_names.py create mode 100644 worlds/stardew_valley/strings/weapon_names.py create mode 100644 worlds/stardew_valley/test/TestMods.py create mode 100644 worlds/stardew_valley/test/checks/__init__.py create mode 100644 worlds/stardew_valley/test/checks/goal_checks.py create mode 100644 worlds/stardew_valley/test/checks/option_checks.py create mode 100644 worlds/stardew_valley/test/checks/world_checks.py create mode 100644 worlds/stardew_valley/test/long/TestOptionsLong.py create mode 100644 worlds/stardew_valley/test/long/TestRandomWorlds.py create mode 100644 worlds/stardew_valley/test/long/__init__.py create mode 100644 worlds/stardew_valley/test/long/option_names.py diff --git a/test/TestBase.py b/test/TestBase.py index 17fe6425..dc79ad28 100644 --- a/test/TestBase.py +++ b/test/TestBase.py @@ -237,7 +237,8 @@ class WorldTestBase(unittest.TestCase): for location in self.multiworld.get_locations(): if location.name not in excluded: with self.subTest("Location should be reached", location=location): - self.assertTrue(location.can_reach(state), f"{location.name} unreachable") + reachable = location.can_reach(state) + self.assertTrue(reachable, f"{location.name} unreachable") with self.subTest("Beatable"): self.multiworld.state = state self.assertBeatable(True) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index bf70cd8e..495b52c3 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,16 +1,19 @@ -from typing import Dict, Any, Iterable, Optional, Union +import logging +from typing import Dict, Any, Iterable, Optional, Union, Set -from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState +from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState, ItemClassification, MultiWorld from worlds.AutoWorld import World, WebWorld from . import rules, logic, options from .bundles import get_all_bundles, Bundle from .items import item_table, create_items, ItemData, Group, items_by_group from .locations import location_table, create_locations, LocationData -from .logic import StardewLogic, StardewRule, True_ +from .logic import StardewLogic, StardewRule, True_, MAX_MONTHS from .options import stardew_valley_options, StardewOptions, fetch_options from .regions import create_regions from .rules import set_rules from worlds.generic.Rules import set_rule +from .mods.mod_data import mod_versions +from .strings.goal_names import Goal client_version = 0 @@ -37,7 +40,7 @@ class StardewWebWorld(WebWorld): "English", "setup_en.md", "setup/en", - ["KaitoKid", "Jouramie"] + ["KaitoKid", "Jouramie", "Witchybun (Mod Support)", "Exempt-Medic (Proofreading)"] )] @@ -62,15 +65,33 @@ class StardewValleyWorld(World): web = StardewWebWorld() modified_bundles: Dict[str, Bundle] randomized_entrances: Dict[str, str] + all_progression_items: Set[str] + + def __init__(self, world: MultiWorld, player: int): + super().__init__(world, player) + self.all_progression_items = set() def generate_early(self): self.options = fetch_options(self.multiworld, self.player) + self.force_change_options_if_incompatible() + self.logic = StardewLogic(self.player, self.options) self.modified_bundles = get_all_bundles(self.multiworld.random, self.logic, self.options[options.BundleRandomization], self.options[options.BundlePrice]) + def force_change_options_if_incompatible(self): + goal_is_walnut_hunter = self.options[options.Goal] == options.Goal.option_greatest_walnut_hunter + goal_is_perfection = self.options[options.Goal] == options.Goal.option_perfection + goal_is_island_related = goal_is_walnut_hunter or goal_is_perfection + exclude_ginger_island = self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true + if goal_is_island_related and exclude_ginger_island: + self.options[options.ExcludeGingerIsland] = options.ExcludeGingerIsland.option_false + goal = options.Goal.name_lookup[self.options[options.Goal]] + player_name = self.multiworld.player_name[self.player] + logging.warning(f"Goal '{goal}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})") + def create_regions(self): def create_region(name: str, exits: Iterable[str]) -> Region: region = Region(name, self.player, self.multiworld) @@ -142,7 +163,7 @@ class StardewValleyWorld(World): self.multiworld.early_items[self.player]["Progressive Backpack"] = 1 def setup_month_events(self): - for i in range(0, 8): + for i in range(0, MAX_MONTHS): month_end = LocationData(None, "Stardew Valley", f"Month End {i + 1}") if i == 0: self.create_event_location(month_end, True_(), "Month End") @@ -152,32 +173,40 @@ class StardewValleyWorld(World): def setup_victory(self): if self.options[options.Goal] == options.Goal.option_community_center: - self.create_event_location(location_table["Complete Community Center"], + self.create_event_location(location_table[Goal.community_center], self.logic.can_complete_community_center().simplify(), "Victory") elif self.options[options.Goal] == options.Goal.option_grandpa_evaluation: - self.create_event_location(location_table["Succeed Grandpa's Evaluation"], + self.create_event_location(location_table[Goal.grandpa_evaluation], self.logic.can_finish_grandpa_evaluation().simplify(), "Victory") elif self.options[options.Goal] == options.Goal.option_bottom_of_the_mines: - self.create_event_location(location_table["Reach the Bottom of The Mines"], + self.create_event_location(location_table[Goal.bottom_of_the_mines], self.logic.can_mine_to_floor(120).simplify(), "Victory") elif self.options[options.Goal] == options.Goal.option_cryptic_note: - self.create_event_location(location_table["Complete Quest Cryptic Note"], + self.create_event_location(location_table[Goal.cryptic_note], self.logic.can_complete_quest("Cryptic Note").simplify(), "Victory") elif self.options[options.Goal] == options.Goal.option_master_angler: - self.create_event_location(location_table["Catch Every Fish"], + self.create_event_location(location_table[Goal.master_angler], self.logic.can_catch_every_fish().simplify(), "Victory") elif self.options[options.Goal] == options.Goal.option_complete_collection: - self.create_event_location(location_table["Complete the Museum Collection"], + self.create_event_location(location_table[Goal.complete_museum], self.logic.can_complete_museum().simplify(), "Victory") elif self.options[options.Goal] == options.Goal.option_full_house: - self.create_event_location(location_table["Full House"], - self.logic.can_have_two_children().simplify(), + self.create_event_location(location_table[Goal.full_house], + (self.logic.has_children(2) & self.logic.can_reproduce()).simplify(), + "Victory") + elif self.options[options.Goal] == options.Goal.option_greatest_walnut_hunter: + self.create_event_location(location_table[Goal.greatest_walnut_hunter], + self.logic.has_walnut(130).simplify(), + "Victory") + elif self.options[options.Goal] == options.Goal.option_perfection: + self.create_event_location(location_table[Goal.perfection], + self.logic.has_everything(self.all_progression_items).simplify(), "Victory") self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) @@ -186,6 +215,8 @@ class StardewValleyWorld(World): if isinstance(item, str): item = item_table[item] + if item.classification == ItemClassification.progression: + self.all_progression_items.add(item.name) return StardewItem(item.name, item.classification, item.code, self.player) def create_event_location(self, location_data: LocationData, rule: StardewRule, item: Optional[str] = None): @@ -245,8 +276,13 @@ class StardewValleyWorld(World): key, value = self.modified_bundles[bundle_key].to_pair() modified_bundles[key] = value - excluded_options = [options.ResourcePackMultiplier, options.BundleRandomization, options.BundlePrice, - options.NumberOfPlayerBuffs] + instance_mod_versions = {} + for mod in mod_versions: + if mod in self.options[options.Mods]: + instance_mod_versions[mod] = mod_versions[mod] + + excluded_options = [options.BundleRandomization, options.BundlePrice, + options.NumberOfMovementBuffs, options.NumberOfLuckBuffs, options.Mods] slot_data = dict(self.options.options) for option in excluded_options: slot_data.pop(option.internal_name) @@ -254,7 +290,8 @@ class StardewValleyWorld(World): "seed": self.multiworld.per_slot_randoms[self.player].randrange(1000000000), # Seed should be max 9 digits "randomized_entrances": self.randomized_entrances, "modified_bundles": modified_bundles, - "client_version": "3.0.0", + "client_version": "4.0.0", + "mod_versions": instance_mod_versions, }) return slot_data diff --git a/worlds/stardew_valley/data/bundle_data.py b/worlds/stardew_valley/data/bundle_data.py index a424cfb8..8a1a6a5b 100644 --- a/worlds/stardew_valley/data/bundle_data.py +++ b/worlds/stardew_valley/data/bundle_data.py @@ -5,7 +5,6 @@ from .common_data import quality_dict from .game_item import GameItem from .museum_data import Mineral - @dataclass(frozen=True) class BundleItem: item: GameItem @@ -36,6 +35,9 @@ class BundleItem: amount = 1 return self.as_gold_quality().as_amount(amount) + def is_gold_quality(self) -> bool: + return self.quality >= 2 + def __repr__(self): return f"{self.amount} {quality_dict[self.quality]} {self.item.name}" diff --git a/worlds/stardew_valley/data/crops.csv b/worlds/stardew_valley/data/crops.csv index 749a9c74..1ca039b7 100644 --- a/worlds/stardew_valley/data/crops.csv +++ b/worlds/stardew_valley/data/crops.csv @@ -1,11 +1,11 @@ crop,farm_growth_seasons,seed,seed_seasons,seed_regions Amaranth,Fall,Amaranth Seeds,Fall,"Pierre's General Store,JojaMart" Artichoke,Fall,Artichoke Seeds,Fall,"Pierre's General Store,JojaMart" -Beet,Fall,Beet Seeds,Fall,The Desert +Beet,Fall,Beet Seeds,Fall,Oasis Blue Jazz,Spring,Jazz Seeds,Spring,"Pierre's General Store,JojaMart" Blueberry,Summer,Blueberry Seeds,Summer,"Pierre's General Store,JojaMart" Bok Choy,Fall,Bok Choy Seeds,Fall,"Pierre's General Store,JojaMart" -Cactus Fruit,,Cactus Seeds,,The Desert +Cactus Fruit,,Cactus Seeds,,Oasis Cauliflower,Spring,Cauliflower Seeds,Spring,"Pierre's General Store,JojaMart" Corn,"Summer,Fall",Corn Seeds,"Summer,Fall","Pierre's General Store,JojaMart" Cranberries,Fall,Cranberry Seeds,Fall,"Pierre's General Store,JojaMart" @@ -19,17 +19,19 @@ Hot Pepper,Summer,Pepper Seeds,Summer,"Pierre's General Store,JojaMart" Kale,Spring,Kale Seeds,Spring,"Pierre's General Store,JojaMart" Melon,Summer,Melon Seeds,Summer,"Pierre's General Store,JojaMart" Parsnip,Spring,Parsnip Seeds,Spring,"Pierre's General Store,JojaMart" +Pineapple,Summer,Pineapple Seeds,Summer,"Island Trader" Poppy,Summer,Poppy Seeds,Summer,"Pierre's General Store,JojaMart" Potato,Spring,Potato Seeds,Spring,"Pierre's General Store,JojaMart" Pumpkin,Fall,Pumpkin Seeds,Fall,"Pierre's General Store,JojaMart" Radish,Summer,Radish Seeds,Summer,"Pierre's General Store,JojaMart" Red Cabbage,Summer,Red Cabbage Seeds,Summer,"Pierre's General Store,JojaMart" -Rhubarb,Spring,Rhubarb Seeds,Spring,The Desert -Starfruit,Summer,Starfruit Seeds,Summer,The Desert +Rhubarb,Spring,Rhubarb Seeds,Spring,Oasis +Starfruit,Summer,Starfruit Seeds,Summer,Oasis Strawberry,Spring,Strawberry Seeds,Spring,"Pierre's General Store,JojaMart" Summer Spangle,Summer,Spangle Seeds,Summer,"Pierre's General Store,JojaMart" Sunflower,"Summer,Fall",Sunflower Seeds,"Summer,Fall","Pierre's General Store,JojaMart" Sweet Gem Berry,Fall,Rare Seed,"Spring,Summer",Traveling Cart +Taro Root,Summer,Taro Tuber,Summer,"Island Trader" Tomato,Summer,Tomato Seeds,Summer,"Pierre's General Store,JojaMart" Tulip,Spring,Tulip Bulb,Spring,"Pierre's General Store,JojaMart" Unmilled Rice,Spring,Rice Shoot,Spring,"Pierre's General Store,JojaMart" diff --git a/worlds/stardew_valley/data/crops_data.py b/worlds/stardew_valley/data/crops_data.py index a8c0cbc7..e7982350 100644 --- a/worlds/stardew_valley/data/crops_data.py +++ b/worlds/stardew_valley/data/crops_data.py @@ -45,3 +45,4 @@ def load_crop_csv(): # TODO Those two should probably be split to we can include rest of seeds all_crops, all_purchasable_seeds = load_crop_csv() +crops_by_name = {crop.name: crop for crop in all_crops} diff --git a/worlds/stardew_valley/data/entrance_data.py b/worlds/stardew_valley/data/entrance_data.py deleted file mode 100644 index 585668cd..00000000 --- a/worlds/stardew_valley/data/entrance_data.py +++ /dev/null @@ -1,2 +0,0 @@ -class Entrance: - to_stardew_valley = "To Stardew Valley" diff --git a/worlds/stardew_valley/data/fish_data.py b/worlds/stardew_valley/data/fish_data.py index 0ab967b1..91a4431c 100644 --- a/worlds/stardew_valley/data/fish_data.py +++ b/worlds/stardew_valley/data/fish_data.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -from typing import List, Tuple, Union +from typing import List, Tuple, Union, Optional from . import season_data as season from .game_item import GameItem -from .region_data import SVRegion +from ..strings.region_names import Region @dataclass(frozen=True) @@ -11,41 +11,43 @@ class FishItem(GameItem): locations: Tuple[str] seasons: Tuple[str] difficulty: int + mod_name: Optional[str] def __repr__(self): return f"{self.name} [{self.item_id}] (Locations: {self.locations} |" \ f" Seasons: {self.seasons} |" \ - f" Difficulty: {self.difficulty}) " + f" Difficulty: {self.difficulty}) |" \ + f"Mod: {self.mod_name}" -fresh_water = (SVRegion.farm, SVRegion.forest, SVRegion.town, SVRegion.mountain) -ocean = (SVRegion.beach,) -town_river = (SVRegion.town,) -mountain_lake = (SVRegion.mountain,) -forest_pond = (SVRegion.forest,) -forest_river = (SVRegion.forest,) -secret_woods = (SVRegion.secret_woods,) -mines_floor_20 = (SVRegion.mines_floor_20,) -mines_floor_60 = (SVRegion.mines_floor_60,) -mines_floor_100 = (SVRegion.mines_floor_100,) -sewers = (SVRegion.sewers,) -desert = (SVRegion.desert,) -mutant_bug_lair = (SVRegion.mutant_bug_lair,) -witch_swamp = (SVRegion.witch_swamp,) -night_market = (SVRegion.beach,) -ginger_island_ocean = (SVRegion.ginger_island,) -ginger_island_river = (SVRegion.ginger_island,) -pirate_cove = (SVRegion.pirate_cove,) +fresh_water = (Region.farm, Region.forest, Region.town, Region.mountain) +ocean = (Region.beach,) +town_river = (Region.town,) +mountain_lake = (Region.mountain,) +forest_pond = (Region.forest,) +forest_river = (Region.forest,) +secret_woods = (Region.secret_woods,) +mines_floor_20 = (Region.mines_floor_20,) +mines_floor_60 = (Region.mines_floor_60,) +mines_floor_100 = (Region.mines_floor_100,) +sewers = (Region.sewer,) +desert = (Region.desert,) +mutant_bug_lair = (Region.mutant_bug_lair,) +witch_swamp = (Region.witch_swamp,) +night_market = (Region.beach,) +ginger_island_ocean = (Region.island_south, Region.island_west) +ginger_island_river = (Region.island_west,) +pirate_cove = (Region.pirate_cove,) all_fish: List[FishItem] = [] def create_fish(name: str, item_id: int, locations: Tuple[str, ...], seasons: Union[str, Tuple[str, ...]], - difficulty: int) -> FishItem: + difficulty: int, mod_name: Optional[str] = None) -> FishItem: if isinstance(seasons, str): seasons = (seasons,) - fish_item = FishItem(name, item_id, locations, seasons, difficulty) + fish_item = FishItem(name, item_id, locations, seasons, difficulty, mod_name) all_fish.append(fish_item) return fish_item @@ -94,6 +96,7 @@ sunfish = create_fish("Sunfish", 145, town_river + forest_river, (season.spring, super_cucumber = create_fish("Super Cucumber", 155, ocean + ginger_island_ocean, (season.summer, season.fall), 80) tiger_trout = create_fish("Tiger Trout", 699, town_river + forest_river, (season.fall, season.winter), 60) tilapia = create_fish("Tilapia", 701, ocean + ginger_island_ocean, (season.summer, season.fall), 50) +# Tuna has different seasons on ginger island. Should be changed when the whole fish thing is refactored tuna = create_fish("Tuna", 130, ocean + ginger_island_ocean, (season.summer, season.winter), 70) void_salmon = create_fish("Void Salmon", 795, witch_swamp, season.all_seasons, 80) walleye = create_fish("Walleye", 140, town_river + forest_river + forest_pond + mountain_lake, season.fall, 45) @@ -122,3 +125,6 @@ snail = create_fish("Snail", 721, fresh_water, season.all_seasons, -1) legendary_fish = [crimsonfish, angler, legend, glacierfish, mutant_carp] special_fish = [*legendary_fish, blob_fish, lava_eel, octopus, scorpion_carp, ice_pip, super_cucumber, dorado] +island_fish = [lionfish, blue_discus, stingray] + +all_fish_by_name = {fish.name: fish for fish in all_fish} diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index f2a45034..c3cd8526 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -1,391 +1,598 @@ -id,name,classification,groups -0,Joja Cola,filler,TRASH -15,Rusty Key,progression,MUSEUM -16,Dwarvish Translation Guide,progression,MUSEUM -17,Bridge Repair,progression,COMMUNITY_REWARD -18,Greenhouse,progression,COMMUNITY_REWARD -19,Glittering Boulder Removed,progression,COMMUNITY_REWARD -20,Minecarts Repair,useful,COMMUNITY_REWARD -21,Bus Repair,progression,COMMUNITY_REWARD -22,Movie Theater,useful, -23,Stardrop,useful, -24,Progressive Backpack,progression, -25,Rusty Sword,progression,WEAPON -26,Leather Boots,progression,"FOOTWEAR,MINES_FLOOR_10" -27,Work Boots,useful,"FOOTWEAR,MINES_FLOOR_10" -28,Wooden Blade,progression,"MINES_FLOOR_10,WEAPON" -29,Iron Dirk,progression,"MINES_FLOOR_10,WEAPON" -30,Wind Spire,progression,"MINES_FLOOR_10,WEAPON" -31,Femur,progression,"MINES_FLOOR_10,WEAPON" -32,Steel Smallsword,progression,"MINES_FLOOR_20,WEAPON" -33,Wood Club,progression,"MINES_FLOOR_20,WEAPON" -34,Elf Blade,progression,"MINES_FLOOR_20,WEAPON" -35,Glow Ring,useful,"MINES_FLOOR_20,RING" -36,Magnet Ring,useful,"MINES_FLOOR_20,RING" -37,Slingshot,progression,WEAPON -38,Tundra Boots,useful,"FOOTWEAR,MINES_FLOOR_50" -39,Thermal Boots,useful,"FOOTWEAR,MINES_FLOOR_50" -40,Combat Boots,useful,"FOOTWEAR,MINES_FLOOR_50" -41,Silver Saber,progression,"MINES_FLOOR_50,WEAPON" -42,Pirate's Sword,progression,"MINES_FLOOR_50,WEAPON" -43,Crystal Dagger,progression,"MINES_FLOOR_60,WEAPON" -44,Cutlass,progression,"MINES_FLOOR_60,WEAPON" -45,Iron Edge,progression,"MINES_FLOOR_60,WEAPON" -46,Burglar's Shank,progression,"MINES_FLOOR_60,WEAPON" -47,Wood Mallet,progression,"MINES_FLOOR_60,WEAPON" -48,Master Slingshot,progression,WEAPON -49,Firewalker Boots,useful,"FOOTWEAR,MINES_FLOOR_80" -50,Dark Boots,useful,"FOOTWEAR,MINES_FLOOR_80" -51,Claymore,progression,"MINES_FLOOR_80,WEAPON" -52,Templar's Blade,progression,"MINES_FLOOR_80,WEAPON" -53,Kudgel,progression,"MINES_FLOOR_80,WEAPON" -54,Shadow Dagger,progression,"MINES_FLOOR_80,WEAPON" -55,Obsidian Edge,progression,"MINES_FLOOR_90,WEAPON" -56,Tempered Broadsword,progression,"MINES_FLOOR_90,WEAPON" -57,Wicked Kris,progression,"MINES_FLOOR_90,WEAPON" -58,Bone Sword,progression,"MINES_FLOOR_90,WEAPON" -59,Ossified Blade,progression,"MINES_FLOOR_90,WEAPON" -60,Space Boots,useful,"FOOTWEAR,MINES_FLOOR_110" -61,Crystal Shoes,useful,"FOOTWEAR,MINES_FLOOR_110" -62,Steel Falchion,progression,"MINES_FLOOR_110,WEAPON" -63,The Slammer,progression,"MINES_FLOOR_110,WEAPON" -64,Skull Key,progression, -65,Progressive Hoe,progression,PROGRESSIVE_TOOLS -66,Progressive Pickaxe,progression,PROGRESSIVE_TOOLS -67,Progressive Axe,progression,PROGRESSIVE_TOOLS -68,Progressive Watering Can,progression,PROGRESSIVE_TOOLS -69,Progressive Trash Can,progression,PROGRESSIVE_TOOLS -70,Progressive Fishing Rod,progression,PROGRESSIVE_TOOLS -71,Golden Scythe,useful, -72,Progressive Mine Elevator,progression, -73,Farming Level,progression,SKILL_LEVEL_UP -74,Fishing Level,progression,SKILL_LEVEL_UP -75,Foraging Level,progression,SKILL_LEVEL_UP -76,Mining Level,progression,SKILL_LEVEL_UP -77,Combat Level,progression,SKILL_LEVEL_UP -78,Earth Obelisk,useful, -79,Water Obelisk,useful, -80,Desert Obelisk,progression, -81,Island Obelisk,progression, -82,Junimo Hut,useful, -83,Gold Clock,useful, -84,Progressive Coop,progression, -85,Progressive Barn,progression, -86,Well,useful, -87,Silo,progression, -88,Mill,progression, -89,Progressive Shed,progression, -90,Fish Pond,progression, -91,Stable,useful, -92,Slime Hutch,useful, -93,Shipping Bin,progression, -94,Beach Bridge,progression, -95,Adventurer's Guild,progression, -96,Club Card,progression, -97,Magnifying Glass,progression, -98,Bear's Knowledge,progression, -99,Iridium Snake Milk,progression, -100,JotPK: Progressive Boots,progression,ARCADE_MACHINE_BUFFS -101,JotPK: Progressive Gun,progression,ARCADE_MACHINE_BUFFS -102,JotPK: Progressive Ammo,progression,ARCADE_MACHINE_BUFFS -103,JotPK: Extra Life,progression,ARCADE_MACHINE_BUFFS -104,JotPK: Increased Drop Rate,progression,ARCADE_MACHINE_BUFFS -105,Junimo Kart: Extra Life,progression,ARCADE_MACHINE_BUFFS -106,Galaxy Sword,progression,"GALAXY_WEAPONS,WEAPON" -107,Galaxy Dagger,progression,"GALAXY_WEAPONS,WEAPON" -108,Galaxy Hammer,progression,"GALAXY_WEAPONS,WEAPON" -109,Movement Speed Bonus,useful, -110,Luck Bonus,useful, -111,Lava Katana,progression,"MINES_FLOOR_110,WEAPON" -112,Progressive House,progression, -113,Traveling Merchant: Sunday,progression,TRAVELING_MERCHANT_DAY -114,Traveling Merchant: Monday,progression,TRAVELING_MERCHANT_DAY -115,Traveling Merchant: Tuesday,progression,TRAVELING_MERCHANT_DAY -116,Traveling Merchant: Wednesday,progression,TRAVELING_MERCHANT_DAY -117,Traveling Merchant: Thursday,progression,TRAVELING_MERCHANT_DAY -118,Traveling Merchant: Friday,progression,TRAVELING_MERCHANT_DAY -119,Traveling Merchant: Saturday,progression,TRAVELING_MERCHANT_DAY -120,Traveling Merchant Stock Size,progression, -121,Traveling Merchant Discount,progression, -122,Return Scepter,useful, -123,Progressive Season,progression, -124,Spring,progression,SEASON -125,Summer,progression,SEASON -126,Fall,progression,SEASON -127,Winter,progression,SEASON -128,Amaranth Seeds,progression,SEED_SHUFFLE -129,Artichoke Seeds,progression,SEED_SHUFFLE -130,Beet Seeds,progression,SEED_SHUFFLE -131,Jazz Seeds,progression,SEED_SHUFFLE -132,Blueberry Seeds,progression,SEED_SHUFFLE -133,Bok Choy Seeds,progression,SEED_SHUFFLE -134,Cauliflower Seeds,progression,SEED_SHUFFLE -135,Corn Seeds,progression,SEED_SHUFFLE -136,Cranberry Seeds,progression,SEED_SHUFFLE -137,Eggplant Seeds,progression,SEED_SHUFFLE -138,Fairy Seeds,progression,SEED_SHUFFLE -139,Garlic Seeds,progression,SEED_SHUFFLE -140,Grape Starter,progression,SEED_SHUFFLE -141,Bean Starter,progression,SEED_SHUFFLE -142,Hops Starter,progression,SEED_SHUFFLE -143,Pepper Seeds,progression,SEED_SHUFFLE -144,Kale Seeds,progression,SEED_SHUFFLE -145,Melon Seeds,progression,SEED_SHUFFLE -146,Parsnip Seeds,progression,SEED_SHUFFLE -147,Poppy Seeds,progression,SEED_SHUFFLE -148,Potato Seeds,progression,SEED_SHUFFLE -149,Pumpkin Seeds,progression,SEED_SHUFFLE -150,Radish Seeds,progression,SEED_SHUFFLE -151,Red Cabbage Seeds,progression,SEED_SHUFFLE -152,Rhubarb Seeds,progression,SEED_SHUFFLE -153,Starfruit Seeds,progression,SEED_SHUFFLE -154,Strawberry Seeds,progression,SEED_SHUFFLE -155,Spangle Seeds,progression,SEED_SHUFFLE -156,Sunflower Seeds,progression,SEED_SHUFFLE -157,Tomato Seeds,progression,SEED_SHUFFLE -158,Tulip Bulb,progression,SEED_SHUFFLE -159,Rice Shoot,progression,SEED_SHUFFLE -160,Wheat Seeds,progression,SEED_SHUFFLE -161,Yam Seeds,progression,SEED_SHUFFLE -162,Cactus Seeds,progression,SEED_SHUFFLE -163,Magic Rock Candy,useful,MUSEUM -164,Ancient Seeds Recipe,progression,MUSEUM -165,Ancient Seeds,useful,MUSEUM -166,Traveling Merchant Metal Detector,progression,MUSEUM -167,Alex: 1 <3,progression,FRIENDSANITY -168,Elliott: 1 <3,progression,FRIENDSANITY -169,Harvey: 1 <3,progression,FRIENDSANITY -170,Sam: 1 <3,progression,FRIENDSANITY -171,Sebastian: 1 <3,progression,FRIENDSANITY -172,Shane: 1 <3,progression,FRIENDSANITY -173,Abigail: 1 <3,progression,FRIENDSANITY -174,Emily: 1 <3,progression,FRIENDSANITY -175,Haley: 1 <3,progression,FRIENDSANITY -176,Leah: 1 <3,progression,FRIENDSANITY -177,Maru: 1 <3,progression,FRIENDSANITY -178,Penny: 1 <3,progression,FRIENDSANITY -179,Caroline: 1 <3,progression,FRIENDSANITY -180,Clint: 1 <3,progression,FRIENDSANITY -181,Demetrius: 1 <3,progression,FRIENDSANITY -182,Dwarf: 1 <3,progression,FRIENDSANITY -183,Evelyn: 1 <3,progression,FRIENDSANITY -184,George: 1 <3,progression,FRIENDSANITY -185,Gus: 1 <3,progression,FRIENDSANITY -186,Jas: 1 <3,progression,FRIENDSANITY -187,Jodi: 1 <3,progression,FRIENDSANITY -188,Kent: 1 <3,progression,FRIENDSANITY -189,Krobus: 1 <3,progression,FRIENDSANITY -190,Leo: 1 <3,progression,FRIENDSANITY -191,Lewis: 1 <3,progression,FRIENDSANITY -192,Linus: 1 <3,progression,FRIENDSANITY -193,Marnie: 1 <3,progression,FRIENDSANITY -194,Pam: 1 <3,progression,FRIENDSANITY -195,Pierre: 1 <3,progression,FRIENDSANITY -196,Robin: 1 <3,progression,FRIENDSANITY -197,Sandy: 1 <3,progression,FRIENDSANITY -198,Vincent: 1 <3,progression,FRIENDSANITY -199,Willy: 1 <3,progression,FRIENDSANITY -200,Wizard: 1 <3,progression,FRIENDSANITY -201,Pet: 1 <3,progression,FRIENDSANITY -5000,Resource Pack: 500 Money,filler,"BASE_RESOURCE,RESOURCE_PACK" -5001,Resource Pack: 1000 Money,filler,"BASE_RESOURCE,RESOURCE_PACK" -5002,Resource Pack: 1500 Money,filler,"BASE_RESOURCE,RESOURCE_PACK" -5003,Resource Pack: 2000 Money,filler,"BASE_RESOURCE,RESOURCE_PACK" -5004,Resource Pack: 25 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK" -5005,Resource Pack: 50 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK" -5006,Resource Pack: 75 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK" -5007,Resource Pack: 100 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK" -5008,Resource Pack: 25 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5009,Resource Pack: 50 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5010,Resource Pack: 75 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5011,Resource Pack: 100 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5012,Resource Pack: 5 Hardwood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5013,Resource Pack: 10 Hardwood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5014,Resource Pack: 15 Hardwood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5015,Resource Pack: 20 Hardwood,filler,"BASE_RESOURCE,RESOURCE_PACK" -5016,Resource Pack: 15 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK" -5017,Resource Pack: 30 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK" -5018,Resource Pack: 45 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK" -5019,Resource Pack: 60 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK" -5020,Resource Pack: 5 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK" -5021,Resource Pack: 10 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK" -5022,Resource Pack: 15 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK" -5023,Resource Pack: 20 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK" -5024,Resource Pack: 5 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK" -5025,Resource Pack: 10 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK" -5026,Resource Pack: 15 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK" -5027,Resource Pack: 20 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK" -5028,Resource Pack: 1 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM" -5029,Resource Pack: 3 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM" -5030,Resource Pack: 5 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM" -5031,Resource Pack: 7 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM" -5032,Resource Pack: 9 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM" -5033,Resource Pack: 10 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM" -5034,Resource Pack: 1 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM" -5035,Resource Pack: 3 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM" -5036,Resource Pack: 5 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM" -5037,Resource Pack: 7 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM" -5038,Resource Pack: 9 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM" -5039,Resource Pack: 10 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM" -5040,Resource Pack: 1 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM" -5041,Resource Pack: 3 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM" -5042,Resource Pack: 5 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM" -5043,Resource Pack: 7 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM" -5044,Resource Pack: 9 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM" -5045,Resource Pack: 10 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM" -5046,Resource Pack: 1 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM" -5047,Resource Pack: 3 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM" -5048,Resource Pack: 5 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM" -5049,Resource Pack: 7 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM" -5050,Resource Pack: 9 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM" -5051,Resource Pack: 10 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM" -5052,Resource Pack: 1 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM" -5053,Resource Pack: 3 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM" -5054,Resource Pack: 5 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM" -5055,Resource Pack: 7 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM" -5056,Resource Pack: 9 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM" -5057,Resource Pack: 10 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM" -5058,Resource Pack: 6 Geode,filler,"GEODE,RESOURCE_PACK" -5059,Resource Pack: 12 Geode,filler,"GEODE,RESOURCE_PACK" -5060,Resource Pack: 18 Geode,filler,"GEODE,RESOURCE_PACK" -5061,Resource Pack: 24 Geode,filler,"GEODE,RESOURCE_PACK" -5062,Resource Pack: 4 Frozen Geode,filler,"GEODE,RESOURCE_PACK" -5063,Resource Pack: 8 Frozen Geode,filler,"GEODE,RESOURCE_PACK" -5064,Resource Pack: 12 Frozen Geode,filler,"GEODE,RESOURCE_PACK" -5065,Resource Pack: 16 Frozen Geode,filler,"GEODE,RESOURCE_PACK" -5066,Resource Pack: 3 Magma Geode,filler,"GEODE,RESOURCE_PACK" -5067,Resource Pack: 6 Magma Geode,filler,"GEODE,RESOURCE_PACK" -5068,Resource Pack: 9 Magma Geode,filler,"GEODE,RESOURCE_PACK" -5069,Resource Pack: 12 Magma Geode,filler,"GEODE,RESOURCE_PACK" -5070,Resource Pack: 2 Omni Geode,filler,"GEODE,RESOURCE_PACK" -5071,Resource Pack: 4 Omni Geode,filler,"GEODE,RESOURCE_PACK" -5072,Resource Pack: 6 Omni Geode,filler,"GEODE,RESOURCE_PACK" -5073,Resource Pack: 8 Omni Geode,filler,"GEODE,RESOURCE_PACK" -5074,Resource Pack: 25 Copper Ore,filler,"ORE,RESOURCE_PACK" -5075,Resource Pack: 50 Copper Ore,filler,"ORE,RESOURCE_PACK" -5076,Resource Pack: 75 Copper Ore,filler,"ORE,RESOURCE_PACK" -5077,Resource Pack: 100 Copper Ore,filler,"ORE,RESOURCE_PACK" -5078,Resource Pack: 125 Copper Ore,filler,"ORE,RESOURCE_PACK" -5079,Resource Pack: 150 Copper Ore,filler,"ORE,RESOURCE_PACK" -5080,Resource Pack: 25 Iron Ore,filler,"ORE,RESOURCE_PACK" -5081,Resource Pack: 50 Iron Ore,filler,"ORE,RESOURCE_PACK" -5082,Resource Pack: 75 Iron Ore,filler,"ORE,RESOURCE_PACK" -5083,Resource Pack: 100 Iron Ore,filler,"ORE,RESOURCE_PACK" -5084,Resource Pack: 12 Gold Ore,filler,"ORE,RESOURCE_PACK" -5085,Resource Pack: 25 Gold Ore,filler,"ORE,RESOURCE_PACK" -5086,Resource Pack: 38 Gold Ore,filler,"ORE,RESOURCE_PACK" -5087,Resource Pack: 50 Gold Ore,filler,"ORE,RESOURCE_PACK" -5088,Resource Pack: 5 Iridium Ore,filler,"ORE,RESOURCE_PACK" -5089,Resource Pack: 10 Iridium Ore,filler,"ORE,RESOURCE_PACK" -5090,Resource Pack: 15 Iridium Ore,filler,"ORE,RESOURCE_PACK" -5091,Resource Pack: 20 Iridium Ore,filler,"ORE,RESOURCE_PACK" -5092,Resource Pack: 5 Quartz,filler,"ORE,RESOURCE_PACK" -5093,Resource Pack: 10 Quartz,filler,"ORE,RESOURCE_PACK" -5094,Resource Pack: 15 Quartz,filler,"ORE,RESOURCE_PACK" -5095,Resource Pack: 20 Quartz,filler,"ORE,RESOURCE_PACK" -5096,Resource Pack: 10 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5097,Resource Pack: 20 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5098,Resource Pack: 30 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5099,Resource Pack: 40 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5100,Resource Pack: 50 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5101,Resource Pack: 60 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5102,Resource Pack: 10 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5103,Resource Pack: 20 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5104,Resource Pack: 30 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5105,Resource Pack: 40 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5106,Resource Pack: 50 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5107,Resource Pack: 60 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5108,Resource Pack: 10 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5109,Resource Pack: 20 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5110,Resource Pack: 30 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5111,Resource Pack: 40 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5112,Resource Pack: 50 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5113,Resource Pack: 60 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5114,Resource Pack: 4 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5115,Resource Pack: 12 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5116,Resource Pack: 20 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5117,Resource Pack: 28 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5118,Resource Pack: 36 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5119,Resource Pack: 40 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5120,Resource Pack: 4 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5121,Resource Pack: 12 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5122,Resource Pack: 20 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5123,Resource Pack: 28 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5124,Resource Pack: 36 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5125,Resource Pack: 40 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5126,Resource Pack: 4 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5127,Resource Pack: 12 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5128,Resource Pack: 20 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5129,Resource Pack: 28 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5130,Resource Pack: 36 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5131,Resource Pack: 40 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5132,Resource Pack: 2 Deluxe Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5133,Resource Pack: 6 Deluxe Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5134,Resource Pack: 10 Deluxe Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5135,Resource Pack: 14 Deluxe Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5136,Resource Pack: 18 Deluxe Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5137,Resource Pack: 20 Deluxe Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5138,Resource Pack: 2 Deluxe Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5139,Resource Pack: 6 Deluxe Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5140,Resource Pack: 10 Deluxe Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5141,Resource Pack: 14 Deluxe Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5142,Resource Pack: 18 Deluxe Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5143,Resource Pack: 20 Deluxe Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK" -5144,Resource Pack: 2 Hyper Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5145,Resource Pack: 6 Hyper Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5146,Resource Pack: 10 Hyper Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5147,Resource Pack: 14 Hyper Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5148,Resource Pack: 18 Hyper Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5149,Resource Pack: 20 Hyper Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK" -5150,Resource Pack: 2 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5151,Resource Pack: 6 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5152,Resource Pack: 10 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5153,Resource Pack: 14 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5154,Resource Pack: 18 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5155,Resource Pack: 20 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK" -5156,Resource Pack: 10 Spring Seeds,filler,"RESOURCE_PACK,SEED" -5157,Resource Pack: 20 Spring Seeds,filler,"RESOURCE_PACK,SEED" -5158,Resource Pack: 30 Spring Seeds,filler,"RESOURCE_PACK,SEED" -5159,Resource Pack: 40 Spring Seeds,filler,"RESOURCE_PACK,SEED" -5160,Resource Pack: 50 Spring Seeds,filler,"RESOURCE_PACK,SEED" -5161,Resource Pack: 60 Spring Seeds,filler,"RESOURCE_PACK,SEED" -5162,Resource Pack: 10 Summer Seeds,filler,"RESOURCE_PACK,SEED" -5163,Resource Pack: 20 Summer Seeds,filler,"RESOURCE_PACK,SEED" -5164,Resource Pack: 30 Summer Seeds,filler,"RESOURCE_PACK,SEED" -5165,Resource Pack: 40 Summer Seeds,filler,"RESOURCE_PACK,SEED" -5166,Resource Pack: 50 Summer Seeds,filler,"RESOURCE_PACK,SEED" -5167,Resource Pack: 60 Summer Seeds,filler,"RESOURCE_PACK,SEED" -5168,Resource Pack: 10 Fall Seeds,filler,"RESOURCE_PACK,SEED" -5169,Resource Pack: 20 Fall Seeds,filler,"RESOURCE_PACK,SEED" -5170,Resource Pack: 30 Fall Seeds,filler,"RESOURCE_PACK,SEED" -5171,Resource Pack: 40 Fall Seeds,filler,"RESOURCE_PACK,SEED" -5172,Resource Pack: 50 Fall Seeds,filler,"RESOURCE_PACK,SEED" -5173,Resource Pack: 60 Fall Seeds,filler,"RESOURCE_PACK,SEED" -5174,Resource Pack: 10 Winter Seeds,filler,"RESOURCE_PACK,SEED" -5175,Resource Pack: 20 Winter Seeds,filler,"RESOURCE_PACK,SEED" -5176,Resource Pack: 30 Winter Seeds,filler,"RESOURCE_PACK,SEED" -5177,Resource Pack: 40 Winter Seeds,filler,"RESOURCE_PACK,SEED" -5178,Resource Pack: 50 Winter Seeds,filler,"RESOURCE_PACK,SEED" -5179,Resource Pack: 60 Winter Seeds,filler,"RESOURCE_PACK,SEED" -5180,Resource Pack: 1 Mahogany Seed,filler,"RESOURCE_PACK,SEED" -5181,Resource Pack: 3 Mahogany Seed,filler,"RESOURCE_PACK,SEED" -5182,Resource Pack: 5 Mahogany Seed,filler,"RESOURCE_PACK,SEED" -5183,Resource Pack: 7 Mahogany Seed,filler,"RESOURCE_PACK,SEED" -5184,Resource Pack: 9 Mahogany Seed,filler,"RESOURCE_PACK,SEED" -5185,Resource Pack: 10 Mahogany Seed,filler,"RESOURCE_PACK,SEED" -5186,Resource Pack: 10 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5187,Resource Pack: 20 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5188,Resource Pack: 30 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5189,Resource Pack: 40 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5190,Resource Pack: 50 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5191,Resource Pack: 60 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5192,Resource Pack: 1 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5193,Resource Pack: 2 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5194,Resource Pack: 3 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5195,Resource Pack: 4 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5196,Resource Pack: 5 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5197,Resource Pack: 6 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK" -5198,Friendship Bonus (1 <3),filler,FRIENDSHIP_PACK -5199,Friendship Bonus (2 <3),filler,FRIENDSHIP_PACK -5200,Friendship Bonus (3 <3),filler,FRIENDSHIP_PACK -5201,Friendship Bonus (4 <3),filler,FRIENDSHIP_PACK +id,name,classification,groups,mod_name +0,Joja Cola,filler,TRASH, +15,Rusty Key,progression,MUSEUM, +16,Dwarvish Translation Guide,progression,MUSEUM, +17,Bridge Repair,progression,COMMUNITY_REWARD, +18,Greenhouse,progression,COMMUNITY_REWARD, +19,Glittering Boulder Removed,progression,COMMUNITY_REWARD, +20,Minecarts Repair,useful,COMMUNITY_REWARD, +21,Bus Repair,progression,COMMUNITY_REWARD, +22,Movie Theater,useful,, +23,Stardrop,progression,, +24,Progressive Backpack,progression,, +25,Rusty Sword,progression,WEAPON, +26,Leather Boots,progression,"FOOTWEAR,MINES_FLOOR_10", +27,Work Boots,useful,"FOOTWEAR,MINES_FLOOR_10", +28,Wooden Blade,progression,"MINES_FLOOR_10,WEAPON", +29,Iron Dirk,progression,"MINES_FLOOR_10,WEAPON", +30,Wind Spire,progression,"MINES_FLOOR_10,WEAPON", +31,Femur,progression,"MINES_FLOOR_10,WEAPON", +32,Steel Smallsword,progression,"MINES_FLOOR_20,WEAPON", +33,Wood Club,progression,"MINES_FLOOR_20,WEAPON", +34,Elf Blade,progression,"MINES_FLOOR_20,WEAPON", +35,Glow Ring,useful,"MINES_FLOOR_20,RING", +36,Magnet Ring,useful,"MINES_FLOOR_20,RING", +37,Slingshot,progression,WEAPON, +38,Tundra Boots,useful,"FOOTWEAR,MINES_FLOOR_50", +39,Thermal Boots,useful,"FOOTWEAR,MINES_FLOOR_50", +40,Combat Boots,useful,"FOOTWEAR,MINES_FLOOR_50", +41,Silver Saber,progression,"MINES_FLOOR_50,WEAPON", +42,Pirate's Sword,progression,"MINES_FLOOR_50,WEAPON", +43,Crystal Dagger,progression,"MINES_FLOOR_60,WEAPON", +44,Cutlass,progression,"MINES_FLOOR_60,WEAPON", +45,Iron Edge,progression,"MINES_FLOOR_60,WEAPON", +46,Burglar's Shank,progression,"MINES_FLOOR_60,WEAPON", +47,Wood Mallet,progression,"MINES_FLOOR_60,WEAPON", +48,Master Slingshot,progression,WEAPON, +49,Firewalker Boots,useful,"FOOTWEAR,MINES_FLOOR_80", +50,Dark Boots,useful,"FOOTWEAR,MINES_FLOOR_80", +51,Claymore,progression,"MINES_FLOOR_80,WEAPON", +52,Templar's Blade,progression,"MINES_FLOOR_80,WEAPON", +53,Kudgel,progression,"MINES_FLOOR_80,WEAPON", +54,Shadow Dagger,progression,"MINES_FLOOR_80,WEAPON", +55,Obsidian Edge,progression,"MINES_FLOOR_90,WEAPON", +56,Tempered Broadsword,progression,"MINES_FLOOR_90,WEAPON", +57,Wicked Kris,progression,"MINES_FLOOR_90,WEAPON", +58,Bone Sword,progression,"MINES_FLOOR_90,WEAPON", +59,Ossified Blade,progression,"MINES_FLOOR_90,WEAPON", +60,Space Boots,useful,"FOOTWEAR,MINES_FLOOR_110", +61,Crystal Shoes,useful,"FOOTWEAR,MINES_FLOOR_110", +62,Steel Falchion,progression,"MINES_FLOOR_110,WEAPON", +63,The Slammer,progression,"MINES_FLOOR_110,WEAPON", +64,Skull Key,progression,, +65,Progressive Hoe,progression,PROGRESSIVE_TOOLS, +66,Progressive Pickaxe,progression,PROGRESSIVE_TOOLS, +67,Progressive Axe,progression,PROGRESSIVE_TOOLS, +68,Progressive Watering Can,progression,PROGRESSIVE_TOOLS, +69,Progressive Trash Can,progression,PROGRESSIVE_TOOLS, +70,Progressive Fishing Rod,progression,PROGRESSIVE_TOOLS, +71,Golden Scythe,useful,, +72,Progressive Mine Elevator,progression,, +73,Farming Level,progression,SKILL_LEVEL_UP, +74,Fishing Level,progression,SKILL_LEVEL_UP, +75,Foraging Level,progression,SKILL_LEVEL_UP, +76,Mining Level,progression,SKILL_LEVEL_UP, +77,Combat Level,progression,SKILL_LEVEL_UP, +78,Earth Obelisk,progression,, +79,Water Obelisk,progression,, +80,Desert Obelisk,progression,, +81,Island Obelisk,progression,GINGER_ISLAND, +82,Junimo Hut,useful,, +83,Gold Clock,progression,, +84,Progressive Coop,progression,, +85,Progressive Barn,progression,, +86,Well,useful,, +87,Silo,progression,, +88,Mill,progression,, +89,Progressive Shed,progression,, +90,Fish Pond,progression,, +91,Stable,useful,, +92,Slime Hutch,useful,, +93,Shipping Bin,progression,, +94,Beach Bridge,progression,, +95,Adventurer's Guild,progression,, +96,Club Card,progression,, +97,Magnifying Glass,progression,, +98,Bear's Knowledge,progression,, +99,Iridium Snake Milk,progression,, +100,JotPK: Progressive Boots,progression,ARCADE_MACHINE_BUFFS, +101,JotPK: Progressive Gun,progression,ARCADE_MACHINE_BUFFS, +102,JotPK: Progressive Ammo,progression,ARCADE_MACHINE_BUFFS, +103,JotPK: Extra Life,progression,ARCADE_MACHINE_BUFFS, +104,JotPK: Increased Drop Rate,progression,ARCADE_MACHINE_BUFFS, +105,Junimo Kart: Extra Life,progression,ARCADE_MACHINE_BUFFS, +106,Galaxy Sword,progression,"GALAXY_WEAPONS,WEAPON", +107,Galaxy Dagger,progression,"GALAXY_WEAPONS,WEAPON", +108,Galaxy Hammer,progression,"GALAXY_WEAPONS,WEAPON", +109,Movement Speed Bonus,progression,, +110,Luck Bonus,progression,, +111,Lava Katana,progression,"MINES_FLOOR_110,WEAPON", +112,Progressive House,progression,, +113,Traveling Merchant: Sunday,progression,TRAVELING_MERCHANT_DAY, +114,Traveling Merchant: Monday,progression,TRAVELING_MERCHANT_DAY, +115,Traveling Merchant: Tuesday,progression,TRAVELING_MERCHANT_DAY, +116,Traveling Merchant: Wednesday,progression,TRAVELING_MERCHANT_DAY, +117,Traveling Merchant: Thursday,progression,TRAVELING_MERCHANT_DAY, +118,Traveling Merchant: Friday,progression,TRAVELING_MERCHANT_DAY, +119,Traveling Merchant: Saturday,progression,TRAVELING_MERCHANT_DAY, +120,Traveling Merchant Stock Size,progression,, +121,Traveling Merchant Discount,progression,, +122,Return Scepter,useful,, +123,Progressive Season,progression,, +124,Spring,progression,SEASON, +125,Summer,progression,SEASON, +126,Fall,progression,SEASON, +127,Winter,progression,SEASON, +128,Amaranth Seeds,progression,CROPSANITY, +129,Artichoke Seeds,progression,CROPSANITY, +130,Beet Seeds,progression,CROPSANITY, +131,Jazz Seeds,progression,CROPSANITY, +132,Blueberry Seeds,progression,CROPSANITY, +133,Bok Choy Seeds,progression,CROPSANITY, +134,Cauliflower Seeds,progression,CROPSANITY, +135,Corn Seeds,progression,CROPSANITY, +136,Cranberry Seeds,progression,CROPSANITY, +137,Eggplant Seeds,progression,CROPSANITY, +138,Fairy Seeds,progression,CROPSANITY, +139,Garlic Seeds,progression,CROPSANITY, +140,Grape Starter,progression,CROPSANITY, +141,Bean Starter,progression,CROPSANITY, +142,Hops Starter,progression,CROPSANITY, +143,Pepper Seeds,progression,CROPSANITY, +144,Kale Seeds,progression,CROPSANITY, +145,Melon Seeds,progression,CROPSANITY, +146,Parsnip Seeds,progression,CROPSANITY, +147,Poppy Seeds,progression,CROPSANITY, +148,Potato Seeds,progression,CROPSANITY, +149,Pumpkin Seeds,progression,CROPSANITY, +150,Radish Seeds,progression,CROPSANITY, +151,Red Cabbage Seeds,progression,CROPSANITY, +152,Rhubarb Seeds,progression,CROPSANITY, +153,Starfruit Seeds,progression,CROPSANITY, +154,Strawberry Seeds,progression,CROPSANITY, +155,Spangle Seeds,progression,CROPSANITY, +156,Sunflower Seeds,progression,CROPSANITY, +157,Tomato Seeds,progression,CROPSANITY, +158,Tulip Bulb,progression,CROPSANITY, +159,Rice Shoot,progression,CROPSANITY, +160,Wheat Seeds,progression,CROPSANITY, +161,Yam Seeds,progression,CROPSANITY, +162,Cactus Seeds,progression,CROPSANITY, +163,Magic Rock Candy,useful,"MUSEUM,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +164,Ancient Seeds Recipe,progression,MUSEUM, +165,Ancient Seeds,progression,"MUSEUM,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +166,Traveling Merchant Metal Detector,progression,MUSEUM, +167,Alex <3,progression,FRIENDSANITY, +168,Elliott <3,progression,FRIENDSANITY, +169,Harvey <3,progression,FRIENDSANITY, +170,Sam <3,progression,FRIENDSANITY, +171,Sebastian <3,progression,FRIENDSANITY, +172,Shane <3,progression,FRIENDSANITY, +173,Abigail <3,progression,FRIENDSANITY, +174,Emily <3,progression,FRIENDSANITY, +175,Haley <3,progression,FRIENDSANITY, +176,Leah <3,progression,FRIENDSANITY, +177,Maru <3,progression,FRIENDSANITY, +178,Penny <3,progression,FRIENDSANITY, +179,Caroline <3,progression,FRIENDSANITY, +180,Clint <3,progression,FRIENDSANITY, +181,Demetrius <3,progression,FRIENDSANITY, +182,Dwarf <3,progression,FRIENDSANITY, +183,Evelyn <3,progression,FRIENDSANITY, +184,George <3,progression,FRIENDSANITY, +185,Gus <3,progression,FRIENDSANITY, +186,Jas <3,progression,FRIENDSANITY, +187,Jodi <3,progression,FRIENDSANITY, +188,Kent <3,progression,FRIENDSANITY, +189,Krobus <3,progression,FRIENDSANITY, +190,Leo <3,progression,"FRIENDSANITY,GINGER_ISLAND", +191,Lewis <3,progression,FRIENDSANITY, +192,Linus <3,progression,FRIENDSANITY, +193,Marnie <3,progression,FRIENDSANITY, +194,Pam <3,progression,FRIENDSANITY, +195,Pierre <3,progression,FRIENDSANITY, +196,Robin <3,progression,FRIENDSANITY, +197,Sandy <3,progression,FRIENDSANITY, +198,Vincent <3,progression,FRIENDSANITY, +199,Willy <3,progression,FRIENDSANITY, +200,Wizard <3,progression,FRIENDSANITY, +201,Pet <3,progression,FRIENDSANITY, +202,Rarecrow #1,progression,"FESTIVAL,RARECROW", +203,Rarecrow #2,progression,"FESTIVAL,RARECROW", +204,Rarecrow #3,progression,"FESTIVAL,RARECROW", +205,Rarecrow #4,progression,"FESTIVAL,RARECROW", +206,Rarecrow #5,progression,"FESTIVAL,RARECROW", +207,Rarecrow #6,progression,"FESTIVAL,RARECROW", +208,Rarecrow #7,progression,"FESTIVAL,RARECROW", +209,Rarecrow #8,progression,"FESTIVAL,RARECROW", +210,Straw Hat,filler,FESTIVAL, +211,Golden Pumpkin,useful,FESTIVAL, +212,Barbed Hook,useful,FESTIVAL, +213,Dressed Spinner,useful,FESTIVAL, +214,Magnet,useful,FESTIVAL, +215,Sailor's Cap,filler,FESTIVAL, +216,Pearl,useful,FESTIVAL, +217,Cone Hat,filler,FESTIVAL, +218,Iridium Fireplace,filler,FESTIVAL, +219,Lupini: Red Eagle,filler,FESTIVAL, +220,Lupini: Portrait Of A Mermaid,filler,FESTIVAL, +221,Lupini: Solar Kingdom,filler,FESTIVAL, +222,Lupini: Clouds,filler,FESTIVAL, +223,Lupini: 1000 Years From Now,filler,FESTIVAL, +224,Lupini: Three Trees,filler,FESTIVAL, +225,Lupini: The Serpent,filler,FESTIVAL, +226,Lupini: 'Tropical Fish #173',filler,FESTIVAL, +227,Lupini: Land Of Clay,filler,FESTIVAL, +228,Special Order Board,progression,SPECIAL_ORDER_BOARD, +229,Solar Panel Recipe,progression,SPECIAL_ORDER_BOARD, +230,Geode Crusher Recipe,progression,SPECIAL_ORDER_BOARD, +231,Farm Computer Recipe,progression,SPECIAL_ORDER_BOARD, +232,Bone Mill Recipe,progression,SPECIAL_ORDER_BOARD, +233,Fiber Seeds Recipe,progression,SPECIAL_ORDER_BOARD, +234,Stone Chest Recipe,progression,SPECIAL_ORDER_BOARD, +235,Quality Bobber Recipe,progression,SPECIAL_ORDER_BOARD, +236,Mini-Obelisk Recipe,progression,SPECIAL_ORDER_BOARD, +237,Monster Musk Recipe,progression,SPECIAL_ORDER_BOARD, +239,Sewing Machine,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", +240,Coffee Maker,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", +241,Mini-Fridge,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", +242,Mini-Shipping Bin,progression,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", +243,Deluxe Fish Tank,useful,"RESOURCE_PACK_USEFUL,SPECIAL_ORDER_BOARD", +244,10 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +245,20 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +246,25 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +247,35 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +248,40 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +249,50 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +250,100 Qi Gems,progression,"GINGER_ISLAND,SPECIAL_ORDER_QI", +251,Rare Seed,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +252,Apple Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +253,Apricot Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +254,Cherry Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +255,Orange Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +256,Pomegranate Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +257,Peach Sapling,progression,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +258,Banana Sapling,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +259,Mango Sapling,progression,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL,CROPSANITY", +260,Boat Repair,progression,GINGER_ISLAND, +261,Open Professor Snail Cave,progression,"GINGER_ISLAND", +262,Island North Turtle,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +263,Island West Turtle,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +264,Island Farmhouse,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +265,Island Mailbox,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +266,Farm Obelisk,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +267,Dig Site Bridge,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +268,Island Trader,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +269,Volcano Bridge,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +270,Volcano Exit Shortcut,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +271,Island Resort,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +272,Parrot Express,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +273,Qi Walnut Room,progression,"GINGER_ISLAND,WALNUT_PURCHASE", +274,Pineapple Seeds,progression,"GINGER_ISLAND,CROPSANITY", +275,Taro Tuber,progression,"GINGER_ISLAND,CROPSANITY", +276,Weather Report,useful,"TV_CHANNEL", +277,Fortune Teller,useful,"TV_CHANNEL", +278,Livin' Off The Land,useful,"TV_CHANNEL", +279,The Queen of Sauce,progression,"TV_CHANNEL", +280,Fishing Information Broadcasting Service,useful,"TV_CHANNEL", +281,Sinister Signal,useful,"TV_CHANNEL", +282,Dark Talisman,progression,, +283,Ostrich Incubator Recipe,progression,"GINGER_ISLAND", +284,Cute Baby,progression,"BABY", +285,Ugly Baby,progression,"BABY", +286,Deluxe Scarecrow Recipe,progression,"FESTIVAL,RARECROW", +4001,Burnt,trap,TRAP, +4002,Darkness,trap,TRAP, +4003,Frozen,trap,TRAP, +4004,Jinxed,trap,TRAP, +4005,Nauseated,trap,TRAP, +4006,Slimed,trap,TRAP, +4007,Weakness,trap,TRAP, +4008,Taxes,trap,TRAP, +4009,Random Teleport,trap,TRAP, +4010,The Crows,trap,TRAP, +4011,Monsters,trap,TRAP, +4012,Entrance Reshuffle,trap,"TRAP,DEPRECATED", +4013,Debris,trap,TRAP, +4014,Shuffle,trap,TRAP, +4015,Temporary Winter,trap,"TRAP,DEPRECATED", +4016,Pariah,trap,TRAP, +4017,Drought,trap,TRAP, +5000,Resource Pack: 500 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", +5001,Resource Pack: 1000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", +5002,Resource Pack: 1500 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", +5003,Resource Pack: 2000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK", +5004,Resource Pack: 25 Stone,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5005,Resource Pack: 50 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK", +5006,Resource Pack: 75 Stone,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5007,Resource Pack: 100 Stone,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5008,Resource Pack: 25 Wood,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5009,Resource Pack: 50 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK", +5010,Resource Pack: 75 Wood,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5011,Resource Pack: 100 Wood,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5012,Resource Pack: 5 Hardwood,useful,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5013,Resource Pack: 10 Hardwood,useful,"BASE_RESOURCE,RESOURCE_PACK", +5014,Resource Pack: 15 Hardwood,useful,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5015,Resource Pack: 20 Hardwood,useful,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5016,Resource Pack: 15 Fiber,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5017,Resource Pack: 30 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK", +5018,Resource Pack: 45 Fiber,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5019,Resource Pack: 60 Fiber,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5020,Resource Pack: 5 Coal,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5021,Resource Pack: 10 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK", +5022,Resource Pack: 15 Coal,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5023,Resource Pack: 20 Coal,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5024,Resource Pack: 5 Clay,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5025,Resource Pack: 10 Clay,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5026,Resource Pack: 15 Clay,filler,"DEPRECATED,BASE_RESOURCE,RESOURCE_PACK", +5027,Resource Pack: 20 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK", +5028,Resource Pack: 1 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5029,Resource Pack: 3 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5030,Resource Pack: 5 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM", +5031,Resource Pack: 7 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5032,Resource Pack: 9 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5033,Resource Pack: 10 Warp Totem: Beach,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5034,Resource Pack: 1 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5035,Resource Pack: 3 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5036,Resource Pack: 5 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM", +5037,Resource Pack: 7 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5038,Resource Pack: 9 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5039,Resource Pack: 10 Warp Totem: Desert,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5040,Resource Pack: 1 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5041,Resource Pack: 3 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5042,Resource Pack: 5 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM", +5043,Resource Pack: 7 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5044,Resource Pack: 9 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5045,Resource Pack: 10 Warp Totem: Farm,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5046,Resource Pack: 1 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5047,Resource Pack: 3 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5048,Resource Pack: 5 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM", +5049,Resource Pack: 7 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5050,Resource Pack: 9 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5051,Resource Pack: 10 Warp Totem: Island,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5052,Resource Pack: 1 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5053,Resource Pack: 3 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5054,Resource Pack: 5 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM", +5055,Resource Pack: 7 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5056,Resource Pack: 9 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5057,Resource Pack: 10 Warp Totem: Mountains,filler,"DEPRECATED,RESOURCE_PACK,WARP_TOTEM", +5058,Resource Pack: 6 Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5059,Resource Pack: 12 Geode,filler,"GEODE,RESOURCE_PACK", +5060,Resource Pack: 18 Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5061,Resource Pack: 24 Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5062,Resource Pack: 4 Frozen Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5063,Resource Pack: 8 Frozen Geode,filler,"GEODE,RESOURCE_PACK", +5064,Resource Pack: 12 Frozen Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5065,Resource Pack: 16 Frozen Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5066,Resource Pack: 3 Magma Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5067,Resource Pack: 6 Magma Geode,filler,"GEODE,RESOURCE_PACK", +5068,Resource Pack: 9 Magma Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5069,Resource Pack: 12 Magma Geode,filler,"DEPRECATED,GEODE,RESOURCE_PACK", +5070,Resource Pack: 2 Omni Geode,useful,"DEPRECATED,GEODE,RESOURCE_PACK", +5071,Resource Pack: 4 Omni Geode,useful,"GEODE,RESOURCE_PACK", +5072,Resource Pack: 6 Omni Geode,useful,"DEPRECATED,GEODE,RESOURCE_PACK", +5073,Resource Pack: 8 Omni Geode,useful,"DEPRECATED,GEODE,RESOURCE_PACK", +5074,Resource Pack: 25 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5075,Resource Pack: 50 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5076,Resource Pack: 75 Copper Ore,filler,"ORE,RESOURCE_PACK", +5077,Resource Pack: 100 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5078,Resource Pack: 125 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5079,Resource Pack: 150 Copper Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5080,Resource Pack: 25 Iron Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5081,Resource Pack: 50 Iron Ore,filler,"ORE,RESOURCE_PACK", +5082,Resource Pack: 75 Iron Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5083,Resource Pack: 100 Iron Ore,filler,"DEPRECATED,ORE,RESOURCE_PACK", +5084,Resource Pack: 12 Gold Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", +5085,Resource Pack: 25 Gold Ore,useful,"ORE,RESOURCE_PACK", +5086,Resource Pack: 38 Gold Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", +5087,Resource Pack: 50 Gold Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", +5088,Resource Pack: 5 Iridium Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", +5089,Resource Pack: 10 Iridium Ore,useful,"ORE,RESOURCE_PACK", +5090,Resource Pack: 15 Iridium Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", +5091,Resource Pack: 20 Iridium Ore,useful,"DEPRECATED,ORE,RESOURCE_PACK", +5092,Resource Pack: 5 Quartz,filler,"ORE,RESOURCE_PACK", +5093,Resource Pack: 10 Quartz,filler,"ORE,DEPRECATED,RESOURCE_PACK", +5094,Resource Pack: 15 Quartz,filler,"ORE,DEPRECATED,RESOURCE_PACK", +5095,Resource Pack: 20 Quartz,filler,"ORE,DEPRECATED,RESOURCE_PACK", +5096,Resource Pack: 10 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5097,Resource Pack: 20 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5098,Resource Pack: 30 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK", +5099,Resource Pack: 40 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5100,Resource Pack: 50 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5101,Resource Pack: 60 Basic Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5102,Resource Pack: 10 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5103,Resource Pack: 20 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5104,Resource Pack: 30 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK", +5105,Resource Pack: 40 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5106,Resource Pack: 50 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5107,Resource Pack: 60 Basic Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5108,Resource Pack: 10 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5109,Resource Pack: 20 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5110,Resource Pack: 30 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK", +5111,Resource Pack: 40 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5112,Resource Pack: 50 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5113,Resource Pack: 60 Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5114,Resource Pack: 4 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5115,Resource Pack: 12 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5116,Resource Pack: 20 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK", +5117,Resource Pack: 28 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5118,Resource Pack: 36 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5119,Resource Pack: 40 Quality Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5120,Resource Pack: 4 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5121,Resource Pack: 12 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5122,Resource Pack: 20 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK", +5123,Resource Pack: 28 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5124,Resource Pack: 36 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5125,Resource Pack: 40 Quality Retaining Soil,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5126,Resource Pack: 4 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5127,Resource Pack: 12 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5128,Resource Pack: 20 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK", +5129,Resource Pack: 28 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5130,Resource Pack: 36 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5131,Resource Pack: 40 Deluxe Speed-Gro,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5132,Resource Pack: 2 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5133,Resource Pack: 6 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5134,Resource Pack: 10 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK", +5135,Resource Pack: 14 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5136,Resource Pack: 18 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5137,Resource Pack: 20 Deluxe Fertilizer,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5138,Resource Pack: 2 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5139,Resource Pack: 6 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5140,Resource Pack: 10 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK", +5141,Resource Pack: 14 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5142,Resource Pack: 18 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5143,Resource Pack: 20 Deluxe Retaining Soil,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5144,Resource Pack: 2 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5145,Resource Pack: 6 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5146,Resource Pack: 10 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK", +5147,Resource Pack: 14 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5148,Resource Pack: 18 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5149,Resource Pack: 20 Hyper Speed-Gro,useful,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5150,Resource Pack: 2 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5151,Resource Pack: 6 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5152,Resource Pack: 10 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK", +5153,Resource Pack: 14 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5154,Resource Pack: 18 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5155,Resource Pack: 20 Tree Fertilizer,filler,"DEPRECATED,FERTILIZER,RESOURCE_PACK", +5156,Resource Pack: 10 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5157,Resource Pack: 20 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5158,Resource Pack: 30 Spring Seeds,filler,"RESOURCE_PACK,SEED", +5159,Resource Pack: 40 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5160,Resource Pack: 50 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5161,Resource Pack: 60 Spring Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5162,Resource Pack: 10 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5163,Resource Pack: 20 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5164,Resource Pack: 30 Summer Seeds,filler,"RESOURCE_PACK,SEED", +5165,Resource Pack: 40 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5166,Resource Pack: 50 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5167,Resource Pack: 60 Summer Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5168,Resource Pack: 10 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5169,Resource Pack: 20 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5170,Resource Pack: 30 Fall Seeds,filler,"RESOURCE_PACK,SEED", +5171,Resource Pack: 40 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5172,Resource Pack: 50 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5173,Resource Pack: 60 Fall Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5174,Resource Pack: 10 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5175,Resource Pack: 20 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5176,Resource Pack: 30 Winter Seeds,filler,"RESOURCE_PACK,SEED", +5177,Resource Pack: 40 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5178,Resource Pack: 50 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5179,Resource Pack: 60 Winter Seeds,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5180,Resource Pack: 1 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5181,Resource Pack: 3 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5182,Resource Pack: 5 Mahogany Seed,filler,"RESOURCE_PACK,SEED", +5183,Resource Pack: 7 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5184,Resource Pack: 9 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5185,Resource Pack: 10 Mahogany Seed,filler,"DEPRECATED,RESOURCE_PACK,SEED", +5186,Resource Pack: 10 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5187,Resource Pack: 20 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5188,Resource Pack: 30 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK", +5189,Resource Pack: 40 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5190,Resource Pack: 50 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5191,Resource Pack: 60 Bait,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5192,Resource Pack: 1 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5193,Resource Pack: 2 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5194,Resource Pack: 3 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK", +5195,Resource Pack: 4 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5196,Resource Pack: 5 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5197,Resource Pack: 6 Crab Pot,filler,"DEPRECATED,FISHING_RESOURCE,RESOURCE_PACK", +5198,Friendship Bonus (1 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK", +5199,Friendship Bonus (2 <3),useful,"FRIENDSHIP_PACK,COMMUNITY_REWARD", +5200,Friendship Bonus (3 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK", +5201,Friendship Bonus (4 <3),useful,"DEPRECATED,FRIENDSHIP_PACK,RESOURCE_PACK", +5202,30 Qi Gems,useful,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5203,Solar Panel,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5204,Geode Crusher,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5205,Farm Computer,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5206,Bone Mill,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5207,Fiber Seeds,filler,RESOURCE_PACK, +5208,Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5209,Stone Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5210,Quality Bobber,filler,RESOURCE_PACK, +5211,Mini-Obelisk,filler,"EXACTLY_TWO,RESOURCE_PACK", +5212,Monster Musk,filler,RESOURCE_PACK, +5213,Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5214,Quality Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5215,Iridium Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5216,Scarecrow,filler,RESOURCE_PACK, +5217,Deluxe Scarecrow,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5218,Furnace,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5219,Charcoal Kiln,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5220,Lightning Rod,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5221,Resource Pack: 5000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5222,Resource Pack: 10000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5223,Junimo Chest,filler,"EXACTLY_TWO,RESOURCE_PACK", +5224,Horse Flute,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5225,Pierre's Missing Stocklist,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5226,Hopper,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5227,Enricher,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5228,Pressure Nozzle,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5229,Deconstructor,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5230,Key To The Town,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5231,Galaxy Soul,filler,"GINGER_ISLAND,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5232,Mushroom Tree Seed,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5233,Resource Pack: 20 Magic Bait,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5234,Resource Pack: 10 Qi Seasoning,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5235,Mr. Qi's Hat,filler,"MAXIMUM_ONE,RESOURCE_PACK", +5236,Aquatic Sanctuary,filler,RESOURCE_PACK, +5237,Heavy Tapper Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5238,Hyper Speed-Gro Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5239,Deluxe Fertilizer Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5240,Hopper Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5241,Magic Bait Recipe,filler,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5242,Exotic Double Bed,filler,RESOURCE_PACK, +5243,Resource Pack: 2 Qi Gem,filler,"GINGER_ISLAND,RESOURCE_PACK", +5244,Golden Egg,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5245,Golden Walnut,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL,GINGER_ISLAND", +5246,Fairy Dust Recipe,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5247,Fairy Dust,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5248,Seed Maker,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5249,Keg,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5250,Cask,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5251,Preserves Jar,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5252,Bee House,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5253,Garden Pot,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5254,Cheese Press,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5255,Mayonnaise Machine,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5256,Loom,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5257,Oil Maker,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5258,Recycling Machine,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5259,Worm Bin,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5260,Tapper,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5261,Heavy Tapper,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5262,Slime Incubator,useful,"RESOURCE_PACK", +5263,Slime Egg-Press,useful,"RESOURCE_PACK", +5264,Crystalarium,useful,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", +5265,Ostrich Incubator,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", +10001,Luck Level,progression,SKILL_LEVEL_UP,Luck Skill +10002,Magic Level,progression,SKILL_LEVEL_UP,Magic +10003,Socializing Level,progression,SKILL_LEVEL_UP,Socializing Skill +10004,Archaeology Level,progression,SKILL_LEVEL_UP,Archaeology +10005,Cooking Level,progression,SKILL_LEVEL_UP,Cooking Skill +10006,Binning Level,progression,SKILL_LEVEL_UP,Binning Skill +10007,Tractor Garage,useful,,Tractor Mod +10008,Woods Obelisk,progression,,DeepWoods +10009,Spell: Clear Debris,progression,MAGIC_SPELL,Magic +10010,Spell: Till,useful,MAGIC_SPELL,Magic +10011,Spell: Water,progression,MAGIC_SPELL,Magic +10012,Spell: Blink,progression,MAGIC_SPELL,Magic +10013,Spell: Evac,useful,MAGIC_SPELL,Magic +10014,Spell: Haste,filler,MAGIC_SPELL,Magic +10015,Spell: Heal,progression,MAGIC_SPELL,Magic +10016,Spell: Buff,useful,MAGIC_SPELL,Magic +10017,Spell: Shockwave,progression,MAGIC_SPELL,Magic +10018,Spell: Fireball,progression,MAGIC_SPELL,Magic +10019,Spell: Frostbite,progression,MAGIC_SPELL,Magic +10020,Spell: Teleport,progression,MAGIC_SPELL,Magic +10021,Spell: Lantern,filler,MAGIC_SPELL,Magic +10022,Spell: Tendrils,progression,MAGIC_SPELL,Magic +10023,Spell: Photosynthesis,useful,MAGIC_SPELL,Magic +10024,Spell: Descend,progression,MAGIC_SPELL,Magic +10025,Spell: Meteor,progression,MAGIC_SPELL,Magic +10026,Spell: Bloodmana,useful,MAGIC_SPELL,Magic +10027,Spell: Lucksteal,useful,MAGIC_SPELL,Magic +10028,Spell: Spirit,progression,MAGIC_SPELL,Magic +10029,Spell: Rewind,useful,MAGIC_SPELL,Magic +10101,Juna <3,progression,FRIENDSANITY,Juna - Roommate NPC +10102,Jasper <3,progression,FRIENDSANITY,Professor Jasper Thomas +10103,Alec <3,progression,FRIENDSANITY,Alec Revisited +10104,Yoba <3,progression,FRIENDSANITY,Custom NPC - Yoba +10105,Eugene <3,progression,FRIENDSANITY,Custom NPC Eugene +10106,Wellwick <3,progression,FRIENDSANITY,'Prophet' Wellwick +10107,Mr. Ginger <3,progression,FRIENDSANITY,Mister Ginger (cat npc) +10108,Shiko <3,progression,FRIENDSANITY,Shiko - New Custom NPC +10109,Delores <3,progression,FRIENDSANITY,Delores - Custom NPC +10110,Ayeisha <3,progression,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +10111,Riley <3,progression,FRIENDSANITY,Custom NPC - Riley +10301,Progressive Wood Obelisk Sigils,progression,,DeepWoods +10302,Progressive Skull Cavern Elevator,progression,,Skull Cavern Elevator diff --git a/worlds/stardew_valley/data/locations.csv b/worlds/stardew_valley/data/locations.csv index b36096e6..50f0267a 100644 --- a/worlds/stardew_valley/data/locations.csv +++ b/worlds/stardew_valley/data/locations.csv @@ -1,910 +1,1290 @@ -id,region,name,tags -1,Crafts Room,Spring Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY" -2,Crafts Room,Summer Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY" -3,Crafts Room,Fall Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY" -4,Crafts Room,Winter Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY" -5,Crafts Room,Construction Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY" -6,Crafts Room,Exotic Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY" -7,Pantry,Spring Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE" -8,Pantry,Summer Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE" -9,Pantry,Fall Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE" -10,Pantry,Quality Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE" -11,Pantry,Animal Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE" -12,Pantry,Artisan Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE" -13,Fish Tank,River Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY" -14,Fish Tank,Lake Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY" -15,Fish Tank,Ocean Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY" -16,Fish Tank,Night Fishing Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY" -17,Fish Tank,Crab Pot Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY" -18,Fish Tank,Specialty Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY" -19,Boiler Room,Blacksmith's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -20,Boiler Room,Geologist's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -21,Boiler Room,Adventurer's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -22,Bulletin Board,Chef's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -23,Bulletin Board,Dye Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -24,Bulletin Board,Field Research Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -25,Bulletin Board,Fodder Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -26,Bulletin Board,Enchanter's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY" -27,Vault,"2,500g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE" -28,Vault,"5,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE" -29,Vault,"10,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE" -30,Vault,"25,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE" -31,Abandoned JojaMart,The Missing Bundle,BUNDLE -32,Crafts Room,Complete Crafts Room,"COMMUNITY_CENTER_ROOM,MANDATORY" -33,Pantry,Complete Pantry,"COMMUNITY_CENTER_ROOM,MANDATORY" -34,Fish Tank,Complete Fish Tank,"COMMUNITY_CENTER_ROOM,MANDATORY" -35,Boiler Room,Complete Boiler Room,"COMMUNITY_CENTER_ROOM,MANDATORY" -36,Bulletin Board,Complete Bulletin Board,"COMMUNITY_CENTER_ROOM,MANDATORY" -37,Vault,Complete Vault,"COMMUNITY_CENTER_ROOM,MANDATORY" -101,Pierre's General Store,Large Pack,BACKPACK -102,Pierre's General Store,Deluxe Pack,BACKPACK -103,Clint's Blacksmith,Copper Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE" -104,Clint's Blacksmith,Iron Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE" -105,Clint's Blacksmith,Gold Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE" -106,Clint's Blacksmith,Iridium Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE" -107,Clint's Blacksmith,Copper Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE" -108,Clint's Blacksmith,Iron Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE" -109,Clint's Blacksmith,Gold Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE" -110,Clint's Blacksmith,Iridium Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE" -111,Clint's Blacksmith,Copper Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE" -112,Clint's Blacksmith,Iron Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE" -113,Clint's Blacksmith,Gold Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE" -114,Clint's Blacksmith,Iridium Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE" -115,Clint's Blacksmith,Copper Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE" -116,Clint's Blacksmith,Iron Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE" -117,Clint's Blacksmith,Gold Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE" -118,Clint's Blacksmith,Iridium Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE" -119,Clint's Blacksmith,Copper Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE" -120,Clint's Blacksmith,Iron Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE" -121,Clint's Blacksmith,Gold Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE" -122,Clint's Blacksmith,Iridium Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE" -123,Willy's Fish Shop,Purchase Training Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE" -124,Stardew Valley,Bamboo Pole Cutscene,"FISHING_ROD_UPGRADE,TOOL_UPGRADE" -125,Willy's Fish Shop,Purchase Fiberglass Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE" -126,Willy's Fish Shop,Purchase Iridium Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE" -201,The Mines - Floor 10,The Mines Floor 10 Treasure,"MANDATORY,THE_MINES_TREASURE" -202,The Mines - Floor 20,The Mines Floor 20 Treasure,"MANDATORY,THE_MINES_TREASURE" -203,The Mines - Floor 40,The Mines Floor 40 Treasure,"MANDATORY,THE_MINES_TREASURE" -204,The Mines - Floor 50,The Mines Floor 50 Treasure,"MANDATORY,THE_MINES_TREASURE" -205,The Mines - Floor 60,The Mines Floor 60 Treasure,"MANDATORY,THE_MINES_TREASURE" -206,The Mines - Floor 70,The Mines Floor 70 Treasure,"MANDATORY,THE_MINES_TREASURE" -207,The Mines - Floor 80,The Mines Floor 80 Treasure,"MANDATORY,THE_MINES_TREASURE" -208,The Mines - Floor 90,The Mines Floor 90 Treasure,"MANDATORY,THE_MINES_TREASURE" -209,The Mines - Floor 100,The Mines Floor 100 Treasure,"MANDATORY,THE_MINES_TREASURE" -210,The Mines - Floor 110,The Mines Floor 110 Treasure,"MANDATORY,THE_MINES_TREASURE" -211,The Mines - Floor 120,The Mines Floor 120 Treasure,"MANDATORY,THE_MINES_TREASURE" -212,Quarry Mine,Grim Reaper statue,MANDATORY -213,The Mines,The Mines Entrance Cutscene,MANDATORY -214,The Mines - Floor 5,Floor 5 Elevator,THE_MINES_ELEVATOR -215,The Mines - Floor 10,Floor 10 Elevator,THE_MINES_ELEVATOR -216,The Mines - Floor 15,Floor 15 Elevator,THE_MINES_ELEVATOR -217,The Mines - Floor 20,Floor 20 Elevator,THE_MINES_ELEVATOR -218,The Mines - Floor 25,Floor 25 Elevator,THE_MINES_ELEVATOR -219,The Mines - Floor 30,Floor 30 Elevator,THE_MINES_ELEVATOR -220,The Mines - Floor 35,Floor 35 Elevator,THE_MINES_ELEVATOR -221,The Mines - Floor 40,Floor 40 Elevator,THE_MINES_ELEVATOR -222,The Mines - Floor 45,Floor 45 Elevator,THE_MINES_ELEVATOR -223,The Mines - Floor 50,Floor 50 Elevator,THE_MINES_ELEVATOR -224,The Mines - Floor 55,Floor 55 Elevator,THE_MINES_ELEVATOR -225,The Mines - Floor 60,Floor 60 Elevator,THE_MINES_ELEVATOR -226,The Mines - Floor 65,Floor 65 Elevator,THE_MINES_ELEVATOR -227,The Mines - Floor 70,Floor 70 Elevator,THE_MINES_ELEVATOR -228,The Mines - Floor 75,Floor 75 Elevator,THE_MINES_ELEVATOR -229,The Mines - Floor 80,Floor 80 Elevator,THE_MINES_ELEVATOR -230,The Mines - Floor 85,Floor 85 Elevator,THE_MINES_ELEVATOR -231,The Mines - Floor 90,Floor 90 Elevator,THE_MINES_ELEVATOR -232,The Mines - Floor 95,Floor 95 Elevator,THE_MINES_ELEVATOR -233,The Mines - Floor 100,Floor 100 Elevator,THE_MINES_ELEVATOR -234,The Mines - Floor 105,Floor 105 Elevator,THE_MINES_ELEVATOR -235,The Mines - Floor 110,Floor 110 Elevator,THE_MINES_ELEVATOR -236,The Mines - Floor 115,Floor 115 Elevator,THE_MINES_ELEVATOR -237,The Mines - Floor 120,Floor 120 Elevator,THE_MINES_ELEVATOR -301,Stardew Valley,Level 1 Farming,"FARMING_LEVEL,SKILL_LEVEL" -302,Stardew Valley,Level 2 Farming,"FARMING_LEVEL,SKILL_LEVEL" -303,Stardew Valley,Level 3 Farming,"FARMING_LEVEL,SKILL_LEVEL" -304,Stardew Valley,Level 4 Farming,"FARMING_LEVEL,SKILL_LEVEL" -305,Stardew Valley,Level 5 Farming,"FARMING_LEVEL,SKILL_LEVEL" -306,Stardew Valley,Level 6 Farming,"FARMING_LEVEL,SKILL_LEVEL" -307,Stardew Valley,Level 7 Farming,"FARMING_LEVEL,SKILL_LEVEL" -308,Stardew Valley,Level 8 Farming,"FARMING_LEVEL,SKILL_LEVEL" -309,Stardew Valley,Level 9 Farming,"FARMING_LEVEL,SKILL_LEVEL" -310,Stardew Valley,Level 10 Farming,"FARMING_LEVEL,SKILL_LEVEL" -311,Stardew Valley,Level 1 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -312,Stardew Valley,Level 2 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -313,Stardew Valley,Level 3 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -314,Stardew Valley,Level 4 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -315,Stardew Valley,Level 5 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -316,Stardew Valley,Level 6 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -317,Stardew Valley,Level 7 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -318,Stardew Valley,Level 8 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -319,Stardew Valley,Level 9 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -320,Stardew Valley,Level 10 Fishing,"FISHING_LEVEL,SKILL_LEVEL" -321,Stardew Valley,Level 1 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -322,Stardew Valley,Level 2 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -323,Stardew Valley,Level 3 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -324,Stardew Valley,Level 4 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -325,Stardew Valley,Level 5 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -326,Stardew Valley,Level 6 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -327,Stardew Valley,Level 7 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -328,Stardew Valley,Level 8 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -329,Stardew Valley,Level 9 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -330,Stardew Valley,Level 10 Foraging,"FORAGING_LEVEL,SKILL_LEVEL" -331,Stardew Valley,Level 1 Mining,"MINING_LEVEL,SKILL_LEVEL" -332,Stardew Valley,Level 2 Mining,"MINING_LEVEL,SKILL_LEVEL" -333,Stardew Valley,Level 3 Mining,"MINING_LEVEL,SKILL_LEVEL" -334,Stardew Valley,Level 4 Mining,"MINING_LEVEL,SKILL_LEVEL" -335,Stardew Valley,Level 5 Mining,"MINING_LEVEL,SKILL_LEVEL" -336,Stardew Valley,Level 6 Mining,"MINING_LEVEL,SKILL_LEVEL" -337,Stardew Valley,Level 7 Mining,"MINING_LEVEL,SKILL_LEVEL" -338,Stardew Valley,Level 8 Mining,"MINING_LEVEL,SKILL_LEVEL" -339,Stardew Valley,Level 9 Mining,"MINING_LEVEL,SKILL_LEVEL" -340,Stardew Valley,Level 10 Mining,"MINING_LEVEL,SKILL_LEVEL" -341,Stardew Valley,Level 1 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -342,Stardew Valley,Level 2 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -343,Stardew Valley,Level 3 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -344,Stardew Valley,Level 4 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -345,Stardew Valley,Level 5 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -346,Stardew Valley,Level 6 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -347,Stardew Valley,Level 7 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -348,Stardew Valley,Level 8 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -349,Stardew Valley,Level 9 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -350,Stardew Valley,Level 10 Combat,"COMBAT_LEVEL,SKILL_LEVEL" -401,Carpenter Shop,Coop Blueprint,BUILDING_BLUEPRINT -402,Carpenter Shop,Big Coop Blueprint,BUILDING_BLUEPRINT -403,Carpenter Shop,Deluxe Coop Blueprint,BUILDING_BLUEPRINT -404,Carpenter Shop,Barn Blueprint,BUILDING_BLUEPRINT -405,Carpenter Shop,Big Barn Blueprint,BUILDING_BLUEPRINT -406,Carpenter Shop,Deluxe Barn Blueprint,BUILDING_BLUEPRINT -407,Carpenter Shop,Well Blueprint,BUILDING_BLUEPRINT -408,Carpenter Shop,Silo Blueprint,BUILDING_BLUEPRINT -409,Carpenter Shop,Mill Blueprint,BUILDING_BLUEPRINT -410,Carpenter Shop,Shed Blueprint,BUILDING_BLUEPRINT -411,Carpenter Shop,Big Shed Blueprint,BUILDING_BLUEPRINT -412,Carpenter Shop,Fish Pond Blueprint,BUILDING_BLUEPRINT -413,Carpenter Shop,Stable Blueprint,BUILDING_BLUEPRINT -414,Carpenter Shop,Slime Hutch Blueprint,BUILDING_BLUEPRINT -415,Carpenter Shop,Shipping Bin Blueprint,BUILDING_BLUEPRINT -416,Carpenter Shop,Kitchen Blueprint,BUILDING_BLUEPRINT -417,Carpenter Shop,Kids Room Blueprint,BUILDING_BLUEPRINT -418,Carpenter Shop,Cellar Blueprint,BUILDING_BLUEPRINT -501,Town,Introductions,"MANDATORY,QUEST" -502,Town,How To Win Friends,"MANDATORY,QUEST" -503,Farm,Getting Started,"MANDATORY,QUEST" -504,Farm,Raising Animals,"MANDATORY,QUEST" -505,Farm,Advancement,"MANDATORY,QUEST" -506,Museum,Archaeology,"MANDATORY,QUEST" -507,Wizard Tower,Meet The Wizard,"MANDATORY,QUEST" -508,Farm,Forging Ahead,"MANDATORY,QUEST" -509,Farm,Smelting,"MANDATORY,QUEST" -510,The Mines - Floor 5,Initiation,"MANDATORY,QUEST" -511,Forest,Robin's Lost Axe,"MANDATORY,QUEST" -512,Sam's House,Jodi's Request,"MANDATORY,QUEST" -513,Marnie's Ranch,"Mayor's ""Shorts""","MANDATORY,QUEST" -514,Tunnel Entrance,Blackberry Basket,"MANDATORY,QUEST" -515,Marnie's Ranch,Marnie's Request,"MANDATORY,QUEST" -516,Town,Pam Is Thirsty,"MANDATORY,QUEST" -517,Wizard Tower,A Dark Reagent,"MANDATORY,QUEST" -518,Marnie's Ranch,Cow's Delight,"MANDATORY,QUEST" -519,Skull Cavern Entrance,The Skull Key,"MANDATORY,QUEST" -520,Town,Crop Research,"MANDATORY,QUEST" -521,Town,Knee Therapy,"MANDATORY,QUEST" -522,Town,Robin's Request,"MANDATORY,QUEST" -523,Skull Cavern,Qi's Challenge,"MANDATORY,QUEST" -524,The Desert,The Mysterious Qi,"MANDATORY,QUEST" -525,Town,Carving Pumpkins,"MANDATORY,QUEST" -526,Town,A Winter Mystery,"MANDATORY,QUEST" -527,Secret Woods,Strange Note,"MANDATORY,QUEST" -528,Skull Cavern,Cryptic Note,"MANDATORY,QUEST" -529,Town,Fresh Fruit,"MANDATORY,QUEST" -530,Town,Aquatic Research,"MANDATORY,QUEST" -531,Town,A Soldier's Star,"MANDATORY,QUEST" -532,Town,Mayor's Need,"MANDATORY,QUEST" -533,Saloon,Wanted: Lobster,"MANDATORY,QUEST" -534,Town,Pam Needs Juice,"MANDATORY,QUEST" -535,Sam's House,Fish Casserole,"MANDATORY,QUEST" -536,Beach,Catch A Squid,"MANDATORY,QUEST" -537,Saloon,Fish Stew,"MANDATORY,QUEST" -538,Town,Pierre's Notice,"MANDATORY,QUEST" -539,Town,Clint's Attempt,"MANDATORY,QUEST" -540,Town,A Favor For Clint,"MANDATORY,QUEST" -541,Wizard Tower,Staff Of Power,"MANDATORY,QUEST" -542,Town,Granny's Gift,"MANDATORY,QUEST" -543,Saloon,Exotic Spirits,"MANDATORY,QUEST" -544,Town,Catch a Lingcod,"MANDATORY,QUEST" -601,JotPK World 1,JotPK: Boots 1,"ARCADE_MACHINE,JOTPK" -602,JotPK World 1,JotPK: Boots 2,"ARCADE_MACHINE,JOTPK" -603,JotPK World 1,JotPK: Gun 1,"ARCADE_MACHINE,JOTPK" -604,JotPK World 2,JotPK: Gun 2,"ARCADE_MACHINE,JOTPK" -605,JotPK World 2,JotPK: Gun 3,"ARCADE_MACHINE,JOTPK" -606,JotPK World 3,JotPK: Super Gun,"ARCADE_MACHINE,JOTPK" -607,JotPK World 1,JotPK: Ammo 1,"ARCADE_MACHINE,JOTPK" -608,JotPK World 2,JotPK: Ammo 2,"ARCADE_MACHINE,JOTPK" -609,JotPK World 3,JotPK: Ammo 3,"ARCADE_MACHINE,JOTPK" -610,JotPK World 1,JotPK: Cowboy 1,"ARCADE_MACHINE,JOTPK" -611,JotPK World 2,JotPK: Cowboy 2,"ARCADE_MACHINE,JOTPK" -612,Junimo Kart 1,Junimo Kart: Crumble Cavern,"ARCADE_MACHINE,JUNIMO_KART" -613,Junimo Kart 1,Junimo Kart: Slippery Slopes,"ARCADE_MACHINE,JUNIMO_KART" -614,Junimo Kart 2,Junimo Kart: Secret Level,"ARCADE_MACHINE,JUNIMO_KART" -615,Junimo Kart 2,Junimo Kart: The Gem Sea Giant,"ARCADE_MACHINE,JUNIMO_KART" -616,Junimo Kart 2,Junimo Kart: Slomp's Stomp,"ARCADE_MACHINE,JUNIMO_KART" -617,Junimo Kart 2,Junimo Kart: Ghastly Galleon,"ARCADE_MACHINE,JUNIMO_KART" -618,Junimo Kart 3,Junimo Kart: Glowshroom Grotto,"ARCADE_MACHINE,JUNIMO_KART" -619,Junimo Kart 3,Junimo Kart: Red Hot Rollercoaster,"ARCADE_MACHINE,JUNIMO_KART" -620,JotPK World 3,Journey of the Prairie King Victory,"ARCADE_MACHINE_VICTORY,JUNIMO_KART" -621,Junimo Kart 3,Junimo Kart: Sunset Speedway (Victory),"ARCADE_MACHINE_VICTORY,JUNIMO_KART" -701,Secret Woods,Old Master Cannoli,MANDATORY -702,Beach,Beach Bridge Repair,MANDATORY -703,The Desert,Galaxy Sword Shrine,MANDATORY -801,Town,Help Wanted: Gathering 1,HELP_WANTED -802,Town,Help Wanted: Gathering 2,HELP_WANTED -803,Town,Help Wanted: Gathering 3,HELP_WANTED -804,Town,Help Wanted: Gathering 4,HELP_WANTED -805,Town,Help Wanted: Gathering 5,HELP_WANTED -806,Town,Help Wanted: Gathering 6,HELP_WANTED -807,Town,Help Wanted: Gathering 7,HELP_WANTED -808,Town,Help Wanted: Gathering 8,HELP_WANTED -811,Town,Help Wanted: Slay Monsters 1,HELP_WANTED -812,Town,Help Wanted: Slay Monsters 2,HELP_WANTED -813,Town,Help Wanted: Slay Monsters 3,HELP_WANTED -814,Town,Help Wanted: Slay Monsters 4,HELP_WANTED -815,Town,Help Wanted: Slay Monsters 5,HELP_WANTED -816,Town,Help Wanted: Slay Monsters 6,HELP_WANTED -817,Town,Help Wanted: Slay Monsters 7,HELP_WANTED -818,Town,Help Wanted: Slay Monsters 8,HELP_WANTED -821,Town,Help Wanted: Fishing 1,HELP_WANTED -822,Town,Help Wanted: Fishing 2,HELP_WANTED -823,Town,Help Wanted: Fishing 3,HELP_WANTED -824,Town,Help Wanted: Fishing 4,HELP_WANTED -825,Town,Help Wanted: Fishing 5,HELP_WANTED -826,Town,Help Wanted: Fishing 6,HELP_WANTED -827,Town,Help Wanted: Fishing 7,HELP_WANTED -828,Town,Help Wanted: Fishing 8,HELP_WANTED -841,Town,Help Wanted: Item Delivery 1,HELP_WANTED -842,Town,Help Wanted: Item Delivery 2,HELP_WANTED -843,Town,Help Wanted: Item Delivery 3,HELP_WANTED -844,Town,Help Wanted: Item Delivery 4,HELP_WANTED -845,Town,Help Wanted: Item Delivery 5,HELP_WANTED -846,Town,Help Wanted: Item Delivery 6,HELP_WANTED -847,Town,Help Wanted: Item Delivery 7,HELP_WANTED -848,Town,Help Wanted: Item Delivery 8,HELP_WANTED -849,Town,Help Wanted: Item Delivery 9,HELP_WANTED -850,Town,Help Wanted: Item Delivery 10,HELP_WANTED -851,Town,Help Wanted: Item Delivery 11,HELP_WANTED -852,Town,Help Wanted: Item Delivery 12,HELP_WANTED -853,Town,Help Wanted: Item Delivery 13,HELP_WANTED -854,Town,Help Wanted: Item Delivery 14,HELP_WANTED -855,Town,Help Wanted: Item Delivery 15,HELP_WANTED -856,Town,Help Wanted: Item Delivery 16,HELP_WANTED -857,Town,Help Wanted: Item Delivery 17,HELP_WANTED -858,Town,Help Wanted: Item Delivery 18,HELP_WANTED -859,Town,Help Wanted: Item Delivery 19,HELP_WANTED -860,Town,Help Wanted: Item Delivery 20,HELP_WANTED -861,Town,Help Wanted: Item Delivery 21,HELP_WANTED -862,Town,Help Wanted: Item Delivery 22,HELP_WANTED -863,Town,Help Wanted: Item Delivery 23,HELP_WANTED -864,Town,Help Wanted: Item Delivery 24,HELP_WANTED -865,Town,Help Wanted: Item Delivery 25,HELP_WANTED -866,Town,Help Wanted: Item Delivery 26,HELP_WANTED -867,Town,Help Wanted: Item Delivery 27,HELP_WANTED -868,Town,Help Wanted: Item Delivery 28,HELP_WANTED -869,Town,Help Wanted: Item Delivery 29,HELP_WANTED -870,Town,Help Wanted: Item Delivery 30,HELP_WANTED -871,Town,Help Wanted: Item Delivery 31,HELP_WANTED -872,Town,Help Wanted: Item Delivery 32,HELP_WANTED -901,Forest,Traveling Merchant Sunday Item 1,"MANDATORY,TRAVELING_MERCHANT" -902,Forest,Traveling Merchant Sunday Item 2,"MANDATORY,TRAVELING_MERCHANT" -903,Forest,Traveling Merchant Sunday Item 3,"MANDATORY,TRAVELING_MERCHANT" -911,Forest,Traveling Merchant Monday Item 1,"MANDATORY,TRAVELING_MERCHANT" -912,Forest,Traveling Merchant Monday Item 2,"MANDATORY,TRAVELING_MERCHANT" -913,Forest,Traveling Merchant Monday Item 3,"MANDATORY,TRAVELING_MERCHANT" -921,Forest,Traveling Merchant Tuesday Item 1,"MANDATORY,TRAVELING_MERCHANT" -922,Forest,Traveling Merchant Tuesday Item 2,"MANDATORY,TRAVELING_MERCHANT" -923,Forest,Traveling Merchant Tuesday Item 3,"MANDATORY,TRAVELING_MERCHANT" -931,Forest,Traveling Merchant Wednesday Item 1,"MANDATORY,TRAVELING_MERCHANT" -932,Forest,Traveling Merchant Wednesday Item 2,"MANDATORY,TRAVELING_MERCHANT" -933,Forest,Traveling Merchant Wednesday Item 3,"MANDATORY,TRAVELING_MERCHANT" -941,Forest,Traveling Merchant Thursday Item 1,"MANDATORY,TRAVELING_MERCHANT" -942,Forest,Traveling Merchant Thursday Item 2,"MANDATORY,TRAVELING_MERCHANT" -943,Forest,Traveling Merchant Thursday Item 3,"MANDATORY,TRAVELING_MERCHANT" -951,Forest,Traveling Merchant Friday Item 1,"MANDATORY,TRAVELING_MERCHANT" -952,Forest,Traveling Merchant Friday Item 2,"MANDATORY,TRAVELING_MERCHANT" -953,Forest,Traveling Merchant Friday Item 3,"MANDATORY,TRAVELING_MERCHANT" -961,Forest,Traveling Merchant Saturday Item 1,"MANDATORY,TRAVELING_MERCHANT" -962,Forest,Traveling Merchant Saturday Item 2,"MANDATORY,TRAVELING_MERCHANT" -963,Forest,Traveling Merchant Saturday Item 3,"MANDATORY,TRAVELING_MERCHANT" -1001,Mountain,Fishsanity: Carp,FISHSANITY -1002,Beach,Fishsanity: Herring,FISHSANITY -1003,Forest,Fishsanity: Smallmouth Bass,FISHSANITY -1004,Beach,Fishsanity: Anchovy,FISHSANITY -1005,Beach,Fishsanity: Sardine,FISHSANITY -1006,Forest,Fishsanity: Sunfish,FISHSANITY -1007,Forest,Fishsanity: Perch,FISHSANITY -1008,Forest,Fishsanity: Chub,FISHSANITY -1009,Forest,Fishsanity: Bream,FISHSANITY -1010,Beach,Fishsanity: Red Snapper,FISHSANITY -1011,Beach,Fishsanity: Sea Cucumber,FISHSANITY -1012,Forest,Fishsanity: Rainbow Trout,FISHSANITY -1013,Forest,Fishsanity: Walleye,FISHSANITY -1014,Forest,Fishsanity: Shad,FISHSANITY -1015,Mountain,Fishsanity: Bullhead,FISHSANITY -1016,Mountain,Fishsanity: Largemouth Bass,FISHSANITY -1017,Forest,Fishsanity: Salmon,FISHSANITY -1018,The Mines - Floor 20,Fishsanity: Ghostfish,FISHSANITY -1019,Beach,Fishsanity: Tilapia,FISHSANITY -1020,Secret Woods,Fishsanity: Woodskip,FISHSANITY -1021,Beach,Fishsanity: Flounder,FISHSANITY -1022,Beach,Fishsanity: Halibut,FISHSANITY -1023,Ginger Island,Fishsanity: Lionfish,FISHSANITY -1024,Mutant Bug Lair,Fishsanity: Slimejack,FISHSANITY -1025,Forest,Fishsanity: Midnight Carp,FISHSANITY -1026,Beach,Fishsanity: Red Mullet,FISHSANITY -1027,Forest,Fishsanity: Pike,FISHSANITY -1028,Forest,Fishsanity: Tiger Trout,FISHSANITY -1029,Ginger Island,Fishsanity: Blue Discus,FISHSANITY -1030,Beach,Fishsanity: Albacore,FISHSANITY -1031,The Desert,Fishsanity: Sandfish,FISHSANITY -1032,The Mines - Floor 20,Fishsanity: Stonefish,FISHSANITY -1033,Beach,Fishsanity: Tuna,FISHSANITY -1034,Beach,Fishsanity: Eel,FISHSANITY -1035,Forest,Fishsanity: Catfish,FISHSANITY -1036,Beach,Fishsanity: Squid,FISHSANITY -1037,Mountain,Fishsanity: Sturgeon,FISHSANITY -1038,Forest,Fishsanity: Dorado,FISHSANITY -1039,Beach,Fishsanity: Pufferfish,FISHSANITY -1040,Witch's Swamp,Fishsanity: Void Salmon,FISHSANITY -1041,Beach,Fishsanity: Super Cucumber,FISHSANITY -1042,Ginger Island,Fishsanity: Stingray,FISHSANITY -1043,The Mines - Floor 60,Fishsanity: Ice Pip,FISHSANITY -1044,Forest,Fishsanity: Lingcod,FISHSANITY -1045,The Desert,Fishsanity: Scorpion Carp,FISHSANITY -1046,The Mines - Floor 100,Fishsanity: Lava Eel,FISHSANITY -1047,Beach,Fishsanity: Octopus,FISHSANITY -1048,Beach,Fishsanity: Midnight Squid,FISHSANITY -1049,Beach,Fishsanity: Spook Fish,FISHSANITY -1050,Beach,Fishsanity: Blobfish,FISHSANITY -1051,Beach,Fishsanity: Crimsonfish,FISHSANITY -1052,Town,Fishsanity: Angler,FISHSANITY -1053,Mountain,Fishsanity: Legend,FISHSANITY -1054,Forest,Fishsanity: Glacierfish,FISHSANITY -1055,Sewers,Fishsanity: Mutant Carp,FISHSANITY -1056,Town,Fishsanity: Crayfish,FISHSANITY -1057,Town,Fishsanity: Snail,FISHSANITY -1058,Town,Fishsanity: Periwinkle,FISHSANITY -1059,Beach,Fishsanity: Lobster,FISHSANITY -1060,Beach,Fishsanity: Clam,FISHSANITY -1061,Beach,Fishsanity: Crab,FISHSANITY -1062,Beach,Fishsanity: Cockle,FISHSANITY -1063,Beach,Fishsanity: Mussel,FISHSANITY -1064,Beach,Fishsanity: Shrimp,FISHSANITY -1065,Beach,Fishsanity: Oyster,FISHSANITY -1100,Stardew Valley,Museumsanity: 5 Donations,MUSEUM_MILESTONES -1101,Stardew Valley,Museumsanity: 10 Donations,MUSEUM_MILESTONES -1102,Stardew Valley,Museumsanity: 15 Donations,MUSEUM_MILESTONES -1103,Stardew Valley,Museumsanity: 20 Donations,MUSEUM_MILESTONES -1104,Stardew Valley,Museumsanity: 25 Donations,MUSEUM_MILESTONES -1105,Stardew Valley,Museumsanity: 30 Donations,MUSEUM_MILESTONES -1106,Stardew Valley,Museumsanity: 35 Donations,MUSEUM_MILESTONES -1107,Stardew Valley,Museumsanity: 40 Donations,MUSEUM_MILESTONES -1108,Stardew Valley,Museumsanity: 50 Donations,MUSEUM_MILESTONES -1109,Stardew Valley,Museumsanity: 60 Donations,MUSEUM_MILESTONES -1110,Stardew Valley,Museumsanity: 70 Donations,MUSEUM_MILESTONES -1111,Stardew Valley,Museumsanity: 80 Donations,MUSEUM_MILESTONES -1112,Stardew Valley,Museumsanity: 90 Donations,MUSEUM_MILESTONES -1113,Stardew Valley,Museumsanity: 95 Donations,MUSEUM_MILESTONES -1114,Stardew Valley,Museumsanity: 11 Minerals,MUSEUM_MILESTONES -1115,Stardew Valley,Museumsanity: 21 Minerals,MUSEUM_MILESTONES -1116,Stardew Valley,Museumsanity: 31 Minerals,MUSEUM_MILESTONES -1117,Stardew Valley,Museumsanity: 41 Minerals,MUSEUM_MILESTONES -1118,Stardew Valley,Museumsanity: 50 Minerals,MUSEUM_MILESTONES -1119,Stardew Valley,Museumsanity: 3 Artifacts,MUSEUM_MILESTONES -1120,Stardew Valley,Museumsanity: 6 Artifacts,MUSEUM_MILESTONES -1121,Stardew Valley,Museumsanity: 9 Artifacts,MUSEUM_MILESTONES -1122,Stardew Valley,Museumsanity: 11 Artifacts,MUSEUM_MILESTONES -1123,Stardew Valley,Museumsanity: 15 Artifacts,MUSEUM_MILESTONES -1124,Stardew Valley,Museumsanity: 20 Artifacts,MUSEUM_MILESTONES -1125,Stardew Valley,Museumsanity: Dwarf Scrolls,MUSEUM_MILESTONES -1126,Stardew Valley,Museumsanity: Skeleton Front,MUSEUM_MILESTONES -1127,Stardew Valley,Museumsanity: Skeleton Middle,MUSEUM_MILESTONES -1128,Stardew Valley,Museumsanity: Skeleton Back,MUSEUM_MILESTONES -1201,The Mines - Floor 20,Museumsanity: Dwarf Scroll I,MUSEUM_DONATIONS -1202,The Mines - Floor 20,Museumsanity: Dwarf Scroll II,MUSEUM_DONATIONS -1203,The Mines - Floor 60,Museumsanity: Dwarf Scroll III,MUSEUM_DONATIONS -1204,The Mines - Floor 100,Museumsanity: Dwarf Scroll IV,MUSEUM_DONATIONS -1205,Town,Museumsanity: Chipped Amphora,MUSEUM_DONATIONS -1206,Forest,Museumsanity: Arrowhead,MUSEUM_DONATIONS -1207,Forest,Museumsanity: Ancient Doll,MUSEUM_DONATIONS -1208,Forest,Museumsanity: Elvish Jewelry,MUSEUM_DONATIONS -1209,Forest,Museumsanity: Chewing Stick,MUSEUM_DONATIONS -1210,Forest,Museumsanity: Ornamental Fan,MUSEUM_DONATIONS -1211,Mountain,Museumsanity: Dinosaur Egg,MUSEUM_DONATIONS -1212,Stardew Valley,Museumsanity: Rare Disc,MUSEUM_DONATIONS -1213,Forest,Museumsanity: Ancient Sword,MUSEUM_DONATIONS -1214,Town,Museumsanity: Rusty Spoon,MUSEUM_DONATIONS -1215,Farm,Museumsanity: Rusty Spur,MUSEUM_DONATIONS -1216,Mountain,Museumsanity: Rusty Cog,MUSEUM_DONATIONS -1217,Farm,Museumsanity: Chicken Statue,MUSEUM_DONATIONS -1218,Forest,Museumsanity: Ancient Seed,"MUSEUM_DONATIONS,MUSEUM_MILESTONES" -1219,Forest,Museumsanity: Prehistoric Tool,MUSEUM_DONATIONS -1220,Beach,Museumsanity: Dried Starfish,MUSEUM_DONATIONS -1221,Beach,Museumsanity: Anchor,MUSEUM_DONATIONS -1222,Beach,Museumsanity: Glass Shards,MUSEUM_DONATIONS -1223,Forest,Museumsanity: Bone Flute,MUSEUM_DONATIONS -1224,Forest,Museumsanity: Prehistoric Handaxe,MUSEUM_DONATIONS -1225,The Mines - Floor 20,Museumsanity: Dwarvish Helm,MUSEUM_DONATIONS -1226,The Mines - Floor 60,Museumsanity: Dwarf Gadget,MUSEUM_DONATIONS -1227,Forest,Museumsanity: Ancient Drum,MUSEUM_DONATIONS -1228,The Desert,Museumsanity: Golden Mask,MUSEUM_DONATIONS -1229,The Desert,Museumsanity: Golden Relic,MUSEUM_DONATIONS -1230,Town,Museumsanity: Strange Doll (Green),MUSEUM_DONATIONS -1231,The Desert,Museumsanity: Strange Doll,MUSEUM_DONATIONS -1232,Forest,Museumsanity: Prehistoric Scapula,MUSEUM_DONATIONS -1233,Forest,Museumsanity: Prehistoric Tibia,MUSEUM_DONATIONS -1234,Ginger Island,Museumsanity: Prehistoric Skull,MUSEUM_DONATIONS -1235,Ginger Island,Museumsanity: Skeletal Hand,MUSEUM_DONATIONS -1236,Ginger Island,Museumsanity: Prehistoric Rib,MUSEUM_DONATIONS -1237,Ginger Island,Museumsanity: Prehistoric Vertebra,MUSEUM_DONATIONS -1238,Ginger Island,Museumsanity: Skeletal Tail,MUSEUM_DONATIONS -1239,Ginger Island,Museumsanity: Nautilus Fossil,MUSEUM_DONATIONS -1240,Forest,Museumsanity: Amphibian Fossil,MUSEUM_DONATIONS -1241,Forest,Museumsanity: Palm Fossil,MUSEUM_DONATIONS -1242,Forest,Museumsanity: Trilobite,MUSEUM_DONATIONS -1243,The Mines - Floor 20,Museumsanity: Quartz,MUSEUM_DONATIONS -1244,The Mines - Floor 100,Museumsanity: Fire Quartz,MUSEUM_DONATIONS -1245,The Mines - Floor 60,Museumsanity: Frozen Tear,MUSEUM_DONATIONS -1246,The Mines - Floor 20,Museumsanity: Earth Crystal,MUSEUM_DONATIONS -1247,The Mines - Floor 100,Museumsanity: Emerald,MUSEUM_DONATIONS -1248,The Mines - Floor 60,Museumsanity: Aquamarine,MUSEUM_DONATIONS -1249,The Mines - Floor 100,Museumsanity: Ruby,MUSEUM_DONATIONS -1250,The Mines - Floor 20,Museumsanity: Amethyst,MUSEUM_DONATIONS -1251,The Mines - Floor 20,Museumsanity: Topaz,MUSEUM_DONATIONS -1252,The Mines - Floor 60,Museumsanity: Jade,MUSEUM_DONATIONS -1253,The Mines - Floor 60,Museumsanity: Diamond,MUSEUM_DONATIONS -1254,Skull Cavern Floor 100,Museumsanity: Prismatic Shard,MUSEUM_DONATIONS -1255,Town,Museumsanity: Alamite,MUSEUM_DONATIONS -1256,Town,Museumsanity: Bixite,MUSEUM_DONATIONS -1257,Town,Museumsanity: Baryte,MUSEUM_DONATIONS -1258,Town,Museumsanity: Aerinite,MUSEUM_DONATIONS -1259,Town,Museumsanity: Calcite,MUSEUM_DONATIONS -1260,Town,Museumsanity: Dolomite,MUSEUM_DONATIONS -1261,Town,Museumsanity: Esperite,MUSEUM_DONATIONS -1262,Town,Museumsanity: Fluorapatite,MUSEUM_DONATIONS -1263,Town,Museumsanity: Geminite,MUSEUM_DONATIONS -1264,Town,Museumsanity: Helvite,MUSEUM_DONATIONS -1265,Town,Museumsanity: Jamborite,MUSEUM_DONATIONS -1266,Town,Museumsanity: Jagoite,MUSEUM_DONATIONS -1267,Town,Museumsanity: Kyanite,MUSEUM_DONATIONS -1268,Town,Museumsanity: Lunarite,MUSEUM_DONATIONS -1269,Town,Museumsanity: Malachite,MUSEUM_DONATIONS -1270,Town,Museumsanity: Neptunite,MUSEUM_DONATIONS -1271,Town,Museumsanity: Lemon Stone,MUSEUM_DONATIONS -1272,Town,Museumsanity: Nekoite,MUSEUM_DONATIONS -1273,Town,Museumsanity: Orpiment,MUSEUM_DONATIONS -1274,Town,Museumsanity: Petrified Slime,MUSEUM_DONATIONS -1275,Town,Museumsanity: Thunder Egg,MUSEUM_DONATIONS -1276,Town,Museumsanity: Pyrite,MUSEUM_DONATIONS -1277,Town,Museumsanity: Ocean Stone,MUSEUM_DONATIONS -1278,Town,Museumsanity: Ghost Crystal,MUSEUM_DONATIONS -1279,Town,Museumsanity: Tigerseye,MUSEUM_DONATIONS -1280,Town,Museumsanity: Jasper,MUSEUM_DONATIONS -1281,Town,Museumsanity: Opal,MUSEUM_DONATIONS -1282,Town,Museumsanity: Fire Opal,MUSEUM_DONATIONS -1283,Town,Museumsanity: Celestine,MUSEUM_DONATIONS -1284,Town,Museumsanity: Marble,MUSEUM_DONATIONS -1285,Town,Museumsanity: Sandstone,MUSEUM_DONATIONS -1286,Town,Museumsanity: Granite,MUSEUM_DONATIONS -1287,Town,Museumsanity: Basalt,MUSEUM_DONATIONS -1288,Town,Museumsanity: Limestone,MUSEUM_DONATIONS -1289,Town,Museumsanity: Soapstone,MUSEUM_DONATIONS -1290,Town,Museumsanity: Hematite,MUSEUM_DONATIONS -1291,Town,Museumsanity: Mudstone,MUSEUM_DONATIONS -1292,Town,Museumsanity: Obsidian,MUSEUM_DONATIONS -1293,Town,Museumsanity: Slate,MUSEUM_DONATIONS -1294,Town,Museumsanity: Fairy Stone,MUSEUM_DONATIONS -1295,Town,Museumsanity: Star Shards,MUSEUM_DONATIONS -1301,Town,Friendsanity: Alex 1 <3,FRIENDSANITY -1302,Town,Friendsanity: Alex 2 <3,FRIENDSANITY -1303,Town,Friendsanity: Alex 3 <3,FRIENDSANITY -1304,Town,Friendsanity: Alex 4 <3,FRIENDSANITY -1305,Town,Friendsanity: Alex 5 <3,FRIENDSANITY -1306,Town,Friendsanity: Alex 6 <3,FRIENDSANITY -1307,Town,Friendsanity: Alex 7 <3,FRIENDSANITY -1308,Town,Friendsanity: Alex 8 <3,FRIENDSANITY -1309,Town,Friendsanity: Alex 9 <3,FRIENDSANITY -1310,Town,Friendsanity: Alex 10 <3,FRIENDSANITY -1311,Town,Friendsanity: Alex 11 <3,FRIENDSANITY -1312,Town,Friendsanity: Alex 12 <3,FRIENDSANITY -1313,Town,Friendsanity: Alex 13 <3,FRIENDSANITY -1314,Town,Friendsanity: Alex 14 <3,FRIENDSANITY -1315,Beach,Friendsanity: Elliott 1 <3,FRIENDSANITY -1316,Beach,Friendsanity: Elliott 2 <3,FRIENDSANITY -1317,Beach,Friendsanity: Elliott 3 <3,FRIENDSANITY -1318,Beach,Friendsanity: Elliott 4 <3,FRIENDSANITY -1319,Beach,Friendsanity: Elliott 5 <3,FRIENDSANITY -1320,Beach,Friendsanity: Elliott 6 <3,FRIENDSANITY -1321,Beach,Friendsanity: Elliott 7 <3,FRIENDSANITY -1322,Beach,Friendsanity: Elliott 8 <3,FRIENDSANITY -1323,Beach,Friendsanity: Elliott 9 <3,FRIENDSANITY -1324,Beach,Friendsanity: Elliott 10 <3,FRIENDSANITY -1325,Beach,Friendsanity: Elliott 11 <3,FRIENDSANITY -1326,Beach,Friendsanity: Elliott 12 <3,FRIENDSANITY -1327,Beach,Friendsanity: Elliott 13 <3,FRIENDSANITY -1328,Beach,Friendsanity: Elliott 14 <3,FRIENDSANITY -1329,Town,Friendsanity: Harvey 1 <3,FRIENDSANITY -1330,Town,Friendsanity: Harvey 2 <3,FRIENDSANITY -1331,Town,Friendsanity: Harvey 3 <3,FRIENDSANITY -1332,Town,Friendsanity: Harvey 4 <3,FRIENDSANITY -1333,Town,Friendsanity: Harvey 5 <3,FRIENDSANITY -1334,Town,Friendsanity: Harvey 6 <3,FRIENDSANITY -1335,Town,Friendsanity: Harvey 7 <3,FRIENDSANITY -1336,Town,Friendsanity: Harvey 8 <3,FRIENDSANITY -1337,Town,Friendsanity: Harvey 9 <3,FRIENDSANITY -1338,Town,Friendsanity: Harvey 10 <3,FRIENDSANITY -1339,Town,Friendsanity: Harvey 11 <3,FRIENDSANITY -1340,Town,Friendsanity: Harvey 12 <3,FRIENDSANITY -1341,Town,Friendsanity: Harvey 13 <3,FRIENDSANITY -1342,Town,Friendsanity: Harvey 14 <3,FRIENDSANITY -1343,Town,Friendsanity: Sam 1 <3,FRIENDSANITY -1344,Town,Friendsanity: Sam 2 <3,FRIENDSANITY -1345,Town,Friendsanity: Sam 3 <3,FRIENDSANITY -1346,Town,Friendsanity: Sam 4 <3,FRIENDSANITY -1347,Town,Friendsanity: Sam 5 <3,FRIENDSANITY -1348,Town,Friendsanity: Sam 6 <3,FRIENDSANITY -1349,Town,Friendsanity: Sam 7 <3,FRIENDSANITY -1350,Town,Friendsanity: Sam 8 <3,FRIENDSANITY -1351,Town,Friendsanity: Sam 9 <3,FRIENDSANITY -1352,Town,Friendsanity: Sam 10 <3,FRIENDSANITY -1353,Town,Friendsanity: Sam 11 <3,FRIENDSANITY -1354,Town,Friendsanity: Sam 12 <3,FRIENDSANITY -1355,Town,Friendsanity: Sam 13 <3,FRIENDSANITY -1356,Town,Friendsanity: Sam 14 <3,FRIENDSANITY -1357,Carpenter Shop,Friendsanity: Sebastian 1 <3,FRIENDSANITY -1358,Carpenter Shop,Friendsanity: Sebastian 2 <3,FRIENDSANITY -1359,Carpenter Shop,Friendsanity: Sebastian 3 <3,FRIENDSANITY -1360,Carpenter Shop,Friendsanity: Sebastian 4 <3,FRIENDSANITY -1361,Carpenter Shop,Friendsanity: Sebastian 5 <3,FRIENDSANITY -1362,Carpenter Shop,Friendsanity: Sebastian 6 <3,FRIENDSANITY -1363,Carpenter Shop,Friendsanity: Sebastian 7 <3,FRIENDSANITY -1364,Carpenter Shop,Friendsanity: Sebastian 8 <3,FRIENDSANITY -1365,Carpenter Shop,Friendsanity: Sebastian 9 <3,FRIENDSANITY -1366,Carpenter Shop,Friendsanity: Sebastian 10 <3,FRIENDSANITY -1367,Carpenter Shop,Friendsanity: Sebastian 11 <3,FRIENDSANITY -1368,Carpenter Shop,Friendsanity: Sebastian 12 <3,FRIENDSANITY -1369,Carpenter Shop,Friendsanity: Sebastian 13 <3,FRIENDSANITY -1370,Carpenter Shop,Friendsanity: Sebastian 14 <3,FRIENDSANITY -1371,Marnie's Ranch,Friendsanity: Shane 1 <3,FRIENDSANITY -1372,Marnie's Ranch,Friendsanity: Shane 2 <3,FRIENDSANITY -1373,Marnie's Ranch,Friendsanity: Shane 3 <3,FRIENDSANITY -1374,Marnie's Ranch,Friendsanity: Shane 4 <3,FRIENDSANITY -1375,Marnie's Ranch,Friendsanity: Shane 5 <3,FRIENDSANITY -1376,Marnie's Ranch,Friendsanity: Shane 6 <3,FRIENDSANITY -1377,Marnie's Ranch,Friendsanity: Shane 7 <3,FRIENDSANITY -1378,Marnie's Ranch,Friendsanity: Shane 8 <3,FRIENDSANITY -1379,Marnie's Ranch,Friendsanity: Shane 9 <3,FRIENDSANITY -1380,Marnie's Ranch,Friendsanity: Shane 10 <3,FRIENDSANITY -1381,Marnie's Ranch,Friendsanity: Shane 11 <3,FRIENDSANITY -1382,Marnie's Ranch,Friendsanity: Shane 12 <3,FRIENDSANITY -1383,Marnie's Ranch,Friendsanity: Shane 13 <3,FRIENDSANITY -1384,Marnie's Ranch,Friendsanity: Shane 14 <3,FRIENDSANITY -1385,Town,Friendsanity: Abigail 1 <3,FRIENDSANITY -1386,Town,Friendsanity: Abigail 2 <3,FRIENDSANITY -1387,Town,Friendsanity: Abigail 3 <3,FRIENDSANITY -1388,Town,Friendsanity: Abigail 4 <3,FRIENDSANITY -1389,Town,Friendsanity: Abigail 5 <3,FRIENDSANITY -1390,Town,Friendsanity: Abigail 6 <3,FRIENDSANITY -1391,Town,Friendsanity: Abigail 7 <3,FRIENDSANITY -1392,Town,Friendsanity: Abigail 8 <3,FRIENDSANITY -1393,Town,Friendsanity: Abigail 9 <3,FRIENDSANITY -1394,Town,Friendsanity: Abigail 10 <3,FRIENDSANITY -1395,Town,Friendsanity: Abigail 11 <3,FRIENDSANITY -1396,Town,Friendsanity: Abigail 12 <3,FRIENDSANITY -1397,Town,Friendsanity: Abigail 13 <3,FRIENDSANITY -1398,Town,Friendsanity: Abigail 14 <3,FRIENDSANITY -1399,Town,Friendsanity: Emily 1 <3,FRIENDSANITY -1400,Town,Friendsanity: Emily 2 <3,FRIENDSANITY -1401,Town,Friendsanity: Emily 3 <3,FRIENDSANITY -1402,Town,Friendsanity: Emily 4 <3,FRIENDSANITY -1403,Town,Friendsanity: Emily 5 <3,FRIENDSANITY -1404,Town,Friendsanity: Emily 6 <3,FRIENDSANITY -1405,Town,Friendsanity: Emily 7 <3,FRIENDSANITY -1406,Town,Friendsanity: Emily 8 <3,FRIENDSANITY -1407,Town,Friendsanity: Emily 9 <3,FRIENDSANITY -1408,Town,Friendsanity: Emily 10 <3,FRIENDSANITY -1409,Town,Friendsanity: Emily 11 <3,FRIENDSANITY -1410,Town,Friendsanity: Emily 12 <3,FRIENDSANITY -1411,Town,Friendsanity: Emily 13 <3,FRIENDSANITY -1412,Town,Friendsanity: Emily 14 <3,FRIENDSANITY -1413,Town,Friendsanity: Haley 1 <3,FRIENDSANITY -1414,Town,Friendsanity: Haley 2 <3,FRIENDSANITY -1415,Town,Friendsanity: Haley 3 <3,FRIENDSANITY -1416,Town,Friendsanity: Haley 4 <3,FRIENDSANITY -1417,Town,Friendsanity: Haley 5 <3,FRIENDSANITY -1418,Town,Friendsanity: Haley 6 <3,FRIENDSANITY -1419,Town,Friendsanity: Haley 7 <3,FRIENDSANITY -1420,Town,Friendsanity: Haley 8 <3,FRIENDSANITY -1421,Town,Friendsanity: Haley 9 <3,FRIENDSANITY -1422,Town,Friendsanity: Haley 10 <3,FRIENDSANITY -1423,Town,Friendsanity: Haley 11 <3,FRIENDSANITY -1424,Town,Friendsanity: Haley 12 <3,FRIENDSANITY -1425,Town,Friendsanity: Haley 13 <3,FRIENDSANITY -1426,Town,Friendsanity: Haley 14 <3,FRIENDSANITY -1427,Forest,Friendsanity: Leah 1 <3,FRIENDSANITY -1428,Forest,Friendsanity: Leah 2 <3,FRIENDSANITY -1429,Forest,Friendsanity: Leah 3 <3,FRIENDSANITY -1430,Forest,Friendsanity: Leah 4 <3,FRIENDSANITY -1431,Forest,Friendsanity: Leah 5 <3,FRIENDSANITY -1432,Forest,Friendsanity: Leah 6 <3,FRIENDSANITY -1433,Forest,Friendsanity: Leah 7 <3,FRIENDSANITY -1434,Forest,Friendsanity: Leah 8 <3,FRIENDSANITY -1435,Forest,Friendsanity: Leah 9 <3,FRIENDSANITY -1436,Forest,Friendsanity: Leah 10 <3,FRIENDSANITY -1437,Forest,Friendsanity: Leah 11 <3,FRIENDSANITY -1438,Forest,Friendsanity: Leah 12 <3,FRIENDSANITY -1439,Forest,Friendsanity: Leah 13 <3,FRIENDSANITY -1440,Forest,Friendsanity: Leah 14 <3,FRIENDSANITY -1441,Carpenter Shop,Friendsanity: Maru 1 <3,FRIENDSANITY -1442,Carpenter Shop,Friendsanity: Maru 2 <3,FRIENDSANITY -1443,Carpenter Shop,Friendsanity: Maru 3 <3,FRIENDSANITY -1444,Carpenter Shop,Friendsanity: Maru 4 <3,FRIENDSANITY -1445,Carpenter Shop,Friendsanity: Maru 5 <3,FRIENDSANITY -1446,Carpenter Shop,Friendsanity: Maru 6 <3,FRIENDSANITY -1447,Carpenter Shop,Friendsanity: Maru 7 <3,FRIENDSANITY -1448,Carpenter Shop,Friendsanity: Maru 8 <3,FRIENDSANITY -1449,Carpenter Shop,Friendsanity: Maru 9 <3,FRIENDSANITY -1450,Carpenter Shop,Friendsanity: Maru 10 <3,FRIENDSANITY -1451,Carpenter Shop,Friendsanity: Maru 11 <3,FRIENDSANITY -1452,Carpenter Shop,Friendsanity: Maru 12 <3,FRIENDSANITY -1453,Carpenter Shop,Friendsanity: Maru 13 <3,FRIENDSANITY -1454,Carpenter Shop,Friendsanity: Maru 14 <3,FRIENDSANITY -1455,Town,Friendsanity: Penny 1 <3,FRIENDSANITY -1456,Town,Friendsanity: Penny 2 <3,FRIENDSANITY -1457,Town,Friendsanity: Penny 3 <3,FRIENDSANITY -1458,Town,Friendsanity: Penny 4 <3,FRIENDSANITY -1459,Town,Friendsanity: Penny 5 <3,FRIENDSANITY -1460,Town,Friendsanity: Penny 6 <3,FRIENDSANITY -1461,Town,Friendsanity: Penny 7 <3,FRIENDSANITY -1462,Town,Friendsanity: Penny 8 <3,FRIENDSANITY -1463,Town,Friendsanity: Penny 9 <3,FRIENDSANITY -1464,Town,Friendsanity: Penny 10 <3,FRIENDSANITY -1465,Town,Friendsanity: Penny 11 <3,FRIENDSANITY -1466,Town,Friendsanity: Penny 12 <3,FRIENDSANITY -1467,Town,Friendsanity: Penny 13 <3,FRIENDSANITY -1468,Town,Friendsanity: Penny 14 <3,FRIENDSANITY -1469,Town,Friendsanity: Caroline 1 <3,FRIENDSANITY -1470,Town,Friendsanity: Caroline 2 <3,FRIENDSANITY -1471,Town,Friendsanity: Caroline 3 <3,FRIENDSANITY -1472,Town,Friendsanity: Caroline 4 <3,FRIENDSANITY -1473,Town,Friendsanity: Caroline 5 <3,FRIENDSANITY -1474,Town,Friendsanity: Caroline 6 <3,FRIENDSANITY -1475,Town,Friendsanity: Caroline 7 <3,FRIENDSANITY -1476,Town,Friendsanity: Caroline 8 <3,FRIENDSANITY -1477,Town,Friendsanity: Caroline 9 <3,FRIENDSANITY -1478,Town,Friendsanity: Caroline 10 <3,FRIENDSANITY -1480,Town,Friendsanity: Clint 1 <3,FRIENDSANITY -1481,Town,Friendsanity: Clint 2 <3,FRIENDSANITY -1482,Town,Friendsanity: Clint 3 <3,FRIENDSANITY -1483,Town,Friendsanity: Clint 4 <3,FRIENDSANITY -1484,Town,Friendsanity: Clint 5 <3,FRIENDSANITY -1485,Town,Friendsanity: Clint 6 <3,FRIENDSANITY -1486,Town,Friendsanity: Clint 7 <3,FRIENDSANITY -1487,Town,Friendsanity: Clint 8 <3,FRIENDSANITY -1488,Town,Friendsanity: Clint 9 <3,FRIENDSANITY -1489,Town,Friendsanity: Clint 10 <3,FRIENDSANITY -1491,Carpenter Shop,Friendsanity: Demetrius 1 <3,FRIENDSANITY -1492,Carpenter Shop,Friendsanity: Demetrius 2 <3,FRIENDSANITY -1493,Carpenter Shop,Friendsanity: Demetrius 3 <3,FRIENDSANITY -1494,Carpenter Shop,Friendsanity: Demetrius 4 <3,FRIENDSANITY -1495,Carpenter Shop,Friendsanity: Demetrius 5 <3,FRIENDSANITY -1496,Carpenter Shop,Friendsanity: Demetrius 6 <3,FRIENDSANITY -1497,Carpenter Shop,Friendsanity: Demetrius 7 <3,FRIENDSANITY -1498,Carpenter Shop,Friendsanity: Demetrius 8 <3,FRIENDSANITY -1499,Carpenter Shop,Friendsanity: Demetrius 9 <3,FRIENDSANITY -1500,Carpenter Shop,Friendsanity: Demetrius 10 <3,FRIENDSANITY -1502,The Mines,Friendsanity: Dwarf 1 <3,FRIENDSANITY -1503,The Mines,Friendsanity: Dwarf 2 <3,FRIENDSANITY -1504,The Mines,Friendsanity: Dwarf 3 <3,FRIENDSANITY -1505,The Mines,Friendsanity: Dwarf 4 <3,FRIENDSANITY -1506,The Mines,Friendsanity: Dwarf 5 <3,FRIENDSANITY -1507,The Mines,Friendsanity: Dwarf 6 <3,FRIENDSANITY -1508,The Mines,Friendsanity: Dwarf 7 <3,FRIENDSANITY -1509,The Mines,Friendsanity: Dwarf 8 <3,FRIENDSANITY -1510,The Mines,Friendsanity: Dwarf 9 <3,FRIENDSANITY -1511,The Mines,Friendsanity: Dwarf 10 <3,FRIENDSANITY -1513,Town,Friendsanity: Evelyn 1 <3,FRIENDSANITY -1514,Town,Friendsanity: Evelyn 2 <3,FRIENDSANITY -1515,Town,Friendsanity: Evelyn 3 <3,FRIENDSANITY -1516,Town,Friendsanity: Evelyn 4 <3,FRIENDSANITY -1517,Town,Friendsanity: Evelyn 5 <3,FRIENDSANITY -1518,Town,Friendsanity: Evelyn 6 <3,FRIENDSANITY -1519,Town,Friendsanity: Evelyn 7 <3,FRIENDSANITY -1520,Town,Friendsanity: Evelyn 8 <3,FRIENDSANITY -1521,Town,Friendsanity: Evelyn 9 <3,FRIENDSANITY -1522,Town,Friendsanity: Evelyn 10 <3,FRIENDSANITY -1524,Town,Friendsanity: George 1 <3,FRIENDSANITY -1525,Town,Friendsanity: George 2 <3,FRIENDSANITY -1526,Town,Friendsanity: George 3 <3,FRIENDSANITY -1527,Town,Friendsanity: George 4 <3,FRIENDSANITY -1528,Town,Friendsanity: George 5 <3,FRIENDSANITY -1529,Town,Friendsanity: George 6 <3,FRIENDSANITY -1530,Town,Friendsanity: George 7 <3,FRIENDSANITY -1531,Town,Friendsanity: George 8 <3,FRIENDSANITY -1532,Town,Friendsanity: George 9 <3,FRIENDSANITY -1533,Town,Friendsanity: George 10 <3,FRIENDSANITY -1535,Town,Friendsanity: Gus 1 <3,FRIENDSANITY -1536,Town,Friendsanity: Gus 2 <3,FRIENDSANITY -1537,Town,Friendsanity: Gus 3 <3,FRIENDSANITY -1538,Town,Friendsanity: Gus 4 <3,FRIENDSANITY -1539,Town,Friendsanity: Gus 5 <3,FRIENDSANITY -1540,Town,Friendsanity: Gus 6 <3,FRIENDSANITY -1541,Town,Friendsanity: Gus 7 <3,FRIENDSANITY -1542,Town,Friendsanity: Gus 8 <3,FRIENDSANITY -1543,Town,Friendsanity: Gus 9 <3,FRIENDSANITY -1544,Town,Friendsanity: Gus 10 <3,FRIENDSANITY -1546,Marnie's Ranch,Friendsanity: Jas 1 <3,FRIENDSANITY -1547,Marnie's Ranch,Friendsanity: Jas 2 <3,FRIENDSANITY -1548,Marnie's Ranch,Friendsanity: Jas 3 <3,FRIENDSANITY -1549,Marnie's Ranch,Friendsanity: Jas 4 <3,FRIENDSANITY -1550,Marnie's Ranch,Friendsanity: Jas 5 <3,FRIENDSANITY -1551,Marnie's Ranch,Friendsanity: Jas 6 <3,FRIENDSANITY -1552,Marnie's Ranch,Friendsanity: Jas 7 <3,FRIENDSANITY -1553,Marnie's Ranch,Friendsanity: Jas 8 <3,FRIENDSANITY -1554,Marnie's Ranch,Friendsanity: Jas 9 <3,FRIENDSANITY -1555,Marnie's Ranch,Friendsanity: Jas 10 <3,FRIENDSANITY -1557,Town,Friendsanity: Jodi 1 <3,FRIENDSANITY -1558,Town,Friendsanity: Jodi 2 <3,FRIENDSANITY -1559,Town,Friendsanity: Jodi 3 <3,FRIENDSANITY -1560,Town,Friendsanity: Jodi 4 <3,FRIENDSANITY -1561,Town,Friendsanity: Jodi 5 <3,FRIENDSANITY -1562,Town,Friendsanity: Jodi 6 <3,FRIENDSANITY -1563,Town,Friendsanity: Jodi 7 <3,FRIENDSANITY -1564,Town,Friendsanity: Jodi 8 <3,FRIENDSANITY -1565,Town,Friendsanity: Jodi 9 <3,FRIENDSANITY -1566,Town,Friendsanity: Jodi 10 <3,FRIENDSANITY -1568,Town,Friendsanity: Kent 1 <3,FRIENDSANITY -1569,Town,Friendsanity: Kent 2 <3,FRIENDSANITY -1570,Town,Friendsanity: Kent 3 <3,FRIENDSANITY -1571,Town,Friendsanity: Kent 4 <3,FRIENDSANITY -1572,Town,Friendsanity: Kent 5 <3,FRIENDSANITY -1573,Town,Friendsanity: Kent 6 <3,FRIENDSANITY -1574,Town,Friendsanity: Kent 7 <3,FRIENDSANITY -1575,Town,Friendsanity: Kent 8 <3,FRIENDSANITY -1576,Town,Friendsanity: Kent 9 <3,FRIENDSANITY -1577,Town,Friendsanity: Kent 10 <3,FRIENDSANITY -1579,Sewers,Friendsanity: Krobus 1 <3,FRIENDSANITY -1580,Sewers,Friendsanity: Krobus 2 <3,FRIENDSANITY -1581,Sewers,Friendsanity: Krobus 3 <3,FRIENDSANITY -1582,Sewers,Friendsanity: Krobus 4 <3,FRIENDSANITY -1583,Sewers,Friendsanity: Krobus 5 <3,FRIENDSANITY -1584,Sewers,Friendsanity: Krobus 6 <3,FRIENDSANITY -1585,Sewers,Friendsanity: Krobus 7 <3,FRIENDSANITY -1586,Sewers,Friendsanity: Krobus 8 <3,FRIENDSANITY -1587,Sewers,Friendsanity: Krobus 9 <3,FRIENDSANITY -1588,Sewers,Friendsanity: Krobus 10 <3,FRIENDSANITY -1590,Ginger Island,Friendsanity: Leo 1 <3,FRIENDSANITY -1591,Ginger Island,Friendsanity: Leo 2 <3,FRIENDSANITY -1592,Ginger Island,Friendsanity: Leo 3 <3,FRIENDSANITY -1593,Ginger Island,Friendsanity: Leo 4 <3,FRIENDSANITY -1594,Ginger Island,Friendsanity: Leo 5 <3,FRIENDSANITY -1595,Ginger Island,Friendsanity: Leo 6 <3,FRIENDSANITY -1596,Ginger Island,Friendsanity: Leo 7 <3,FRIENDSANITY -1597,Ginger Island,Friendsanity: Leo 8 <3,FRIENDSANITY -1598,Ginger Island,Friendsanity: Leo 9 <3,FRIENDSANITY -1599,Ginger Island,Friendsanity: Leo 10 <3,FRIENDSANITY -1601,Town,Friendsanity: Lewis 1 <3,FRIENDSANITY -1602,Town,Friendsanity: Lewis 2 <3,FRIENDSANITY -1603,Town,Friendsanity: Lewis 3 <3,FRIENDSANITY -1604,Town,Friendsanity: Lewis 4 <3,FRIENDSANITY -1605,Town,Friendsanity: Lewis 5 <3,FRIENDSANITY -1606,Town,Friendsanity: Lewis 6 <3,FRIENDSANITY -1607,Town,Friendsanity: Lewis 7 <3,FRIENDSANITY -1608,Town,Friendsanity: Lewis 8 <3,FRIENDSANITY -1609,Town,Friendsanity: Lewis 9 <3,FRIENDSANITY -1610,Town,Friendsanity: Lewis 10 <3,FRIENDSANITY -1612,Mountain,Friendsanity: Linus 1 <3,FRIENDSANITY -1613,Mountain,Friendsanity: Linus 2 <3,FRIENDSANITY -1614,Mountain,Friendsanity: Linus 3 <3,FRIENDSANITY -1615,Mountain,Friendsanity: Linus 4 <3,FRIENDSANITY -1616,Mountain,Friendsanity: Linus 5 <3,FRIENDSANITY -1617,Mountain,Friendsanity: Linus 6 <3,FRIENDSANITY -1618,Mountain,Friendsanity: Linus 7 <3,FRIENDSANITY -1619,Mountain,Friendsanity: Linus 8 <3,FRIENDSANITY -1620,Mountain,Friendsanity: Linus 9 <3,FRIENDSANITY -1621,Mountain,Friendsanity: Linus 10 <3,FRIENDSANITY -1623,Marnie's Ranch,Friendsanity: Marnie 1 <3,FRIENDSANITY -1624,Marnie's Ranch,Friendsanity: Marnie 2 <3,FRIENDSANITY -1625,Marnie's Ranch,Friendsanity: Marnie 3 <3,FRIENDSANITY -1626,Marnie's Ranch,Friendsanity: Marnie 4 <3,FRIENDSANITY -1627,Marnie's Ranch,Friendsanity: Marnie 5 <3,FRIENDSANITY -1628,Marnie's Ranch,Friendsanity: Marnie 6 <3,FRIENDSANITY -1629,Marnie's Ranch,Friendsanity: Marnie 7 <3,FRIENDSANITY -1630,Marnie's Ranch,Friendsanity: Marnie 8 <3,FRIENDSANITY -1631,Marnie's Ranch,Friendsanity: Marnie 9 <3,FRIENDSANITY -1632,Marnie's Ranch,Friendsanity: Marnie 10 <3,FRIENDSANITY -1634,Town,Friendsanity: Pam 1 <3,FRIENDSANITY -1635,Town,Friendsanity: Pam 2 <3,FRIENDSANITY -1636,Town,Friendsanity: Pam 3 <3,FRIENDSANITY -1637,Town,Friendsanity: Pam 4 <3,FRIENDSANITY -1638,Town,Friendsanity: Pam 5 <3,FRIENDSANITY -1639,Town,Friendsanity: Pam 6 <3,FRIENDSANITY -1640,Town,Friendsanity: Pam 7 <3,FRIENDSANITY -1641,Town,Friendsanity: Pam 8 <3,FRIENDSANITY -1642,Town,Friendsanity: Pam 9 <3,FRIENDSANITY -1643,Town,Friendsanity: Pam 10 <3,FRIENDSANITY -1645,Town,Friendsanity: Pierre 1 <3,FRIENDSANITY -1646,Town,Friendsanity: Pierre 2 <3,FRIENDSANITY -1647,Town,Friendsanity: Pierre 3 <3,FRIENDSANITY -1648,Town,Friendsanity: Pierre 4 <3,FRIENDSANITY -1649,Town,Friendsanity: Pierre 5 <3,FRIENDSANITY -1650,Town,Friendsanity: Pierre 6 <3,FRIENDSANITY -1651,Town,Friendsanity: Pierre 7 <3,FRIENDSANITY -1652,Town,Friendsanity: Pierre 8 <3,FRIENDSANITY -1653,Town,Friendsanity: Pierre 9 <3,FRIENDSANITY -1654,Town,Friendsanity: Pierre 10 <3,FRIENDSANITY -1656,Carpenter Shop,Friendsanity: Robin 1 <3,FRIENDSANITY -1657,Carpenter Shop,Friendsanity: Robin 2 <3,FRIENDSANITY -1658,Carpenter Shop,Friendsanity: Robin 3 <3,FRIENDSANITY -1659,Carpenter Shop,Friendsanity: Robin 4 <3,FRIENDSANITY -1660,Carpenter Shop,Friendsanity: Robin 5 <3,FRIENDSANITY -1661,Carpenter Shop,Friendsanity: Robin 6 <3,FRIENDSANITY -1662,Carpenter Shop,Friendsanity: Robin 7 <3,FRIENDSANITY -1663,Carpenter Shop,Friendsanity: Robin 8 <3,FRIENDSANITY -1664,Carpenter Shop,Friendsanity: Robin 9 <3,FRIENDSANITY -1665,Carpenter Shop,Friendsanity: Robin 10 <3,FRIENDSANITY -1667,The Desert,Friendsanity: Sandy 1 <3,FRIENDSANITY -1668,The Desert,Friendsanity: Sandy 2 <3,FRIENDSANITY -1669,The Desert,Friendsanity: Sandy 3 <3,FRIENDSANITY -1670,The Desert,Friendsanity: Sandy 4 <3,FRIENDSANITY -1671,The Desert,Friendsanity: Sandy 5 <3,FRIENDSANITY -1672,The Desert,Friendsanity: Sandy 6 <3,FRIENDSANITY -1673,The Desert,Friendsanity: Sandy 7 <3,FRIENDSANITY -1674,The Desert,Friendsanity: Sandy 8 <3,FRIENDSANITY -1675,The Desert,Friendsanity: Sandy 9 <3,FRIENDSANITY -1676,The Desert,Friendsanity: Sandy 10 <3,FRIENDSANITY -1678,Town,Friendsanity: Vincent 1 <3,FRIENDSANITY -1679,Town,Friendsanity: Vincent 2 <3,FRIENDSANITY -1680,Town,Friendsanity: Vincent 3 <3,FRIENDSANITY -1681,Town,Friendsanity: Vincent 4 <3,FRIENDSANITY -1682,Town,Friendsanity: Vincent 5 <3,FRIENDSANITY -1683,Town,Friendsanity: Vincent 6 <3,FRIENDSANITY -1684,Town,Friendsanity: Vincent 7 <3,FRIENDSANITY -1685,Town,Friendsanity: Vincent 8 <3,FRIENDSANITY -1686,Town,Friendsanity: Vincent 9 <3,FRIENDSANITY -1687,Town,Friendsanity: Vincent 10 <3,FRIENDSANITY -1689,Beach,Friendsanity: Willy 1 <3,FRIENDSANITY -1690,Beach,Friendsanity: Willy 2 <3,FRIENDSANITY -1691,Beach,Friendsanity: Willy 3 <3,FRIENDSANITY -1692,Beach,Friendsanity: Willy 4 <3,FRIENDSANITY -1693,Beach,Friendsanity: Willy 5 <3,FRIENDSANITY -1694,Beach,Friendsanity: Willy 6 <3,FRIENDSANITY -1695,Beach,Friendsanity: Willy 7 <3,FRIENDSANITY -1696,Beach,Friendsanity: Willy 8 <3,FRIENDSANITY -1697,Beach,Friendsanity: Willy 9 <3,FRIENDSANITY -1698,Beach,Friendsanity: Willy 10 <3,FRIENDSANITY -1700,Forest,Friendsanity: Wizard 1 <3,FRIENDSANITY -1701,Forest,Friendsanity: Wizard 2 <3,FRIENDSANITY -1702,Forest,Friendsanity: Wizard 3 <3,FRIENDSANITY -1703,Forest,Friendsanity: Wizard 4 <3,FRIENDSANITY -1704,Forest,Friendsanity: Wizard 5 <3,FRIENDSANITY -1705,Forest,Friendsanity: Wizard 6 <3,FRIENDSANITY -1706,Forest,Friendsanity: Wizard 7 <3,FRIENDSANITY -1707,Forest,Friendsanity: Wizard 8 <3,FRIENDSANITY -1708,Forest,Friendsanity: Wizard 9 <3,FRIENDSANITY -1709,Forest,Friendsanity: Wizard 10 <3,FRIENDSANITY -1710,Farm,Friendsanity: Pet 1 <3,FRIENDSANITY -1711,Farm,Friendsanity: Pet 2 <3,FRIENDSANITY -1712,Farm,Friendsanity: Pet 3 <3,FRIENDSANITY -1713,Farm,Friendsanity: Pet 4 <3,FRIENDSANITY -1714,Farm,Friendsanity: Pet 5 <3,FRIENDSANITY -1715,Farm,Friendsanity: Friend 1 <3,FRIENDSANITY -1716,Farm,Friendsanity: Friend 2 <3,FRIENDSANITY -1717,Farm,Friendsanity: Friend 3 <3,FRIENDSANITY -1718,Farm,Friendsanity: Friend 4 <3,FRIENDSANITY -1719,Farm,Friendsanity: Friend 5 <3,FRIENDSANITY -1720,Farm,Friendsanity: Friend 6 <3,FRIENDSANITY -1721,Farm,Friendsanity: Friend 7 <3,FRIENDSANITY -1722,Farm,Friendsanity: Friend 8 <3,FRIENDSANITY -1723,Farm,Friendsanity: Suitor 9 <3,FRIENDSANITY -1724,Farm,Friendsanity: Suitor 10 <3,FRIENDSANITY -1725,Farm,Friendsanity: Spouse 11 <3,FRIENDSANITY -1726,Farm,Friendsanity: Spouse 12 <3,FRIENDSANITY -1727,Farm,Friendsanity: Spouse 13 <3,FRIENDSANITY -1728,Farm,Friendsanity: Spouse 14 <3,FRIENDSANITY +id,region,name,tags,mod_name +1,Crafts Room,Spring Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY", +2,Crafts Room,Summer Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY", +3,Crafts Room,Fall Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY", +4,Crafts Room,Winter Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY", +5,Crafts Room,Construction Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY", +6,Crafts Room,Exotic Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY", +7,Pantry,Spring Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE", +8,Pantry,Summer Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE", +9,Pantry,Fall Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE", +10,Pantry,Quality Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE", +11,Pantry,Animal Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE", +12,Pantry,Artisan Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE", +13,Fish Tank,River Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY", +14,Fish Tank,Lake Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY", +15,Fish Tank,Ocean Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY", +16,Fish Tank,Night Fishing Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY", +17,Fish Tank,Crab Pot Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY", +18,Fish Tank,Specialty Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY", +19,Boiler Room,Blacksmith's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +20,Boiler Room,Geologist's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +21,Boiler Room,Adventurer's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +22,Bulletin Board,Chef's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +23,Bulletin Board,Dye Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +24,Bulletin Board,Field Research Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +25,Bulletin Board,Fodder Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +26,Bulletin Board,Enchanter's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY", +27,Vault,"2,500g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE", +28,Vault,"5,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE", +29,Vault,"10,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE", +30,Vault,"25,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE", +31,Abandoned JojaMart,The Missing Bundle,BUNDLE, +32,Crafts Room,Complete Crafts Room,"COMMUNITY_CENTER_ROOM,MANDATORY", +33,Pantry,Complete Pantry,"COMMUNITY_CENTER_ROOM,MANDATORY", +34,Fish Tank,Complete Fish Tank,"COMMUNITY_CENTER_ROOM,MANDATORY", +35,Boiler Room,Complete Boiler Room,"COMMUNITY_CENTER_ROOM,MANDATORY", +36,Bulletin Board,Complete Bulletin Board,"COMMUNITY_CENTER_ROOM,MANDATORY", +37,Vault,Complete Vault,"COMMUNITY_CENTER_ROOM,MANDATORY", +101,Pierre's General Store,Large Pack,BACKPACK, +102,Pierre's General Store,Deluxe Pack,BACKPACK, +103,Clint's Blacksmith,Copper Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", +104,Clint's Blacksmith,Iron Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", +105,Clint's Blacksmith,Gold Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", +106,Clint's Blacksmith,Iridium Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE", +107,Clint's Blacksmith,Copper Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE", +108,Clint's Blacksmith,Iron Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE", +109,Clint's Blacksmith,Gold Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE", +110,Clint's Blacksmith,Iridium Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE", +111,Clint's Blacksmith,Copper Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE", +112,Clint's Blacksmith,Iron Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE", +113,Clint's Blacksmith,Gold Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE", +114,Clint's Blacksmith,Iridium Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE", +115,Clint's Blacksmith,Copper Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE", +116,Clint's Blacksmith,Iron Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE", +117,Clint's Blacksmith,Gold Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE", +118,Clint's Blacksmith,Iridium Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE", +119,Clint's Blacksmith,Copper Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE", +120,Clint's Blacksmith,Iron Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE", +121,Clint's Blacksmith,Gold Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE", +122,Clint's Blacksmith,Iridium Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE", +123,Willy's Fish Shop,Purchase Training Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE", +124,Beach,Bamboo Pole Cutscene,"FISHING_ROD_UPGRADE,TOOL_UPGRADE", +125,Willy's Fish Shop,Purchase Fiberglass Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE", +126,Willy's Fish Shop,Purchase Iridium Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE", +201,The Mines - Floor 10,The Mines Floor 10 Treasure,"MANDATORY,THE_MINES_TREASURE", +202,The Mines - Floor 20,The Mines Floor 20 Treasure,"MANDATORY,THE_MINES_TREASURE", +203,The Mines - Floor 40,The Mines Floor 40 Treasure,"MANDATORY,THE_MINES_TREASURE", +204,The Mines - Floor 50,The Mines Floor 50 Treasure,"MANDATORY,THE_MINES_TREASURE", +205,The Mines - Floor 60,The Mines Floor 60 Treasure,"MANDATORY,THE_MINES_TREASURE", +206,The Mines - Floor 70,The Mines Floor 70 Treasure,"MANDATORY,THE_MINES_TREASURE", +207,The Mines - Floor 80,The Mines Floor 80 Treasure,"MANDATORY,THE_MINES_TREASURE", +208,The Mines - Floor 90,The Mines Floor 90 Treasure,"MANDATORY,THE_MINES_TREASURE", +209,The Mines - Floor 100,The Mines Floor 100 Treasure,"MANDATORY,THE_MINES_TREASURE", +210,The Mines - Floor 110,The Mines Floor 110 Treasure,"MANDATORY,THE_MINES_TREASURE", +211,The Mines - Floor 120,The Mines Floor 120 Treasure,"MANDATORY,THE_MINES_TREASURE", +212,Quarry Mine,Grim Reaper statue,MANDATORY, +213,The Mines,The Mines Entrance Cutscene,MANDATORY, +214,The Mines - Floor 5,Floor 5 Elevator,ELEVATOR, +215,The Mines - Floor 10,Floor 10 Elevator,ELEVATOR, +216,The Mines - Floor 15,Floor 15 Elevator,ELEVATOR, +217,The Mines - Floor 20,Floor 20 Elevator,ELEVATOR, +218,The Mines - Floor 25,Floor 25 Elevator,ELEVATOR, +219,The Mines - Floor 30,Floor 30 Elevator,ELEVATOR, +220,The Mines - Floor 35,Floor 35 Elevator,ELEVATOR, +221,The Mines - Floor 40,Floor 40 Elevator,ELEVATOR, +222,The Mines - Floor 45,Floor 45 Elevator,ELEVATOR, +223,The Mines - Floor 50,Floor 50 Elevator,ELEVATOR, +224,The Mines - Floor 55,Floor 55 Elevator,ELEVATOR, +225,The Mines - Floor 60,Floor 60 Elevator,ELEVATOR, +226,The Mines - Floor 65,Floor 65 Elevator,ELEVATOR, +227,The Mines - Floor 70,Floor 70 Elevator,ELEVATOR, +228,The Mines - Floor 75,Floor 75 Elevator,ELEVATOR, +229,The Mines - Floor 80,Floor 80 Elevator,ELEVATOR, +230,The Mines - Floor 85,Floor 85 Elevator,ELEVATOR, +231,The Mines - Floor 90,Floor 90 Elevator,ELEVATOR, +232,The Mines - Floor 95,Floor 95 Elevator,ELEVATOR, +233,The Mines - Floor 100,Floor 100 Elevator,ELEVATOR, +234,The Mines - Floor 105,Floor 105 Elevator,ELEVATOR, +235,The Mines - Floor 110,Floor 110 Elevator,ELEVATOR, +236,The Mines - Floor 115,Floor 115 Elevator,ELEVATOR, +237,The Mines - Floor 120,Floor 120 Elevator,ELEVATOR, +251,Volcano - Floor 10,Volcano Caldera Treasure,"MANDATORY,GINGER_ISLAND", +301,Stardew Valley,Level 1 Farming,"FARMING_LEVEL,SKILL_LEVEL", +302,Stardew Valley,Level 2 Farming,"FARMING_LEVEL,SKILL_LEVEL", +303,Stardew Valley,Level 3 Farming,"FARMING_LEVEL,SKILL_LEVEL", +304,Stardew Valley,Level 4 Farming,"FARMING_LEVEL,SKILL_LEVEL", +305,Stardew Valley,Level 5 Farming,"FARMING_LEVEL,SKILL_LEVEL", +306,Stardew Valley,Level 6 Farming,"FARMING_LEVEL,SKILL_LEVEL", +307,Stardew Valley,Level 7 Farming,"FARMING_LEVEL,SKILL_LEVEL", +308,Stardew Valley,Level 8 Farming,"FARMING_LEVEL,SKILL_LEVEL", +309,Stardew Valley,Level 9 Farming,"FARMING_LEVEL,SKILL_LEVEL", +310,Stardew Valley,Level 10 Farming,"FARMING_LEVEL,SKILL_LEVEL", +311,Stardew Valley,Level 1 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +312,Stardew Valley,Level 2 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +313,Stardew Valley,Level 3 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +314,Stardew Valley,Level 4 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +315,Stardew Valley,Level 5 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +316,Stardew Valley,Level 6 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +317,Stardew Valley,Level 7 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +318,Stardew Valley,Level 8 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +319,Stardew Valley,Level 9 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +320,Stardew Valley,Level 10 Fishing,"FISHING_LEVEL,SKILL_LEVEL", +321,Stardew Valley,Level 1 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +322,Stardew Valley,Level 2 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +323,Stardew Valley,Level 3 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +324,Stardew Valley,Level 4 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +325,Stardew Valley,Level 5 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +326,Stardew Valley,Level 6 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +327,Stardew Valley,Level 7 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +328,Stardew Valley,Level 8 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +329,Stardew Valley,Level 9 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +330,Stardew Valley,Level 10 Foraging,"FORAGING_LEVEL,SKILL_LEVEL", +331,Stardew Valley,Level 1 Mining,"MINING_LEVEL,SKILL_LEVEL", +332,Stardew Valley,Level 2 Mining,"MINING_LEVEL,SKILL_LEVEL", +333,Stardew Valley,Level 3 Mining,"MINING_LEVEL,SKILL_LEVEL", +334,Stardew Valley,Level 4 Mining,"MINING_LEVEL,SKILL_LEVEL", +335,Stardew Valley,Level 5 Mining,"MINING_LEVEL,SKILL_LEVEL", +336,Stardew Valley,Level 6 Mining,"MINING_LEVEL,SKILL_LEVEL", +337,Stardew Valley,Level 7 Mining,"MINING_LEVEL,SKILL_LEVEL", +338,Stardew Valley,Level 8 Mining,"MINING_LEVEL,SKILL_LEVEL", +339,Stardew Valley,Level 9 Mining,"MINING_LEVEL,SKILL_LEVEL", +340,Stardew Valley,Level 10 Mining,"MINING_LEVEL,SKILL_LEVEL", +341,Stardew Valley,Level 1 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +342,Stardew Valley,Level 2 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +343,Stardew Valley,Level 3 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +344,Stardew Valley,Level 4 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +345,Stardew Valley,Level 5 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +346,Stardew Valley,Level 6 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +347,Stardew Valley,Level 7 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +348,Stardew Valley,Level 8 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +349,Stardew Valley,Level 9 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +350,Stardew Valley,Level 10 Combat,"COMBAT_LEVEL,SKILL_LEVEL", +401,Carpenter Shop,Coop Blueprint,BUILDING_BLUEPRINT, +402,Carpenter Shop,Big Coop Blueprint,BUILDING_BLUEPRINT, +403,Carpenter Shop,Deluxe Coop Blueprint,BUILDING_BLUEPRINT, +404,Carpenter Shop,Barn Blueprint,BUILDING_BLUEPRINT, +405,Carpenter Shop,Big Barn Blueprint,BUILDING_BLUEPRINT, +406,Carpenter Shop,Deluxe Barn Blueprint,BUILDING_BLUEPRINT, +407,Carpenter Shop,Well Blueprint,BUILDING_BLUEPRINT, +408,Carpenter Shop,Silo Blueprint,BUILDING_BLUEPRINT, +409,Carpenter Shop,Mill Blueprint,BUILDING_BLUEPRINT, +410,Carpenter Shop,Shed Blueprint,BUILDING_BLUEPRINT, +411,Carpenter Shop,Big Shed Blueprint,BUILDING_BLUEPRINT, +412,Carpenter Shop,Fish Pond Blueprint,BUILDING_BLUEPRINT, +413,Carpenter Shop,Stable Blueprint,BUILDING_BLUEPRINT, +414,Carpenter Shop,Slime Hutch Blueprint,BUILDING_BLUEPRINT, +415,Carpenter Shop,Shipping Bin Blueprint,BUILDING_BLUEPRINT, +416,Carpenter Shop,Kitchen Blueprint,BUILDING_BLUEPRINT, +417,Carpenter Shop,Kids Room Blueprint,BUILDING_BLUEPRINT, +418,Carpenter Shop,Cellar Blueprint,BUILDING_BLUEPRINT, +501,Town,Introductions,"MANDATORY,QUEST", +502,Town,How To Win Friends,"MANDATORY,QUEST", +503,Farm,Getting Started,"MANDATORY,QUEST", +504,Farm,Raising Animals,"MANDATORY,QUEST", +505,Farm,Advancement,"MANDATORY,QUEST", +506,Museum,Archaeology,"MANDATORY,QUEST", +507,Wizard Tower,Meet The Wizard,"MANDATORY,QUEST", +508,Farm,Forging Ahead,"MANDATORY,QUEST", +509,Farm,Smelting,"MANDATORY,QUEST", +510,The Mines - Floor 5,Initiation,"MANDATORY,QUEST", +511,Forest,Robin's Lost Axe,"MANDATORY,QUEST", +512,Sam's House,Jodi's Request,"MANDATORY,QUEST", +513,Marnie's Ranch,"Mayor's ""Shorts""","MANDATORY,QUEST", +514,Tunnel Entrance,Blackberry Basket,"MANDATORY,QUEST", +515,Marnie's Ranch,Marnie's Request,"MANDATORY,QUEST", +516,Town,Pam Is Thirsty,"MANDATORY,QUEST", +517,Wizard Tower,A Dark Reagent,"MANDATORY,QUEST", +518,Marnie's Ranch,Cow's Delight,"MANDATORY,QUEST", +519,Skull Cavern Entrance,The Skull Key,"MANDATORY,QUEST", +520,Town,Crop Research,"MANDATORY,QUEST", +521,Town,Knee Therapy,"MANDATORY,QUEST", +522,Town,Robin's Request,"MANDATORY,QUEST", +523,Skull Cavern,Qi's Challenge,"MANDATORY,QUEST", +524,Desert,The Mysterious Qi,"MANDATORY,QUEST", +525,Town,Carving Pumpkins,"MANDATORY,QUEST", +526,Town,A Winter Mystery,"MANDATORY,QUEST", +527,Secret Woods,Strange Note,"MANDATORY,QUEST", +528,Skull Cavern,Cryptic Note,"MANDATORY,QUEST", +529,Town,Fresh Fruit,"MANDATORY,QUEST", +530,Town,Aquatic Research,"MANDATORY,QUEST", +531,Town,A Soldier's Star,"MANDATORY,QUEST", +532,Town,Mayor's Need,"MANDATORY,QUEST", +533,Saloon,Wanted: Lobster,"MANDATORY,QUEST", +534,Town,Pam Needs Juice,"MANDATORY,QUEST", +535,Sam's House,Fish Casserole,"MANDATORY,QUEST", +536,Beach,Catch A Squid,"MANDATORY,QUEST", +537,Saloon,Fish Stew,"MANDATORY,QUEST", +538,Town,Pierre's Notice,"MANDATORY,QUEST", +539,Town,Clint's Attempt,"MANDATORY,QUEST", +540,Town,A Favor For Clint,"MANDATORY,QUEST", +541,Wizard Tower,Staff Of Power,"MANDATORY,QUEST", +542,Town,Granny's Gift,"MANDATORY,QUEST", +543,Saloon,Exotic Spirits,"MANDATORY,QUEST", +544,Town,Catch a Lingcod,"MANDATORY,QUEST", +545,Island West,The Pirate's Wife,"GINGER_ISLAND,MANDATORY,QUEST", +546,Railroad,Dark Talisman,"MANDATORY,QUEST", +547,Witch's Swamp,Goblin Problem,"MANDATORY,QUEST", +548,Witch's Hut,Magic Ink,"MANDATORY,QUEST", +601,JotPK World 1,JotPK: Boots 1,"ARCADE_MACHINE,JOTPK", +602,JotPK World 1,JotPK: Boots 2,"ARCADE_MACHINE,JOTPK", +603,JotPK World 1,JotPK: Gun 1,"ARCADE_MACHINE,JOTPK", +604,JotPK World 2,JotPK: Gun 2,"ARCADE_MACHINE,JOTPK", +605,JotPK World 2,JotPK: Gun 3,"ARCADE_MACHINE,JOTPK", +606,JotPK World 3,JotPK: Super Gun,"ARCADE_MACHINE,JOTPK", +607,JotPK World 1,JotPK: Ammo 1,"ARCADE_MACHINE,JOTPK", +608,JotPK World 2,JotPK: Ammo 2,"ARCADE_MACHINE,JOTPK", +609,JotPK World 3,JotPK: Ammo 3,"ARCADE_MACHINE,JOTPK", +610,JotPK World 1,JotPK: Cowboy 1,"ARCADE_MACHINE,JOTPK", +611,JotPK World 2,JotPK: Cowboy 2,"ARCADE_MACHINE,JOTPK", +612,Junimo Kart 1,Junimo Kart: Crumble Cavern,"ARCADE_MACHINE,JUNIMO_KART", +613,Junimo Kart 1,Junimo Kart: Slippery Slopes,"ARCADE_MACHINE,JUNIMO_KART", +614,Junimo Kart 2,Junimo Kart: Secret Level,"ARCADE_MACHINE,JUNIMO_KART", +615,Junimo Kart 2,Junimo Kart: The Gem Sea Giant,"ARCADE_MACHINE,JUNIMO_KART", +616,Junimo Kart 2,Junimo Kart: Slomp's Stomp,"ARCADE_MACHINE,JUNIMO_KART", +617,Junimo Kart 2,Junimo Kart: Ghastly Galleon,"ARCADE_MACHINE,JUNIMO_KART", +618,Junimo Kart 3,Junimo Kart: Glowshroom Grotto,"ARCADE_MACHINE,JUNIMO_KART", +619,Junimo Kart 3,Junimo Kart: Red Hot Rollercoaster,"ARCADE_MACHINE,JUNIMO_KART", +620,JotPK World 3,Journey of the Prairie King Victory,"ARCADE_MACHINE_VICTORY,JOTPK", +621,Junimo Kart 3,Junimo Kart: Sunset Speedway (Victory),"ARCADE_MACHINE_VICTORY,JUNIMO_KART", +701,Secret Woods,Old Master Cannoli,MANDATORY, +702,Beach,Beach Bridge Repair,MANDATORY, +703,Desert,Galaxy Sword Shrine,MANDATORY, +704,Farmhouse,Have a Baby,MANDATORY, +705,Farmhouse,Have Another Baby,MANDATORY, +801,Town,Help Wanted: Gathering 1,HELP_WANTED, +802,Town,Help Wanted: Gathering 2,HELP_WANTED, +803,Town,Help Wanted: Gathering 3,HELP_WANTED, +804,Town,Help Wanted: Gathering 4,HELP_WANTED, +805,Town,Help Wanted: Gathering 5,HELP_WANTED, +806,Town,Help Wanted: Gathering 6,HELP_WANTED, +807,Town,Help Wanted: Gathering 7,HELP_WANTED, +808,Town,Help Wanted: Gathering 8,HELP_WANTED, +811,Town,Help Wanted: Slay Monsters 1,HELP_WANTED, +812,Town,Help Wanted: Slay Monsters 2,HELP_WANTED, +813,Town,Help Wanted: Slay Monsters 3,HELP_WANTED, +814,Town,Help Wanted: Slay Monsters 4,HELP_WANTED, +815,Town,Help Wanted: Slay Monsters 5,HELP_WANTED, +816,Town,Help Wanted: Slay Monsters 6,HELP_WANTED, +817,Town,Help Wanted: Slay Monsters 7,HELP_WANTED, +818,Town,Help Wanted: Slay Monsters 8,HELP_WANTED, +821,Town,Help Wanted: Fishing 1,HELP_WANTED, +822,Town,Help Wanted: Fishing 2,HELP_WANTED, +823,Town,Help Wanted: Fishing 3,HELP_WANTED, +824,Town,Help Wanted: Fishing 4,HELP_WANTED, +825,Town,Help Wanted: Fishing 5,HELP_WANTED, +826,Town,Help Wanted: Fishing 6,HELP_WANTED, +827,Town,Help Wanted: Fishing 7,HELP_WANTED, +828,Town,Help Wanted: Fishing 8,HELP_WANTED, +841,Town,Help Wanted: Item Delivery 1,HELP_WANTED, +842,Town,Help Wanted: Item Delivery 2,HELP_WANTED, +843,Town,Help Wanted: Item Delivery 3,HELP_WANTED, +844,Town,Help Wanted: Item Delivery 4,HELP_WANTED, +845,Town,Help Wanted: Item Delivery 5,HELP_WANTED, +846,Town,Help Wanted: Item Delivery 6,HELP_WANTED, +847,Town,Help Wanted: Item Delivery 7,HELP_WANTED, +848,Town,Help Wanted: Item Delivery 8,HELP_WANTED, +849,Town,Help Wanted: Item Delivery 9,HELP_WANTED, +850,Town,Help Wanted: Item Delivery 10,HELP_WANTED, +851,Town,Help Wanted: Item Delivery 11,HELP_WANTED, +852,Town,Help Wanted: Item Delivery 12,HELP_WANTED, +853,Town,Help Wanted: Item Delivery 13,HELP_WANTED, +854,Town,Help Wanted: Item Delivery 14,HELP_WANTED, +855,Town,Help Wanted: Item Delivery 15,HELP_WANTED, +856,Town,Help Wanted: Item Delivery 16,HELP_WANTED, +857,Town,Help Wanted: Item Delivery 17,HELP_WANTED, +858,Town,Help Wanted: Item Delivery 18,HELP_WANTED, +859,Town,Help Wanted: Item Delivery 19,HELP_WANTED, +860,Town,Help Wanted: Item Delivery 20,HELP_WANTED, +861,Town,Help Wanted: Item Delivery 21,HELP_WANTED, +862,Town,Help Wanted: Item Delivery 22,HELP_WANTED, +863,Town,Help Wanted: Item Delivery 23,HELP_WANTED, +864,Town,Help Wanted: Item Delivery 24,HELP_WANTED, +865,Town,Help Wanted: Item Delivery 25,HELP_WANTED, +866,Town,Help Wanted: Item Delivery 26,HELP_WANTED, +867,Town,Help Wanted: Item Delivery 27,HELP_WANTED, +868,Town,Help Wanted: Item Delivery 28,HELP_WANTED, +869,Town,Help Wanted: Item Delivery 29,HELP_WANTED, +870,Town,Help Wanted: Item Delivery 30,HELP_WANTED, +871,Town,Help Wanted: Item Delivery 31,HELP_WANTED, +872,Town,Help Wanted: Item Delivery 32,HELP_WANTED, +901,Forest,Traveling Merchant Sunday Item 1,"MANDATORY,TRAVELING_MERCHANT", +902,Forest,Traveling Merchant Sunday Item 2,"MANDATORY,TRAVELING_MERCHANT", +903,Forest,Traveling Merchant Sunday Item 3,"MANDATORY,TRAVELING_MERCHANT", +911,Forest,Traveling Merchant Monday Item 1,"MANDATORY,TRAVELING_MERCHANT", +912,Forest,Traveling Merchant Monday Item 2,"MANDATORY,TRAVELING_MERCHANT", +913,Forest,Traveling Merchant Monday Item 3,"MANDATORY,TRAVELING_MERCHANT", +921,Forest,Traveling Merchant Tuesday Item 1,"MANDATORY,TRAVELING_MERCHANT", +922,Forest,Traveling Merchant Tuesday Item 2,"MANDATORY,TRAVELING_MERCHANT", +923,Forest,Traveling Merchant Tuesday Item 3,"MANDATORY,TRAVELING_MERCHANT", +931,Forest,Traveling Merchant Wednesday Item 1,"MANDATORY,TRAVELING_MERCHANT", +932,Forest,Traveling Merchant Wednesday Item 2,"MANDATORY,TRAVELING_MERCHANT", +933,Forest,Traveling Merchant Wednesday Item 3,"MANDATORY,TRAVELING_MERCHANT", +941,Forest,Traveling Merchant Thursday Item 1,"MANDATORY,TRAVELING_MERCHANT", +942,Forest,Traveling Merchant Thursday Item 2,"MANDATORY,TRAVELING_MERCHANT", +943,Forest,Traveling Merchant Thursday Item 3,"MANDATORY,TRAVELING_MERCHANT", +951,Forest,Traveling Merchant Friday Item 1,"MANDATORY,TRAVELING_MERCHANT", +952,Forest,Traveling Merchant Friday Item 2,"MANDATORY,TRAVELING_MERCHANT", +953,Forest,Traveling Merchant Friday Item 3,"MANDATORY,TRAVELING_MERCHANT", +961,Forest,Traveling Merchant Saturday Item 1,"MANDATORY,TRAVELING_MERCHANT", +962,Forest,Traveling Merchant Saturday Item 2,"MANDATORY,TRAVELING_MERCHANT", +963,Forest,Traveling Merchant Saturday Item 3,"MANDATORY,TRAVELING_MERCHANT", +1001,Mountain,Fishsanity: Carp,FISHSANITY, +1002,Beach,Fishsanity: Herring,FISHSANITY, +1003,Forest,Fishsanity: Smallmouth Bass,FISHSANITY, +1004,Beach,Fishsanity: Anchovy,FISHSANITY, +1005,Beach,Fishsanity: Sardine,FISHSANITY, +1006,Forest,Fishsanity: Sunfish,FISHSANITY, +1007,Forest,Fishsanity: Perch,FISHSANITY, +1008,Forest,Fishsanity: Chub,FISHSANITY, +1009,Forest,Fishsanity: Bream,FISHSANITY, +1010,Beach,Fishsanity: Red Snapper,FISHSANITY, +1011,Beach,Fishsanity: Sea Cucumber,FISHSANITY, +1012,Forest,Fishsanity: Rainbow Trout,FISHSANITY, +1013,Forest,Fishsanity: Walleye,FISHSANITY, +1014,Forest,Fishsanity: Shad,FISHSANITY, +1015,Mountain,Fishsanity: Bullhead,FISHSANITY, +1016,Mountain,Fishsanity: Largemouth Bass,FISHSANITY, +1017,Forest,Fishsanity: Salmon,FISHSANITY, +1018,The Mines - Floor 20,Fishsanity: Ghostfish,FISHSANITY, +1019,Beach,Fishsanity: Tilapia,FISHSANITY, +1020,Secret Woods,Fishsanity: Woodskip,FISHSANITY, +1021,Beach,Fishsanity: Flounder,FISHSANITY, +1022,Beach,Fishsanity: Halibut,FISHSANITY, +1023,Island West,Fishsanity: Lionfish,"FISHSANITY,GINGER_ISLAND", +1024,Mutant Bug Lair,Fishsanity: Slimejack,FISHSANITY, +1025,Forest,Fishsanity: Midnight Carp,FISHSANITY, +1026,Beach,Fishsanity: Red Mullet,FISHSANITY, +1027,Forest,Fishsanity: Pike,FISHSANITY, +1028,Forest,Fishsanity: Tiger Trout,FISHSANITY, +1029,Island West,Fishsanity: Blue Discus,"FISHSANITY,GINGER_ISLAND", +1030,Beach,Fishsanity: Albacore,FISHSANITY, +1031,Desert,Fishsanity: Sandfish,FISHSANITY, +1032,The Mines - Floor 20,Fishsanity: Stonefish,FISHSANITY, +1033,Beach,Fishsanity: Tuna,FISHSANITY, +1034,Beach,Fishsanity: Eel,FISHSANITY, +1035,Forest,Fishsanity: Catfish,FISHSANITY, +1036,Beach,Fishsanity: Squid,FISHSANITY, +1037,Mountain,Fishsanity: Sturgeon,FISHSANITY, +1038,Forest,Fishsanity: Dorado,FISHSANITY, +1039,Beach,Fishsanity: Pufferfish,FISHSANITY, +1040,Witch's Swamp,Fishsanity: Void Salmon,FISHSANITY, +1041,Beach,Fishsanity: Super Cucumber,FISHSANITY, +1042,Pirate Cove,Fishsanity: Stingray,"FISHSANITY,GINGER_ISLAND", +1043,The Mines - Floor 60,Fishsanity: Ice Pip,FISHSANITY, +1044,Forest,Fishsanity: Lingcod,FISHSANITY, +1045,Desert,Fishsanity: Scorpion Carp,FISHSANITY, +1046,The Mines - Floor 100,Fishsanity: Lava Eel,FISHSANITY, +1047,Beach,Fishsanity: Octopus,FISHSANITY, +1048,Beach,Fishsanity: Midnight Squid,FISHSANITY, +1049,Beach,Fishsanity: Spook Fish,FISHSANITY, +1050,Beach,Fishsanity: Blobfish,FISHSANITY, +1051,Beach,Fishsanity: Crimsonfish,FISHSANITY, +1052,Town,Fishsanity: Angler,FISHSANITY, +1053,Mountain,Fishsanity: Legend,FISHSANITY, +1054,Forest,Fishsanity: Glacierfish,FISHSANITY, +1055,Sewer,Fishsanity: Mutant Carp,FISHSANITY, +1056,Town,Fishsanity: Crayfish,FISHSANITY, +1057,Town,Fishsanity: Snail,FISHSANITY, +1058,Town,Fishsanity: Periwinkle,FISHSANITY, +1059,Beach,Fishsanity: Lobster,FISHSANITY, +1060,Beach,Fishsanity: Clam,FISHSANITY, +1061,Beach,Fishsanity: Crab,FISHSANITY, +1062,Beach,Fishsanity: Cockle,FISHSANITY, +1063,Beach,Fishsanity: Mussel,FISHSANITY, +1064,Beach,Fishsanity: Shrimp,FISHSANITY, +1065,Beach,Fishsanity: Oyster,FISHSANITY, +1100,Stardew Valley,Museumsanity: 5 Donations,MUSEUM_MILESTONES, +1101,Stardew Valley,Museumsanity: 10 Donations,MUSEUM_MILESTONES, +1102,Stardew Valley,Museumsanity: 15 Donations,MUSEUM_MILESTONES, +1103,Stardew Valley,Museumsanity: 20 Donations,MUSEUM_MILESTONES, +1104,Stardew Valley,Museumsanity: 25 Donations,MUSEUM_MILESTONES, +1105,Stardew Valley,Museumsanity: 30 Donations,MUSEUM_MILESTONES, +1106,Stardew Valley,Museumsanity: 35 Donations,MUSEUM_MILESTONES, +1107,Stardew Valley,Museumsanity: 40 Donations,MUSEUM_MILESTONES, +1108,Stardew Valley,Museumsanity: 50 Donations,MUSEUM_MILESTONES, +1109,Stardew Valley,Museumsanity: 60 Donations,MUSEUM_MILESTONES, +1110,Stardew Valley,Museumsanity: 70 Donations,MUSEUM_MILESTONES, +1111,Stardew Valley,Museumsanity: 80 Donations,MUSEUM_MILESTONES, +1112,Stardew Valley,Museumsanity: 90 Donations,MUSEUM_MILESTONES, +1113,Stardew Valley,Museumsanity: 95 Donations,MUSEUM_MILESTONES, +1114,Stardew Valley,Museumsanity: 11 Minerals,MUSEUM_MILESTONES, +1115,Stardew Valley,Museumsanity: 21 Minerals,MUSEUM_MILESTONES, +1116,Stardew Valley,Museumsanity: 31 Minerals,MUSEUM_MILESTONES, +1117,Stardew Valley,Museumsanity: 41 Minerals,MUSEUM_MILESTONES, +1118,Stardew Valley,Museumsanity: 50 Minerals,MUSEUM_MILESTONES, +1119,Stardew Valley,Museumsanity: 3 Artifacts,MUSEUM_MILESTONES, +1120,Stardew Valley,Museumsanity: 6 Artifacts,MUSEUM_MILESTONES, +1121,Stardew Valley,Museumsanity: 9 Artifacts,MUSEUM_MILESTONES, +1122,Stardew Valley,Museumsanity: 11 Artifacts,MUSEUM_MILESTONES, +1123,Stardew Valley,Museumsanity: 15 Artifacts,MUSEUM_MILESTONES, +1124,Stardew Valley,Museumsanity: 20 Artifacts,MUSEUM_MILESTONES, +1125,Stardew Valley,Museumsanity: Dwarf Scrolls,MUSEUM_MILESTONES, +1126,Stardew Valley,Museumsanity: Skeleton Front,MUSEUM_MILESTONES, +1127,Stardew Valley,Museumsanity: Skeleton Middle,MUSEUM_MILESTONES, +1128,Stardew Valley,Museumsanity: Skeleton Back,MUSEUM_MILESTONES, +1201,The Mines - Floor 20,Museumsanity: Dwarf Scroll I,MUSEUM_DONATIONS, +1202,The Mines - Floor 20,Museumsanity: Dwarf Scroll II,MUSEUM_DONATIONS, +1203,The Mines - Floor 60,Museumsanity: Dwarf Scroll III,MUSEUM_DONATIONS, +1204,The Mines - Floor 100,Museumsanity: Dwarf Scroll IV,MUSEUM_DONATIONS, +1205,Town,Museumsanity: Chipped Amphora,MUSEUM_DONATIONS, +1206,Forest,Museumsanity: Arrowhead,MUSEUM_DONATIONS, +1207,Forest,Museumsanity: Ancient Doll,MUSEUM_DONATIONS, +1208,Forest,Museumsanity: Elvish Jewelry,MUSEUM_DONATIONS, +1209,Forest,Museumsanity: Chewing Stick,MUSEUM_DONATIONS, +1210,Forest,Museumsanity: Ornamental Fan,MUSEUM_DONATIONS, +1211,Mountain,Museumsanity: Dinosaur Egg,MUSEUM_DONATIONS, +1212,Stardew Valley,Museumsanity: Rare Disc,MUSEUM_DONATIONS, +1213,Forest,Museumsanity: Ancient Sword,MUSEUM_DONATIONS, +1214,Town,Museumsanity: Rusty Spoon,MUSEUM_DONATIONS, +1215,Farm,Museumsanity: Rusty Spur,MUSEUM_DONATIONS, +1216,Mountain,Museumsanity: Rusty Cog,MUSEUM_DONATIONS, +1217,Farm,Museumsanity: Chicken Statue,MUSEUM_DONATIONS, +1218,Forest,Museumsanity: Ancient Seed,"MUSEUM_DONATIONS,MUSEUM_MILESTONES", +1219,Forest,Museumsanity: Prehistoric Tool,MUSEUM_DONATIONS, +1220,Beach,Museumsanity: Dried Starfish,MUSEUM_DONATIONS, +1221,Beach,Museumsanity: Anchor,MUSEUM_DONATIONS, +1222,Beach,Museumsanity: Glass Shards,MUSEUM_DONATIONS, +1223,Forest,Museumsanity: Bone Flute,MUSEUM_DONATIONS, +1224,Forest,Museumsanity: Prehistoric Handaxe,MUSEUM_DONATIONS, +1225,The Mines - Floor 20,Museumsanity: Dwarvish Helm,MUSEUM_DONATIONS, +1226,The Mines - Floor 60,Museumsanity: Dwarf Gadget,MUSEUM_DONATIONS, +1227,Forest,Museumsanity: Ancient Drum,MUSEUM_DONATIONS, +1228,Desert,Museumsanity: Golden Mask,MUSEUM_DONATIONS, +1229,Desert,Museumsanity: Golden Relic,MUSEUM_DONATIONS, +1230,Town,Museumsanity: Strange Doll (Green),MUSEUM_DONATIONS, +1231,Desert,Museumsanity: Strange Doll,MUSEUM_DONATIONS, +1232,Forest,Museumsanity: Prehistoric Scapula,MUSEUM_DONATIONS, +1233,Forest,Museumsanity: Prehistoric Tibia,MUSEUM_DONATIONS, +1234,Dig Site,Museumsanity: Prehistoric Skull,MUSEUM_DONATIONS, +1235,Dig Site,Museumsanity: Skeletal Hand,MUSEUM_DONATIONS, +1236,Dig Site,Museumsanity: Prehistoric Rib,MUSEUM_DONATIONS, +1237,Dig Site,Museumsanity: Prehistoric Vertebra,MUSEUM_DONATIONS, +1238,Dig Site,Museumsanity: Skeletal Tail,MUSEUM_DONATIONS, +1239,Dig Site,Museumsanity: Nautilus Fossil,MUSEUM_DONATIONS, +1240,Forest,Museumsanity: Amphibian Fossil,MUSEUM_DONATIONS, +1241,Forest,Museumsanity: Palm Fossil,MUSEUM_DONATIONS, +1242,Forest,Museumsanity: Trilobite,MUSEUM_DONATIONS, +1243,The Mines - Floor 20,Museumsanity: Quartz,MUSEUM_DONATIONS, +1244,The Mines - Floor 100,Museumsanity: Fire Quartz,MUSEUM_DONATIONS, +1245,The Mines - Floor 60,Museumsanity: Frozen Tear,MUSEUM_DONATIONS, +1246,The Mines - Floor 20,Museumsanity: Earth Crystal,MUSEUM_DONATIONS, +1247,The Mines - Floor 100,Museumsanity: Emerald,MUSEUM_DONATIONS, +1248,The Mines - Floor 60,Museumsanity: Aquamarine,MUSEUM_DONATIONS, +1249,The Mines - Floor 100,Museumsanity: Ruby,MUSEUM_DONATIONS, +1250,The Mines - Floor 20,Museumsanity: Amethyst,MUSEUM_DONATIONS, +1251,The Mines - Floor 20,Museumsanity: Topaz,MUSEUM_DONATIONS, +1252,The Mines - Floor 60,Museumsanity: Jade,MUSEUM_DONATIONS, +1253,The Mines - Floor 60,Museumsanity: Diamond,MUSEUM_DONATIONS, +1254,Skull Cavern Floor 100,Museumsanity: Prismatic Shard,MUSEUM_DONATIONS, +1255,Town,Museumsanity: Alamite,MUSEUM_DONATIONS, +1256,Town,Museumsanity: Bixite,MUSEUM_DONATIONS, +1257,Town,Museumsanity: Baryte,MUSEUM_DONATIONS, +1258,Town,Museumsanity: Aerinite,MUSEUM_DONATIONS, +1259,Town,Museumsanity: Calcite,MUSEUM_DONATIONS, +1260,Town,Museumsanity: Dolomite,MUSEUM_DONATIONS, +1261,Town,Museumsanity: Esperite,MUSEUM_DONATIONS, +1262,Town,Museumsanity: Fluorapatite,MUSEUM_DONATIONS, +1263,Town,Museumsanity: Geminite,MUSEUM_DONATIONS, +1264,Town,Museumsanity: Helvite,MUSEUM_DONATIONS, +1265,Town,Museumsanity: Jamborite,MUSEUM_DONATIONS, +1266,Town,Museumsanity: Jagoite,MUSEUM_DONATIONS, +1267,Town,Museumsanity: Kyanite,MUSEUM_DONATIONS, +1268,Town,Museumsanity: Lunarite,MUSEUM_DONATIONS, +1269,Town,Museumsanity: Malachite,MUSEUM_DONATIONS, +1270,Town,Museumsanity: Neptunite,MUSEUM_DONATIONS, +1271,Town,Museumsanity: Lemon Stone,MUSEUM_DONATIONS, +1272,Town,Museumsanity: Nekoite,MUSEUM_DONATIONS, +1273,Town,Museumsanity: Orpiment,MUSEUM_DONATIONS, +1274,Town,Museumsanity: Petrified Slime,MUSEUM_DONATIONS, +1275,Town,Museumsanity: Thunder Egg,MUSEUM_DONATIONS, +1276,Town,Museumsanity: Pyrite,MUSEUM_DONATIONS, +1277,Town,Museumsanity: Ocean Stone,MUSEUM_DONATIONS, +1278,Town,Museumsanity: Ghost Crystal,MUSEUM_DONATIONS, +1279,Town,Museumsanity: Tigerseye,MUSEUM_DONATIONS, +1280,Town,Museumsanity: Jasper,MUSEUM_DONATIONS, +1281,Town,Museumsanity: Opal,MUSEUM_DONATIONS, +1282,Town,Museumsanity: Fire Opal,MUSEUM_DONATIONS, +1283,Town,Museumsanity: Celestine,MUSEUM_DONATIONS, +1284,Town,Museumsanity: Marble,MUSEUM_DONATIONS, +1285,Town,Museumsanity: Sandstone,MUSEUM_DONATIONS, +1286,Town,Museumsanity: Granite,MUSEUM_DONATIONS, +1287,Town,Museumsanity: Basalt,MUSEUM_DONATIONS, +1288,Town,Museumsanity: Limestone,MUSEUM_DONATIONS, +1289,Town,Museumsanity: Soapstone,MUSEUM_DONATIONS, +1290,Town,Museumsanity: Hematite,MUSEUM_DONATIONS, +1291,Town,Museumsanity: Mudstone,MUSEUM_DONATIONS, +1292,Town,Museumsanity: Obsidian,MUSEUM_DONATIONS, +1293,Town,Museumsanity: Slate,MUSEUM_DONATIONS, +1294,Town,Museumsanity: Fairy Stone,MUSEUM_DONATIONS, +1295,Town,Museumsanity: Star Shards,MUSEUM_DONATIONS, +1301,Town,Friendsanity: Alex 1 <3,FRIENDSANITY, +1302,Town,Friendsanity: Alex 2 <3,FRIENDSANITY, +1303,Town,Friendsanity: Alex 3 <3,FRIENDSANITY, +1304,Town,Friendsanity: Alex 4 <3,FRIENDSANITY, +1305,Town,Friendsanity: Alex 5 <3,FRIENDSANITY, +1306,Town,Friendsanity: Alex 6 <3,FRIENDSANITY, +1307,Town,Friendsanity: Alex 7 <3,FRIENDSANITY, +1308,Town,Friendsanity: Alex 8 <3,FRIENDSANITY, +1309,Town,Friendsanity: Alex 9 <3,FRIENDSANITY, +1310,Town,Friendsanity: Alex 10 <3,FRIENDSANITY, +1311,Town,Friendsanity: Alex 11 <3,FRIENDSANITY, +1312,Town,Friendsanity: Alex 12 <3,FRIENDSANITY, +1313,Town,Friendsanity: Alex 13 <3,FRIENDSANITY, +1314,Town,Friendsanity: Alex 14 <3,FRIENDSANITY, +1315,Beach,Friendsanity: Elliott 1 <3,FRIENDSANITY, +1316,Beach,Friendsanity: Elliott 2 <3,FRIENDSANITY, +1317,Beach,Friendsanity: Elliott 3 <3,FRIENDSANITY, +1318,Beach,Friendsanity: Elliott 4 <3,FRIENDSANITY, +1319,Beach,Friendsanity: Elliott 5 <3,FRIENDSANITY, +1320,Beach,Friendsanity: Elliott 6 <3,FRIENDSANITY, +1321,Beach,Friendsanity: Elliott 7 <3,FRIENDSANITY, +1322,Beach,Friendsanity: Elliott 8 <3,FRIENDSANITY, +1323,Beach,Friendsanity: Elliott 9 <3,FRIENDSANITY, +1324,Beach,Friendsanity: Elliott 10 <3,FRIENDSANITY, +1325,Beach,Friendsanity: Elliott 11 <3,FRIENDSANITY, +1326,Beach,Friendsanity: Elliott 12 <3,FRIENDSANITY, +1327,Beach,Friendsanity: Elliott 13 <3,FRIENDSANITY, +1328,Beach,Friendsanity: Elliott 14 <3,FRIENDSANITY, +1329,Town,Friendsanity: Harvey 1 <3,FRIENDSANITY, +1330,Town,Friendsanity: Harvey 2 <3,FRIENDSANITY, +1331,Town,Friendsanity: Harvey 3 <3,FRIENDSANITY, +1332,Town,Friendsanity: Harvey 4 <3,FRIENDSANITY, +1333,Town,Friendsanity: Harvey 5 <3,FRIENDSANITY, +1334,Town,Friendsanity: Harvey 6 <3,FRIENDSANITY, +1335,Town,Friendsanity: Harvey 7 <3,FRIENDSANITY, +1336,Town,Friendsanity: Harvey 8 <3,FRIENDSANITY, +1337,Town,Friendsanity: Harvey 9 <3,FRIENDSANITY, +1338,Town,Friendsanity: Harvey 10 <3,FRIENDSANITY, +1339,Town,Friendsanity: Harvey 11 <3,FRIENDSANITY, +1340,Town,Friendsanity: Harvey 12 <3,FRIENDSANITY, +1341,Town,Friendsanity: Harvey 13 <3,FRIENDSANITY, +1342,Town,Friendsanity: Harvey 14 <3,FRIENDSANITY, +1343,Town,Friendsanity: Sam 1 <3,FRIENDSANITY, +1344,Town,Friendsanity: Sam 2 <3,FRIENDSANITY, +1345,Town,Friendsanity: Sam 3 <3,FRIENDSANITY, +1346,Town,Friendsanity: Sam 4 <3,FRIENDSANITY, +1347,Town,Friendsanity: Sam 5 <3,FRIENDSANITY, +1348,Town,Friendsanity: Sam 6 <3,FRIENDSANITY, +1349,Town,Friendsanity: Sam 7 <3,FRIENDSANITY, +1350,Town,Friendsanity: Sam 8 <3,FRIENDSANITY, +1351,Town,Friendsanity: Sam 9 <3,FRIENDSANITY, +1352,Town,Friendsanity: Sam 10 <3,FRIENDSANITY, +1353,Town,Friendsanity: Sam 11 <3,FRIENDSANITY, +1354,Town,Friendsanity: Sam 12 <3,FRIENDSANITY, +1355,Town,Friendsanity: Sam 13 <3,FRIENDSANITY, +1356,Town,Friendsanity: Sam 14 <3,FRIENDSANITY, +1357,Carpenter Shop,Friendsanity: Sebastian 1 <3,FRIENDSANITY, +1358,Carpenter Shop,Friendsanity: Sebastian 2 <3,FRIENDSANITY, +1359,Carpenter Shop,Friendsanity: Sebastian 3 <3,FRIENDSANITY, +1360,Carpenter Shop,Friendsanity: Sebastian 4 <3,FRIENDSANITY, +1361,Carpenter Shop,Friendsanity: Sebastian 5 <3,FRIENDSANITY, +1362,Carpenter Shop,Friendsanity: Sebastian 6 <3,FRIENDSANITY, +1363,Carpenter Shop,Friendsanity: Sebastian 7 <3,FRIENDSANITY, +1364,Carpenter Shop,Friendsanity: Sebastian 8 <3,FRIENDSANITY, +1365,Carpenter Shop,Friendsanity: Sebastian 9 <3,FRIENDSANITY, +1366,Carpenter Shop,Friendsanity: Sebastian 10 <3,FRIENDSANITY, +1367,Carpenter Shop,Friendsanity: Sebastian 11 <3,FRIENDSANITY, +1368,Carpenter Shop,Friendsanity: Sebastian 12 <3,FRIENDSANITY, +1369,Carpenter Shop,Friendsanity: Sebastian 13 <3,FRIENDSANITY, +1370,Carpenter Shop,Friendsanity: Sebastian 14 <3,FRIENDSANITY, +1371,Marnie's Ranch,Friendsanity: Shane 1 <3,FRIENDSANITY, +1372,Marnie's Ranch,Friendsanity: Shane 2 <3,FRIENDSANITY, +1373,Marnie's Ranch,Friendsanity: Shane 3 <3,FRIENDSANITY, +1374,Marnie's Ranch,Friendsanity: Shane 4 <3,FRIENDSANITY, +1375,Marnie's Ranch,Friendsanity: Shane 5 <3,FRIENDSANITY, +1376,Marnie's Ranch,Friendsanity: Shane 6 <3,FRIENDSANITY, +1377,Marnie's Ranch,Friendsanity: Shane 7 <3,FRIENDSANITY, +1378,Marnie's Ranch,Friendsanity: Shane 8 <3,FRIENDSANITY, +1379,Marnie's Ranch,Friendsanity: Shane 9 <3,FRIENDSANITY, +1380,Marnie's Ranch,Friendsanity: Shane 10 <3,FRIENDSANITY, +1381,Marnie's Ranch,Friendsanity: Shane 11 <3,FRIENDSANITY, +1382,Marnie's Ranch,Friendsanity: Shane 12 <3,FRIENDSANITY, +1383,Marnie's Ranch,Friendsanity: Shane 13 <3,FRIENDSANITY, +1384,Marnie's Ranch,Friendsanity: Shane 14 <3,FRIENDSANITY, +1385,Town,Friendsanity: Abigail 1 <3,FRIENDSANITY, +1386,Town,Friendsanity: Abigail 2 <3,FRIENDSANITY, +1387,Town,Friendsanity: Abigail 3 <3,FRIENDSANITY, +1388,Town,Friendsanity: Abigail 4 <3,FRIENDSANITY, +1389,Town,Friendsanity: Abigail 5 <3,FRIENDSANITY, +1390,Town,Friendsanity: Abigail 6 <3,FRIENDSANITY, +1391,Town,Friendsanity: Abigail 7 <3,FRIENDSANITY, +1392,Town,Friendsanity: Abigail 8 <3,FRIENDSANITY, +1393,Town,Friendsanity: Abigail 9 <3,FRIENDSANITY, +1394,Town,Friendsanity: Abigail 10 <3,FRIENDSANITY, +1395,Town,Friendsanity: Abigail 11 <3,FRIENDSANITY, +1396,Town,Friendsanity: Abigail 12 <3,FRIENDSANITY, +1397,Town,Friendsanity: Abigail 13 <3,FRIENDSANITY, +1398,Town,Friendsanity: Abigail 14 <3,FRIENDSANITY, +1399,Town,Friendsanity: Emily 1 <3,FRIENDSANITY, +1400,Town,Friendsanity: Emily 2 <3,FRIENDSANITY, +1401,Town,Friendsanity: Emily 3 <3,FRIENDSANITY, +1402,Town,Friendsanity: Emily 4 <3,FRIENDSANITY, +1403,Town,Friendsanity: Emily 5 <3,FRIENDSANITY, +1404,Town,Friendsanity: Emily 6 <3,FRIENDSANITY, +1405,Town,Friendsanity: Emily 7 <3,FRIENDSANITY, +1406,Town,Friendsanity: Emily 8 <3,FRIENDSANITY, +1407,Town,Friendsanity: Emily 9 <3,FRIENDSANITY, +1408,Town,Friendsanity: Emily 10 <3,FRIENDSANITY, +1409,Town,Friendsanity: Emily 11 <3,FRIENDSANITY, +1410,Town,Friendsanity: Emily 12 <3,FRIENDSANITY, +1411,Town,Friendsanity: Emily 13 <3,FRIENDSANITY, +1412,Town,Friendsanity: Emily 14 <3,FRIENDSANITY, +1413,Town,Friendsanity: Haley 1 <3,FRIENDSANITY, +1414,Town,Friendsanity: Haley 2 <3,FRIENDSANITY, +1415,Town,Friendsanity: Haley 3 <3,FRIENDSANITY, +1416,Town,Friendsanity: Haley 4 <3,FRIENDSANITY, +1417,Town,Friendsanity: Haley 5 <3,FRIENDSANITY, +1418,Town,Friendsanity: Haley 6 <3,FRIENDSANITY, +1419,Town,Friendsanity: Haley 7 <3,FRIENDSANITY, +1420,Town,Friendsanity: Haley 8 <3,FRIENDSANITY, +1421,Town,Friendsanity: Haley 9 <3,FRIENDSANITY, +1422,Town,Friendsanity: Haley 10 <3,FRIENDSANITY, +1423,Town,Friendsanity: Haley 11 <3,FRIENDSANITY, +1424,Town,Friendsanity: Haley 12 <3,FRIENDSANITY, +1425,Town,Friendsanity: Haley 13 <3,FRIENDSANITY, +1426,Town,Friendsanity: Haley 14 <3,FRIENDSANITY, +1427,Forest,Friendsanity: Leah 1 <3,FRIENDSANITY, +1428,Forest,Friendsanity: Leah 2 <3,FRIENDSANITY, +1429,Forest,Friendsanity: Leah 3 <3,FRIENDSANITY, +1430,Forest,Friendsanity: Leah 4 <3,FRIENDSANITY, +1431,Forest,Friendsanity: Leah 5 <3,FRIENDSANITY, +1432,Forest,Friendsanity: Leah 6 <3,FRIENDSANITY, +1433,Forest,Friendsanity: Leah 7 <3,FRIENDSANITY, +1434,Forest,Friendsanity: Leah 8 <3,FRIENDSANITY, +1435,Forest,Friendsanity: Leah 9 <3,FRIENDSANITY, +1436,Forest,Friendsanity: Leah 10 <3,FRIENDSANITY, +1437,Forest,Friendsanity: Leah 11 <3,FRIENDSANITY, +1438,Forest,Friendsanity: Leah 12 <3,FRIENDSANITY, +1439,Forest,Friendsanity: Leah 13 <3,FRIENDSANITY, +1440,Forest,Friendsanity: Leah 14 <3,FRIENDSANITY, +1441,Carpenter Shop,Friendsanity: Maru 1 <3,FRIENDSANITY, +1442,Carpenter Shop,Friendsanity: Maru 2 <3,FRIENDSANITY, +1443,Carpenter Shop,Friendsanity: Maru 3 <3,FRIENDSANITY, +1444,Carpenter Shop,Friendsanity: Maru 4 <3,FRIENDSANITY, +1445,Carpenter Shop,Friendsanity: Maru 5 <3,FRIENDSANITY, +1446,Carpenter Shop,Friendsanity: Maru 6 <3,FRIENDSANITY, +1447,Carpenter Shop,Friendsanity: Maru 7 <3,FRIENDSANITY, +1448,Carpenter Shop,Friendsanity: Maru 8 <3,FRIENDSANITY, +1449,Carpenter Shop,Friendsanity: Maru 9 <3,FRIENDSANITY, +1450,Carpenter Shop,Friendsanity: Maru 10 <3,FRIENDSANITY, +1451,Carpenter Shop,Friendsanity: Maru 11 <3,FRIENDSANITY, +1452,Carpenter Shop,Friendsanity: Maru 12 <3,FRIENDSANITY, +1453,Carpenter Shop,Friendsanity: Maru 13 <3,FRIENDSANITY, +1454,Carpenter Shop,Friendsanity: Maru 14 <3,FRIENDSANITY, +1455,Town,Friendsanity: Penny 1 <3,FRIENDSANITY, +1456,Town,Friendsanity: Penny 2 <3,FRIENDSANITY, +1457,Town,Friendsanity: Penny 3 <3,FRIENDSANITY, +1458,Town,Friendsanity: Penny 4 <3,FRIENDSANITY, +1459,Town,Friendsanity: Penny 5 <3,FRIENDSANITY, +1460,Town,Friendsanity: Penny 6 <3,FRIENDSANITY, +1461,Town,Friendsanity: Penny 7 <3,FRIENDSANITY, +1462,Town,Friendsanity: Penny 8 <3,FRIENDSANITY, +1463,Town,Friendsanity: Penny 9 <3,FRIENDSANITY, +1464,Town,Friendsanity: Penny 10 <3,FRIENDSANITY, +1465,Town,Friendsanity: Penny 11 <3,FRIENDSANITY, +1466,Town,Friendsanity: Penny 12 <3,FRIENDSANITY, +1467,Town,Friendsanity: Penny 13 <3,FRIENDSANITY, +1468,Town,Friendsanity: Penny 14 <3,FRIENDSANITY, +1469,Town,Friendsanity: Caroline 1 <3,FRIENDSANITY, +1470,Town,Friendsanity: Caroline 2 <3,FRIENDSANITY, +1471,Town,Friendsanity: Caroline 3 <3,FRIENDSANITY, +1472,Town,Friendsanity: Caroline 4 <3,FRIENDSANITY, +1473,Town,Friendsanity: Caroline 5 <3,FRIENDSANITY, +1474,Town,Friendsanity: Caroline 6 <3,FRIENDSANITY, +1475,Town,Friendsanity: Caroline 7 <3,FRIENDSANITY, +1476,Town,Friendsanity: Caroline 8 <3,FRIENDSANITY, +1477,Town,Friendsanity: Caroline 9 <3,FRIENDSANITY, +1478,Town,Friendsanity: Caroline 10 <3,FRIENDSANITY, +1480,Town,Friendsanity: Clint 1 <3,FRIENDSANITY, +1481,Town,Friendsanity: Clint 2 <3,FRIENDSANITY, +1482,Town,Friendsanity: Clint 3 <3,FRIENDSANITY, +1483,Town,Friendsanity: Clint 4 <3,FRIENDSANITY, +1484,Town,Friendsanity: Clint 5 <3,FRIENDSANITY, +1485,Town,Friendsanity: Clint 6 <3,FRIENDSANITY, +1486,Town,Friendsanity: Clint 7 <3,FRIENDSANITY, +1487,Town,Friendsanity: Clint 8 <3,FRIENDSANITY, +1488,Town,Friendsanity: Clint 9 <3,FRIENDSANITY, +1489,Town,Friendsanity: Clint 10 <3,FRIENDSANITY, +1491,Carpenter Shop,Friendsanity: Demetrius 1 <3,FRIENDSANITY, +1492,Carpenter Shop,Friendsanity: Demetrius 2 <3,FRIENDSANITY, +1493,Carpenter Shop,Friendsanity: Demetrius 3 <3,FRIENDSANITY, +1494,Carpenter Shop,Friendsanity: Demetrius 4 <3,FRIENDSANITY, +1495,Carpenter Shop,Friendsanity: Demetrius 5 <3,FRIENDSANITY, +1496,Carpenter Shop,Friendsanity: Demetrius 6 <3,FRIENDSANITY, +1497,Carpenter Shop,Friendsanity: Demetrius 7 <3,FRIENDSANITY, +1498,Carpenter Shop,Friendsanity: Demetrius 8 <3,FRIENDSANITY, +1499,Carpenter Shop,Friendsanity: Demetrius 9 <3,FRIENDSANITY, +1500,Carpenter Shop,Friendsanity: Demetrius 10 <3,FRIENDSANITY, +1502,The Mines,Friendsanity: Dwarf 1 <3,FRIENDSANITY, +1503,The Mines,Friendsanity: Dwarf 2 <3,FRIENDSANITY, +1504,The Mines,Friendsanity: Dwarf 3 <3,FRIENDSANITY, +1505,The Mines,Friendsanity: Dwarf 4 <3,FRIENDSANITY, +1506,The Mines,Friendsanity: Dwarf 5 <3,FRIENDSANITY, +1507,The Mines,Friendsanity: Dwarf 6 <3,FRIENDSANITY, +1508,The Mines,Friendsanity: Dwarf 7 <3,FRIENDSANITY, +1509,The Mines,Friendsanity: Dwarf 8 <3,FRIENDSANITY, +1510,The Mines,Friendsanity: Dwarf 9 <3,FRIENDSANITY, +1511,The Mines,Friendsanity: Dwarf 10 <3,FRIENDSANITY, +1513,Town,Friendsanity: Evelyn 1 <3,FRIENDSANITY, +1514,Town,Friendsanity: Evelyn 2 <3,FRIENDSANITY, +1515,Town,Friendsanity: Evelyn 3 <3,FRIENDSANITY, +1516,Town,Friendsanity: Evelyn 4 <3,FRIENDSANITY, +1517,Town,Friendsanity: Evelyn 5 <3,FRIENDSANITY, +1518,Town,Friendsanity: Evelyn 6 <3,FRIENDSANITY, +1519,Town,Friendsanity: Evelyn 7 <3,FRIENDSANITY, +1520,Town,Friendsanity: Evelyn 8 <3,FRIENDSANITY, +1521,Town,Friendsanity: Evelyn 9 <3,FRIENDSANITY, +1522,Town,Friendsanity: Evelyn 10 <3,FRIENDSANITY, +1524,Town,Friendsanity: George 1 <3,FRIENDSANITY, +1525,Town,Friendsanity: George 2 <3,FRIENDSANITY, +1526,Town,Friendsanity: George 3 <3,FRIENDSANITY, +1527,Town,Friendsanity: George 4 <3,FRIENDSANITY, +1528,Town,Friendsanity: George 5 <3,FRIENDSANITY, +1529,Town,Friendsanity: George 6 <3,FRIENDSANITY, +1530,Town,Friendsanity: George 7 <3,FRIENDSANITY, +1531,Town,Friendsanity: George 8 <3,FRIENDSANITY, +1532,Town,Friendsanity: George 9 <3,FRIENDSANITY, +1533,Town,Friendsanity: George 10 <3,FRIENDSANITY, +1535,Town,Friendsanity: Gus 1 <3,FRIENDSANITY, +1536,Town,Friendsanity: Gus 2 <3,FRIENDSANITY, +1537,Town,Friendsanity: Gus 3 <3,FRIENDSANITY, +1538,Town,Friendsanity: Gus 4 <3,FRIENDSANITY, +1539,Town,Friendsanity: Gus 5 <3,FRIENDSANITY, +1540,Town,Friendsanity: Gus 6 <3,FRIENDSANITY, +1541,Town,Friendsanity: Gus 7 <3,FRIENDSANITY, +1542,Town,Friendsanity: Gus 8 <3,FRIENDSANITY, +1543,Town,Friendsanity: Gus 9 <3,FRIENDSANITY, +1544,Town,Friendsanity: Gus 10 <3,FRIENDSANITY, +1546,Marnie's Ranch,Friendsanity: Jas 1 <3,FRIENDSANITY, +1547,Marnie's Ranch,Friendsanity: Jas 2 <3,FRIENDSANITY, +1548,Marnie's Ranch,Friendsanity: Jas 3 <3,FRIENDSANITY, +1549,Marnie's Ranch,Friendsanity: Jas 4 <3,FRIENDSANITY, +1550,Marnie's Ranch,Friendsanity: Jas 5 <3,FRIENDSANITY, +1551,Marnie's Ranch,Friendsanity: Jas 6 <3,FRIENDSANITY, +1552,Marnie's Ranch,Friendsanity: Jas 7 <3,FRIENDSANITY, +1553,Marnie's Ranch,Friendsanity: Jas 8 <3,FRIENDSANITY, +1554,Marnie's Ranch,Friendsanity: Jas 9 <3,FRIENDSANITY, +1555,Marnie's Ranch,Friendsanity: Jas 10 <3,FRIENDSANITY, +1557,Town,Friendsanity: Jodi 1 <3,FRIENDSANITY, +1558,Town,Friendsanity: Jodi 2 <3,FRIENDSANITY, +1559,Town,Friendsanity: Jodi 3 <3,FRIENDSANITY, +1560,Town,Friendsanity: Jodi 4 <3,FRIENDSANITY, +1561,Town,Friendsanity: Jodi 5 <3,FRIENDSANITY, +1562,Town,Friendsanity: Jodi 6 <3,FRIENDSANITY, +1563,Town,Friendsanity: Jodi 7 <3,FRIENDSANITY, +1564,Town,Friendsanity: Jodi 8 <3,FRIENDSANITY, +1565,Town,Friendsanity: Jodi 9 <3,FRIENDSANITY, +1566,Town,Friendsanity: Jodi 10 <3,FRIENDSANITY, +1568,Town,Friendsanity: Kent 1 <3,FRIENDSANITY, +1569,Town,Friendsanity: Kent 2 <3,FRIENDSANITY, +1570,Town,Friendsanity: Kent 3 <3,FRIENDSANITY, +1571,Town,Friendsanity: Kent 4 <3,FRIENDSANITY, +1572,Town,Friendsanity: Kent 5 <3,FRIENDSANITY, +1573,Town,Friendsanity: Kent 6 <3,FRIENDSANITY, +1574,Town,Friendsanity: Kent 7 <3,FRIENDSANITY, +1575,Town,Friendsanity: Kent 8 <3,FRIENDSANITY, +1576,Town,Friendsanity: Kent 9 <3,FRIENDSANITY, +1577,Town,Friendsanity: Kent 10 <3,FRIENDSANITY, +1579,Sewer,Friendsanity: Krobus 1 <3,FRIENDSANITY, +1580,Sewer,Friendsanity: Krobus 2 <3,FRIENDSANITY, +1581,Sewer,Friendsanity: Krobus 3 <3,FRIENDSANITY, +1582,Sewer,Friendsanity: Krobus 4 <3,FRIENDSANITY, +1583,Sewer,Friendsanity: Krobus 5 <3,FRIENDSANITY, +1584,Sewer,Friendsanity: Krobus 6 <3,FRIENDSANITY, +1585,Sewer,Friendsanity: Krobus 7 <3,FRIENDSANITY, +1586,Sewer,Friendsanity: Krobus 8 <3,FRIENDSANITY, +1587,Sewer,Friendsanity: Krobus 9 <3,FRIENDSANITY, +1588,Sewer,Friendsanity: Krobus 10 <3,FRIENDSANITY, +1590,Leo's Hut,Friendsanity: Leo 1 <3,"FRIENDSANITY,GINGER_ISLAND", +1591,Leo's Hut,Friendsanity: Leo 2 <3,"FRIENDSANITY,GINGER_ISLAND", +1592,Leo's Hut,Friendsanity: Leo 3 <3,"FRIENDSANITY,GINGER_ISLAND", +1593,Leo's Hut,Friendsanity: Leo 4 <3,"FRIENDSANITY,GINGER_ISLAND", +1594,Leo's Hut,Friendsanity: Leo 5 <3,"FRIENDSANITY,GINGER_ISLAND", +1595,Leo's Hut,Friendsanity: Leo 6 <3,"FRIENDSANITY,GINGER_ISLAND", +1596,Leo's Hut,Friendsanity: Leo 7 <3,"FRIENDSANITY,GINGER_ISLAND", +1597,Leo's Hut,Friendsanity: Leo 8 <3,"FRIENDSANITY,GINGER_ISLAND", +1598,Leo's Hut,Friendsanity: Leo 9 <3,"FRIENDSANITY,GINGER_ISLAND", +1599,Leo's Hut,Friendsanity: Leo 10 <3,"FRIENDSANITY,GINGER_ISLAND", +1601,Town,Friendsanity: Lewis 1 <3,FRIENDSANITY, +1602,Town,Friendsanity: Lewis 2 <3,FRIENDSANITY, +1603,Town,Friendsanity: Lewis 3 <3,FRIENDSANITY, +1604,Town,Friendsanity: Lewis 4 <3,FRIENDSANITY, +1605,Town,Friendsanity: Lewis 5 <3,FRIENDSANITY, +1606,Town,Friendsanity: Lewis 6 <3,FRIENDSANITY, +1607,Town,Friendsanity: Lewis 7 <3,FRIENDSANITY, +1608,Town,Friendsanity: Lewis 8 <3,FRIENDSANITY, +1609,Town,Friendsanity: Lewis 9 <3,FRIENDSANITY, +1610,Town,Friendsanity: Lewis 10 <3,FRIENDSANITY, +1612,Mountain,Friendsanity: Linus 1 <3,FRIENDSANITY, +1613,Mountain,Friendsanity: Linus 2 <3,FRIENDSANITY, +1614,Mountain,Friendsanity: Linus 3 <3,FRIENDSANITY, +1615,Mountain,Friendsanity: Linus 4 <3,FRIENDSANITY, +1616,Mountain,Friendsanity: Linus 5 <3,FRIENDSANITY, +1617,Mountain,Friendsanity: Linus 6 <3,FRIENDSANITY, +1618,Mountain,Friendsanity: Linus 7 <3,FRIENDSANITY, +1619,Mountain,Friendsanity: Linus 8 <3,FRIENDSANITY, +1620,Mountain,Friendsanity: Linus 9 <3,FRIENDSANITY, +1621,Mountain,Friendsanity: Linus 10 <3,FRIENDSANITY, +1623,Marnie's Ranch,Friendsanity: Marnie 1 <3,FRIENDSANITY, +1624,Marnie's Ranch,Friendsanity: Marnie 2 <3,FRIENDSANITY, +1625,Marnie's Ranch,Friendsanity: Marnie 3 <3,FRIENDSANITY, +1626,Marnie's Ranch,Friendsanity: Marnie 4 <3,FRIENDSANITY, +1627,Marnie's Ranch,Friendsanity: Marnie 5 <3,FRIENDSANITY, +1628,Marnie's Ranch,Friendsanity: Marnie 6 <3,FRIENDSANITY, +1629,Marnie's Ranch,Friendsanity: Marnie 7 <3,FRIENDSANITY, +1630,Marnie's Ranch,Friendsanity: Marnie 8 <3,FRIENDSANITY, +1631,Marnie's Ranch,Friendsanity: Marnie 9 <3,FRIENDSANITY, +1632,Marnie's Ranch,Friendsanity: Marnie 10 <3,FRIENDSANITY, +1634,Town,Friendsanity: Pam 1 <3,FRIENDSANITY, +1635,Town,Friendsanity: Pam 2 <3,FRIENDSANITY, +1636,Town,Friendsanity: Pam 3 <3,FRIENDSANITY, +1637,Town,Friendsanity: Pam 4 <3,FRIENDSANITY, +1638,Town,Friendsanity: Pam 5 <3,FRIENDSANITY, +1639,Town,Friendsanity: Pam 6 <3,FRIENDSANITY, +1640,Town,Friendsanity: Pam 7 <3,FRIENDSANITY, +1641,Town,Friendsanity: Pam 8 <3,FRIENDSANITY, +1642,Town,Friendsanity: Pam 9 <3,FRIENDSANITY, +1643,Town,Friendsanity: Pam 10 <3,FRIENDSANITY, +1645,Town,Friendsanity: Pierre 1 <3,FRIENDSANITY, +1646,Town,Friendsanity: Pierre 2 <3,FRIENDSANITY, +1647,Town,Friendsanity: Pierre 3 <3,FRIENDSANITY, +1648,Town,Friendsanity: Pierre 4 <3,FRIENDSANITY, +1649,Town,Friendsanity: Pierre 5 <3,FRIENDSANITY, +1650,Town,Friendsanity: Pierre 6 <3,FRIENDSANITY, +1651,Town,Friendsanity: Pierre 7 <3,FRIENDSANITY, +1652,Town,Friendsanity: Pierre 8 <3,FRIENDSANITY, +1653,Town,Friendsanity: Pierre 9 <3,FRIENDSANITY, +1654,Town,Friendsanity: Pierre 10 <3,FRIENDSANITY, +1656,Carpenter Shop,Friendsanity: Robin 1 <3,FRIENDSANITY, +1657,Carpenter Shop,Friendsanity: Robin 2 <3,FRIENDSANITY, +1658,Carpenter Shop,Friendsanity: Robin 3 <3,FRIENDSANITY, +1659,Carpenter Shop,Friendsanity: Robin 4 <3,FRIENDSANITY, +1660,Carpenter Shop,Friendsanity: Robin 5 <3,FRIENDSANITY, +1661,Carpenter Shop,Friendsanity: Robin 6 <3,FRIENDSANITY, +1662,Carpenter Shop,Friendsanity: Robin 7 <3,FRIENDSANITY, +1663,Carpenter Shop,Friendsanity: Robin 8 <3,FRIENDSANITY, +1664,Carpenter Shop,Friendsanity: Robin 9 <3,FRIENDSANITY, +1665,Carpenter Shop,Friendsanity: Robin 10 <3,FRIENDSANITY, +1667,Oasis,Friendsanity: Sandy 1 <3,FRIENDSANITY, +1668,Oasis,Friendsanity: Sandy 2 <3,FRIENDSANITY, +1669,Oasis,Friendsanity: Sandy 3 <3,FRIENDSANITY, +1670,Oasis,Friendsanity: Sandy 4 <3,FRIENDSANITY, +1671,Oasis,Friendsanity: Sandy 5 <3,FRIENDSANITY, +1672,Oasis,Friendsanity: Sandy 6 <3,FRIENDSANITY, +1673,Oasis,Friendsanity: Sandy 7 <3,FRIENDSANITY, +1674,Oasis,Friendsanity: Sandy 8 <3,FRIENDSANITY, +1675,Oasis,Friendsanity: Sandy 9 <3,FRIENDSANITY, +1676,Oasis,Friendsanity: Sandy 10 <3,FRIENDSANITY, +1678,Town,Friendsanity: Vincent 1 <3,FRIENDSANITY, +1679,Town,Friendsanity: Vincent 2 <3,FRIENDSANITY, +1680,Town,Friendsanity: Vincent 3 <3,FRIENDSANITY, +1681,Town,Friendsanity: Vincent 4 <3,FRIENDSANITY, +1682,Town,Friendsanity: Vincent 5 <3,FRIENDSANITY, +1683,Town,Friendsanity: Vincent 6 <3,FRIENDSANITY, +1684,Town,Friendsanity: Vincent 7 <3,FRIENDSANITY, +1685,Town,Friendsanity: Vincent 8 <3,FRIENDSANITY, +1686,Town,Friendsanity: Vincent 9 <3,FRIENDSANITY, +1687,Town,Friendsanity: Vincent 10 <3,FRIENDSANITY, +1689,Beach,Friendsanity: Willy 1 <3,FRIENDSANITY, +1690,Beach,Friendsanity: Willy 2 <3,FRIENDSANITY, +1691,Beach,Friendsanity: Willy 3 <3,FRIENDSANITY, +1692,Beach,Friendsanity: Willy 4 <3,FRIENDSANITY, +1693,Beach,Friendsanity: Willy 5 <3,FRIENDSANITY, +1694,Beach,Friendsanity: Willy 6 <3,FRIENDSANITY, +1695,Beach,Friendsanity: Willy 7 <3,FRIENDSANITY, +1696,Beach,Friendsanity: Willy 8 <3,FRIENDSANITY, +1697,Beach,Friendsanity: Willy 9 <3,FRIENDSANITY, +1698,Beach,Friendsanity: Willy 10 <3,FRIENDSANITY, +1700,Forest,Friendsanity: Wizard 1 <3,FRIENDSANITY, +1701,Forest,Friendsanity: Wizard 2 <3,FRIENDSANITY, +1702,Forest,Friendsanity: Wizard 3 <3,FRIENDSANITY, +1703,Forest,Friendsanity: Wizard 4 <3,FRIENDSANITY, +1704,Forest,Friendsanity: Wizard 5 <3,FRIENDSANITY, +1705,Forest,Friendsanity: Wizard 6 <3,FRIENDSANITY, +1706,Forest,Friendsanity: Wizard 7 <3,FRIENDSANITY, +1707,Forest,Friendsanity: Wizard 8 <3,FRIENDSANITY, +1708,Forest,Friendsanity: Wizard 9 <3,FRIENDSANITY, +1709,Forest,Friendsanity: Wizard 10 <3,FRIENDSANITY, +1710,Farm,Friendsanity: Pet 1 <3,FRIENDSANITY, +1711,Farm,Friendsanity: Pet 2 <3,FRIENDSANITY, +1712,Farm,Friendsanity: Pet 3 <3,FRIENDSANITY, +1713,Farm,Friendsanity: Pet 4 <3,FRIENDSANITY, +1714,Farm,Friendsanity: Pet 5 <3,FRIENDSANITY, +1715,Farm,Friendsanity: Friend 1 <3,FRIENDSANITY, +1716,Farm,Friendsanity: Friend 2 <3,FRIENDSANITY, +1717,Farm,Friendsanity: Friend 3 <3,FRIENDSANITY, +1718,Farm,Friendsanity: Friend 4 <3,FRIENDSANITY, +1719,Farm,Friendsanity: Friend 5 <3,FRIENDSANITY, +1720,Farm,Friendsanity: Friend 6 <3,FRIENDSANITY, +1721,Farm,Friendsanity: Friend 7 <3,FRIENDSANITY, +1722,Farm,Friendsanity: Friend 8 <3,FRIENDSANITY, +1723,Farm,Friendsanity: Suitor 9 <3,FRIENDSANITY, +1724,Farm,Friendsanity: Suitor 10 <3,FRIENDSANITY, +1725,Farm,Friendsanity: Spouse 11 <3,FRIENDSANITY, +1726,Farm,Friendsanity: Spouse 12 <3,FRIENDSANITY, +1727,Farm,Friendsanity: Spouse 13 <3,FRIENDSANITY, +1728,Farm,Friendsanity: Spouse 14 <3,FRIENDSANITY, +2001,Town,Egg Hunt Victory,FESTIVAL, +2002,Town,Egg Festival: Strawberry Seeds,FESTIVAL, +2003,Forest,Dance with someone,FESTIVAL, +2004,Forest,Rarecrow #5 (Woman),FESTIVAL, +2005,Beach,Luau Soup,FESTIVAL, +2006,Beach,Dance of the Moonlight Jellies,FESTIVAL, +2007,Town,Smashing Stone,FESTIVAL, +2008,Town,Grange Display,FESTIVAL, +2009,Town,Rarecrow #1 (Turnip Head),FESTIVAL, +2010,Town,Fair Stardrop,FESTIVAL, +2011,Town,Spirit's Eve Maze,FESTIVAL, +2012,Town,Rarecrow #2 (Witch),FESTIVAL, +2013,Forest,Win Fishing Competition,FESTIVAL, +2014,Forest,Rarecrow #4 (Snowman),FESTIVAL, +2015,Beach,Mermaid Pearl,FESTIVAL, +2016,Beach,Cone Hat,FESTIVAL_HARD, +2017,Beach,Iridium Fireplace,FESTIVAL_HARD, +2018,Beach,Rarecrow #7 (Tanuki),FESTIVAL, +2019,Beach,Rarecrow #8 (Tribal Mask),FESTIVAL, +2020,Beach,Lupini: Red Eagle,FESTIVAL, +2021,Beach,Lupini: Portrait Of A Mermaid,FESTIVAL, +2022,Beach,Lupini: Solar Kingdom,FESTIVAL, +2023,Beach,Lupini: Clouds,FESTIVAL_HARD, +2024,Beach,Lupini: 1000 Years From Now,FESTIVAL_HARD, +2025,Beach,Lupini: Three Trees,FESTIVAL_HARD, +2026,Beach,Lupini: The Serpent,FESTIVAL_HARD, +2027,Beach,Lupini: 'Tropical Fish #173',FESTIVAL_HARD, +2028,Beach,Lupini: Land Of Clay,FESTIVAL_HARD, +2029,Town,Secret Santa,FESTIVAL, +2030,Town,The Legend of the Winter Star,FESTIVAL, +2031,Farm,Collect All Rarecrows,FESTIVAL, +2101,Town,Island Ingredients,"GINGER_ISLAND,SPECIAL_ORDER_BOARD", +2102,Town,Cave Patrol,SPECIAL_ORDER_BOARD, +2103,Town,Aquatic Overpopulation,SPECIAL_ORDER_BOARD, +2104,Town,Biome Balance,SPECIAL_ORDER_BOARD, +2105,Town,Rock Rejuvenation,SPECIAL_ORDER_BOARD, +2106,Town,Gifts for George,SPECIAL_ORDER_BOARD, +2107,Town,Fragments of the past,"GINGER_ISLAND,SPECIAL_ORDER_BOARD", +2108,Town,Gus' Famous Omelet,SPECIAL_ORDER_BOARD, +2109,Town,Crop Order,SPECIAL_ORDER_BOARD, +2110,Town,Community Cleanup,SPECIAL_ORDER_BOARD, +2111,Town,The Strong Stuff,SPECIAL_ORDER_BOARD, +2112,Town,Pierre's Prime Produce,SPECIAL_ORDER_BOARD, +2113,Town,Robin's Project,SPECIAL_ORDER_BOARD, +2114,Town,Robin's Resource Rush,SPECIAL_ORDER_BOARD, +2115,Town,Juicy Bugs Wanted!,SPECIAL_ORDER_BOARD, +2116,Town,Tropical Fish,"GINGER_ISLAND,SPECIAL_ORDER_BOARD", +2117,Town,A Curious Substance,SPECIAL_ORDER_BOARD, +2118,Town,Prismatic Jelly,SPECIAL_ORDER_BOARD, +2151,Qi's Walnut Room,Qi's Crop,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2152,Qi's Walnut Room,Let's Play A Game,"GINGER_ISLAND,SPECIAL_ORDER_QI,JUNIMO_KART", +2153,Qi's Walnut Room,Four Precious Stones,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2154,Qi's Walnut Room,Qi's Hungry Challenge,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2155,Qi's Walnut Room,Qi's Cuisine,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2156,Qi's Walnut Room,Qi's Kindness,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2157,Qi's Walnut Room,Extended Family,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2158,Qi's Walnut Room,Danger In The Deep,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2159,Qi's Walnut Room,Skull Cavern Invasion,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2160,Qi's Walnut Room,Qi's Prismatic Grange,"GINGER_ISLAND,SPECIAL_ORDER_QI", +2201,Boat Tunnel,Repair Ticket Machine,GINGER_ISLAND, +2202,Boat Tunnel,Repair Boat Hull,GINGER_ISLAND, +2203,Boat Tunnel,Repair Boat Anchor,GINGER_ISLAND, +2204,Leo's Hut,Leo's Parrot,"GINGER_ISLAND,WALNUT_PURCHASE", +2205,Island South,Island West Turtle,"GINGER_ISLAND,WALNUT_PURCHASE", +2206,Island West,Island Farmhouse,"GINGER_ISLAND,WALNUT_PURCHASE", +2207,Island Farmhouse,Island Mailbox,"GINGER_ISLAND,WALNUT_PURCHASE", +2208,Island Farmhouse,Farm Obelisk,"GINGER_ISLAND,WALNUT_PURCHASE", +2209,Island North,Dig Site Bridge,"GINGER_ISLAND,WALNUT_PURCHASE", +2210,Island North,Island Trader,"GINGER_ISLAND,WALNUT_PURCHASE", +2211,Volcano Entrance,Volcano Bridge,"GINGER_ISLAND,WALNUT_PURCHASE", +2212,Volcano - Floor 5,Volcano Exit Shortcut,"GINGER_ISLAND,WALNUT_PURCHASE", +2213,Island South,Island Resort,"GINGER_ISLAND,WALNUT_PURCHASE", +2214,Island West,Parrot Express,"GINGER_ISLAND,WALNUT_PURCHASE", +2215,Dig Site,Open Professor Snail Cave,"GINGER_ISLAND", +2216,Field Office,Complete Island Field Office,"GINGER_ISLAND", +2301,Farm,Harvest Amaranth,"CROPSANITY", +2302,Farm,Harvest Artichoke,"CROPSANITY", +2303,Farm,Harvest Beet,"CROPSANITY", +2304,Farm,Harvest Blue Jazz,"CROPSANITY", +2305,Farm,Harvest Blueberry,"CROPSANITY", +2306,Farm,Harvest Bok Choy,"CROPSANITY", +2307,Farm,Harvest Cauliflower,"CROPSANITY", +2308,Farm,Harvest Corn,"CROPSANITY", +2309,Farm,Harvest Cranberries,"CROPSANITY", +2310,Farm,Harvest Eggplant,"CROPSANITY", +2311,Farm,Harvest Fairy Rose,"CROPSANITY", +2312,Farm,Harvest Garlic,"CROPSANITY", +2313,Farm,Harvest Grape,"CROPSANITY", +2314,Farm,Harvest Green Bean,"CROPSANITY", +2315,Farm,Harvest Hops,"CROPSANITY", +2316,Farm,Harvest Hot Pepper,"CROPSANITY", +2317,Farm,Harvest Kale,"CROPSANITY", +2318,Farm,Harvest Melon,"CROPSANITY", +2319,Farm,Harvest Parsnip,"CROPSANITY", +2320,Farm,Harvest Poppy,"CROPSANITY", +2321,Farm,Harvest Potato,"CROPSANITY", +2322,Farm,Harvest Pumpkin,"CROPSANITY", +2323,Farm,Harvest Radish,"CROPSANITY", +2324,Farm,Harvest Red Cabbage,"CROPSANITY", +2325,Farm,Harvest Rhubarb,"CROPSANITY", +2326,Farm,Harvest Starfruit,"CROPSANITY", +2327,Farm,Harvest Strawberry,"CROPSANITY", +2328,Farm,Harvest Summer Spangle,"CROPSANITY", +2329,Farm,Harvest Sunflower,"CROPSANITY", +2330,Farm,Harvest Tomato,"CROPSANITY", +2331,Farm,Harvest Tulip,"CROPSANITY", +2332,Farm,Harvest Unmilled Rice,"CROPSANITY", +2333,Farm,Harvest Wheat,"CROPSANITY", +2334,Farm,Harvest Yam,"CROPSANITY", +2335,Farm,Harvest Cactus Fruit,"CROPSANITY", +2336,Farm,Harvest Pineapple,"CROPSANITY,GINGER_ISLAND", +2337,Farm,Harvest Taro Root,"CROPSANITY,GINGER_ISLAND", +2338,Farm,Harvest Sweet Gem Berry,"CROPSANITY", +2339,Farm,Harvest Apple,"CROPSANITY", +2340,Farm,Harvest Apricot,"CROPSANITY", +2341,Farm,Harvest Cherry,"CROPSANITY", +2342,Farm,Harvest Orange,"CROPSANITY", +2343,Farm,Harvest Pomegranate,"CROPSANITY", +2344,Farm,Harvest Peach,"CROPSANITY", +2345,Farm,Harvest Banana,"CROPSANITY,GINGER_ISLAND", +2346,Farm,Harvest Mango,"CROPSANITY,GINGER_ISLAND", +5001,Stardew Valley,Level 1 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5002,Stardew Valley,Level 2 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5003,Stardew Valley,Level 3 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5004,Stardew Valley,Level 4 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5005,Stardew Valley,Level 5 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5006,Stardew Valley,Level 6 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5007,Stardew Valley,Level 7 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5008,Stardew Valley,Level 8 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5009,Stardew Valley,Level 9 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5010,Stardew Valley,Level 10 Luck,"LUCK_LEVEL,SKILL_LEVEL",Luck Skill +5011,Stardew Valley,Level 1 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5012,Stardew Valley,Level 2 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5013,Stardew Valley,Level 3 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5014,Stardew Valley,Level 4 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5015,Stardew Valley,Level 5 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5016,Stardew Valley,Level 6 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5017,Stardew Valley,Level 7 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5018,Stardew Valley,Level 8 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5019,Stardew Valley,Level 9 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5020,Stardew Valley,Level 10 Socializing,"SOCIALIZING_LEVEL,SKILL_LEVEL",Socializing Skill +5021,Stardew Valley,Level 1 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5022,Stardew Valley,Level 2 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5023,Stardew Valley,Level 3 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5024,Stardew Valley,Level 4 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5025,Stardew Valley,Level 5 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5026,Stardew Valley,Level 6 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5027,Stardew Valley,Level 7 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5028,Stardew Valley,Level 8 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5029,Stardew Valley,Level 9 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5030,Stardew Valley,Level 10 Magic,"MAGIC_LEVEL,SKILL_LEVEL",Magic +5031,Stardew Valley,Level 1 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5032,Stardew Valley,Level 2 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5033,Stardew Valley,Level 3 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5034,Stardew Valley,Level 4 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5035,Stardew Valley,Level 5 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5036,Stardew Valley,Level 6 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5037,Stardew Valley,Level 7 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5038,Stardew Valley,Level 8 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5039,Stardew Valley,Level 9 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5040,Stardew Valley,Level 10 Binning,"BINNING_LEVEL,SKILL_LEVEL",Binning Skill +5041,Stardew Valley,Level 1 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5042,Stardew Valley,Level 2 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5043,Stardew Valley,Level 3 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5044,Stardew Valley,Level 4 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5045,Stardew Valley,Level 5 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5046,Stardew Valley,Level 6 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5047,Stardew Valley,Level 7 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5048,Stardew Valley,Level 8 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5049,Stardew Valley,Level 9 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5050,Stardew Valley,Level 10 Archaeology,"ARCHAEOLOGY_LEVEL,SKILL_LEVEL",Archaeology +5051,Stardew Valley,Level 1 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5052,Stardew Valley,Level 2 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5053,Stardew Valley,Level 3 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5054,Stardew Valley,Level 4 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5055,Stardew Valley,Level 5 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5056,Stardew Valley,Level 6 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5057,Stardew Valley,Level 7 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5058,Stardew Valley,Level 8 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5059,Stardew Valley,Level 9 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5060,Stardew Valley,Level 10 Cooking,"COOKING_LEVEL,SKILL_LEVEL",Cooking Skill +5501,Stardew Valley,Analyze: Clear Debris,MANDATORY,Magic +5502,Stardew Valley,Analyze: Till,MANDATORY,Magic +5503,Stardew Valley,Analyze: Water,MANDATORY,Magic +5504,Stardew Valley,Analyze All Toil School Locations,MANDATORY,Magic +5505,Stardew Valley,Analyze: Evac,MANDATORY,Magic +5506,Stardew Valley,Analyze: Haste,MANDATORY,Magic +5507,Stardew Valley,Analyze: Heal,MANDATORY,Magic +5508,Stardew Valley,Analyze All Life School Locations,MANDATORY,Magic +5509,Stardew Valley,Analyze: Descend,MANDATORY,Magic +5510,Stardew Valley,Analyze: Fireball,MANDATORY,Magic +5511,Stardew Valley,Analyze: Frostbite,MANDATORY,Magic +5512,Stardew Valley,Analyze All Elemental School Locations,MANDATORY,Magic +5513,Stardew Valley,Analyze: Lantern,MANDATORY,Magic +5514,Stardew Valley,Analyze: Tendrils,MANDATORY,Magic +5515,Stardew Valley,Analyze: Shockwave,MANDATORY,Magic +5516,Stardew Valley,Analyze All Nature School Locations,MANDATORY,Magic +5517,Stardew Valley,Analyze: Meteor,MANDATORY,Magic +5518,Stardew Valley,Analyze: Lucksteal,MANDATORY,Magic +5519,Stardew Valley,Analyze: Bloodmana,MANDATORY,Magic +5520,Stardew Valley,Analyze All Eldritch School Locations,MANDATORY,Magic +5521,Stardew Valley,Analyze Every Magic School Location,MANDATORY,Magic +6001,Town,Friendsanity: Jasper 1 <3,FRIENDSANITY,Professor Jasper Thomas +6002,Town,Friendsanity: Jasper 2 <3,FRIENDSANITY,Professor Jasper Thomas +6003,Town,Friendsanity: Jasper 3 <3,FRIENDSANITY,Professor Jasper Thomas +6004,Town,Friendsanity: Jasper 4 <3,FRIENDSANITY,Professor Jasper Thomas +6005,Town,Friendsanity: Jasper 5 <3,FRIENDSANITY,Professor Jasper Thomas +6006,Town,Friendsanity: Jasper 6 <3,FRIENDSANITY,Professor Jasper Thomas +6007,Town,Friendsanity: Jasper 7 <3,FRIENDSANITY,Professor Jasper Thomas +6008,Town,Friendsanity: Jasper 8 <3,FRIENDSANITY,Professor Jasper Thomas +6009,Town,Friendsanity: Jasper 9 <3,FRIENDSANITY,Professor Jasper Thomas +6010,Town,Friendsanity: Jasper 10 <3,FRIENDSANITY,Professor Jasper Thomas +6011,Town,Friendsanity: Jasper 11 <3,FRIENDSANITY,Professor Jasper Thomas +6012,Town,Friendsanity: Jasper 12 <3,FRIENDSANITY,Professor Jasper Thomas +6013,Town,Friendsanity: Jasper 13 <3,FRIENDSANITY,Professor Jasper Thomas +6014,Town,Friendsanity: Jasper 14 <3,FRIENDSANITY,Professor Jasper Thomas +6015,Secret Woods,Friendsanity: Yoba 1 <3,FRIENDSANITY,Custom NPC - Yoba +6016,Secret Woods,Friendsanity: Yoba 2 <3,FRIENDSANITY,Custom NPC - Yoba +6017,Secret Woods,Friendsanity: Yoba 3 <3,FRIENDSANITY,Custom NPC - Yoba +6018,Secret Woods,Friendsanity: Yoba 4 <3,FRIENDSANITY,Custom NPC - Yoba +6019,Secret Woods,Friendsanity: Yoba 5 <3,FRIENDSANITY,Custom NPC - Yoba +6020,Secret Woods,Friendsanity: Yoba 6 <3,FRIENDSANITY,Custom NPC - Yoba +6021,Secret Woods,Friendsanity: Yoba 7 <3,FRIENDSANITY,Custom NPC - Yoba +6022,Secret Woods,Friendsanity: Yoba 8 <3,FRIENDSANITY,Custom NPC - Yoba +6023,Secret Woods,Friendsanity: Yoba 9 <3,FRIENDSANITY,Custom NPC - Yoba +6024,Secret Woods,Friendsanity: Yoba 10 <3,FRIENDSANITY,Custom NPC - Yoba +6025,Forest,Friendsanity: Mr. Ginger 1 <3,FRIENDSANITY,Mister Ginger (cat npc) +6026,Forest,Friendsanity: Mr. Ginger 2 <3,FRIENDSANITY,Mister Ginger (cat npc) +6027,Forest,Friendsanity: Mr. Ginger 3 <3,FRIENDSANITY,Mister Ginger (cat npc) +6028,Forest,Friendsanity: Mr. Ginger 4 <3,FRIENDSANITY,Mister Ginger (cat npc) +6029,Forest,Friendsanity: Mr. Ginger 5 <3,FRIENDSANITY,Mister Ginger (cat npc) +6030,Forest,Friendsanity: Mr. Ginger 6 <3,FRIENDSANITY,Mister Ginger (cat npc) +6031,Forest,Friendsanity: Mr. Ginger 7 <3,FRIENDSANITY,Mister Ginger (cat npc) +6032,Forest,Friendsanity: Mr. Ginger 8 <3,FRIENDSANITY,Mister Ginger (cat npc) +6033,Forest,Friendsanity: Mr. Ginger 9 <3,FRIENDSANITY,Mister Ginger (cat npc) +6034,Forest,Friendsanity: Mr. Ginger 10 <3,FRIENDSANITY,Mister Ginger (cat npc) +6035,Town,Friendsanity: Ayeisha 1 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6036,Town,Friendsanity: Ayeisha 2 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6037,Town,Friendsanity: Ayeisha 3 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6038,Town,Friendsanity: Ayeisha 4 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6039,Town,Friendsanity: Ayeisha 5 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6040,Town,Friendsanity: Ayeisha 6 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6041,Town,Friendsanity: Ayeisha 7 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6042,Town,Friendsanity: Ayeisha 8 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6043,Town,Friendsanity: Ayeisha 9 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6044,Town,Friendsanity: Ayeisha 10 <3,FRIENDSANITY,Ayeisha - The Postal Worker (Custom NPC) +6045,Town,Friendsanity: Shiko 1 <3,FRIENDSANITY,Shiko - New Custom NPC +6046,Town,Friendsanity: Shiko 2 <3,FRIENDSANITY,Shiko - New Custom NPC +6047,Town,Friendsanity: Shiko 3 <3,FRIENDSANITY,Shiko - New Custom NPC +6048,Town,Friendsanity: Shiko 4 <3,FRIENDSANITY,Shiko - New Custom NPC +6049,Town,Friendsanity: Shiko 5 <3,FRIENDSANITY,Shiko - New Custom NPC +6050,Town,Friendsanity: Shiko 6 <3,FRIENDSANITY,Shiko - New Custom NPC +6051,Town,Friendsanity: Shiko 7 <3,FRIENDSANITY,Shiko - New Custom NPC +6052,Town,Friendsanity: Shiko 8 <3,FRIENDSANITY,Shiko - New Custom NPC +6053,Town,Friendsanity: Shiko 9 <3,FRIENDSANITY,Shiko - New Custom NPC +6054,Town,Friendsanity: Shiko 10 <3,FRIENDSANITY,Shiko - New Custom NPC +6055,Town,Friendsanity: Shiko 11 <3,FRIENDSANITY,Shiko - New Custom NPC +6056,Town,Friendsanity: Shiko 12 <3,FRIENDSANITY,Shiko - New Custom NPC +6057,Town,Friendsanity: Shiko 13 <3,FRIENDSANITY,Shiko - New Custom NPC +6058,Town,Friendsanity: Shiko 14 <3,FRIENDSANITY,Shiko - New Custom NPC +6059,Forest,Friendsanity: Wellwick 1 <3,FRIENDSANITY,'Prophet' Wellwick +6060,Forest,Friendsanity: Wellwick 2 <3,FRIENDSANITY,'Prophet' Wellwick +6061,Forest,Friendsanity: Wellwick 3 <3,FRIENDSANITY,'Prophet' Wellwick +6062,Forest,Friendsanity: Wellwick 4 <3,FRIENDSANITY,'Prophet' Wellwick +6063,Forest,Friendsanity: Wellwick 5 <3,FRIENDSANITY,'Prophet' Wellwick +6064,Forest,Friendsanity: Wellwick 6 <3,FRIENDSANITY,'Prophet' Wellwick +6065,Forest,Friendsanity: Wellwick 7 <3,FRIENDSANITY,'Prophet' Wellwick +6066,Forest,Friendsanity: Wellwick 8 <3,FRIENDSANITY,'Prophet' Wellwick +6067,Forest,Friendsanity: Wellwick 9 <3,FRIENDSANITY,'Prophet' Wellwick +6068,Forest,Friendsanity: Wellwick 10 <3,FRIENDSANITY,'Prophet' Wellwick +6069,Forest,Friendsanity: Wellwick 11 <3,FRIENDSANITY,'Prophet' Wellwick +6070,Forest,Friendsanity: Wellwick 12 <3,FRIENDSANITY,'Prophet' Wellwick +6071,Forest,Friendsanity: Wellwick 13 <3,FRIENDSANITY,'Prophet' Wellwick +6072,Forest,Friendsanity: Wellwick 14 <3,FRIENDSANITY,'Prophet' Wellwick +6073,Forest,Friendsanity: Delores 1 <3,FRIENDSANITY,Delores - Custom NPC +6074,Forest,Friendsanity: Delores 2 <3,FRIENDSANITY,Delores - Custom NPC +6075,Forest,Friendsanity: Delores 3 <3,FRIENDSANITY,Delores - Custom NPC +6076,Forest,Friendsanity: Delores 4 <3,FRIENDSANITY,Delores - Custom NPC +6077,Forest,Friendsanity: Delores 5 <3,FRIENDSANITY,Delores - Custom NPC +6078,Forest,Friendsanity: Delores 6 <3,FRIENDSANITY,Delores - Custom NPC +6079,Forest,Friendsanity: Delores 7 <3,FRIENDSANITY,Delores - Custom NPC +6080,Forest,Friendsanity: Delores 8 <3,FRIENDSANITY,Delores - Custom NPC +6081,Forest,Friendsanity: Delores 9 <3,FRIENDSANITY,Delores - Custom NPC +6082,Forest,Friendsanity: Delores 10 <3,FRIENDSANITY,Delores - Custom NPC +6083,Forest,Friendsanity: Delores 11 <3,FRIENDSANITY,Delores - Custom NPC +6084,Forest,Friendsanity: Delores 12 <3,FRIENDSANITY,Delores - Custom NPC +6085,Forest,Friendsanity: Delores 13 <3,FRIENDSANITY,Delores - Custom NPC +6086,Forest,Friendsanity: Delores 14 <3,FRIENDSANITY,Delores - Custom NPC +6087,Forest,Friendsanity: Alec 1 <3,FRIENDSANITY,Alec Revisited +6088,Forest,Friendsanity: Alec 2 <3,FRIENDSANITY,Alec Revisited +6089,Forest,Friendsanity: Alec 3 <3,FRIENDSANITY,Alec Revisited +6090,Forest,Friendsanity: Alec 4 <3,FRIENDSANITY,Alec Revisited +6091,Forest,Friendsanity: Alec 5 <3,FRIENDSANITY,Alec Revisited +6092,Forest,Friendsanity: Alec 6 <3,FRIENDSANITY,Alec Revisited +6093,Forest,Friendsanity: Alec 7 <3,FRIENDSANITY,Alec Revisited +6094,Forest,Friendsanity: Alec 8 <3,FRIENDSANITY,Alec Revisited +6095,Forest,Friendsanity: Alec 9 <3,FRIENDSANITY,Alec Revisited +6096,Forest,Friendsanity: Alec 10 <3,FRIENDSANITY,Alec Revisited +6097,Forest,Friendsanity: Alec 11 <3,FRIENDSANITY,Alec Revisited +6098,Forest,Friendsanity: Alec 12 <3,FRIENDSANITY,Alec Revisited +6099,Forest,Friendsanity: Alec 13 <3,FRIENDSANITY,Alec Revisited +6100,Forest,Friendsanity: Alec 14 <3,FRIENDSANITY,Alec Revisited +6101,Forest,Friendsanity: Eugene 1 <3,FRIENDSANITY,Custom NPC Eugene +6102,Forest,Friendsanity: Eugene 2 <3,FRIENDSANITY,Custom NPC Eugene +6103,Forest,Friendsanity: Eugene 3 <3,FRIENDSANITY,Custom NPC Eugene +6104,Forest,Friendsanity: Eugene 4 <3,FRIENDSANITY,Custom NPC Eugene +6105,Forest,Friendsanity: Eugene 5 <3,FRIENDSANITY,Custom NPC Eugene +6106,Forest,Friendsanity: Eugene 6 <3,FRIENDSANITY,Custom NPC Eugene +6107,Forest,Friendsanity: Eugene 7 <3,FRIENDSANITY,Custom NPC Eugene +6108,Forest,Friendsanity: Eugene 8 <3,FRIENDSANITY,Custom NPC Eugene +6109,Forest,Friendsanity: Eugene 9 <3,FRIENDSANITY,Custom NPC Eugene +6110,Forest,Friendsanity: Eugene 10 <3,FRIENDSANITY,Custom NPC Eugene +6111,Forest,Friendsanity: Eugene 11 <3,FRIENDSANITY,Custom NPC Eugene +6112,Forest,Friendsanity: Eugene 12 <3,FRIENDSANITY,Custom NPC Eugene +6113,Forest,Friendsanity: Eugene 13 <3,FRIENDSANITY,Custom NPC Eugene +6114,Forest,Friendsanity: Eugene 14 <3,FRIENDSANITY,Custom NPC Eugene +6115,Forest,Friendsanity: Juna 1 <3,FRIENDSANITY,Juna - Roommate NPC +6116,Forest,Friendsanity: Juna 2 <3,FRIENDSANITY,Juna - Roommate NPC +6117,Forest,Friendsanity: Juna 3 <3,FRIENDSANITY,Juna - Roommate NPC +6118,Forest,Friendsanity: Juna 4 <3,FRIENDSANITY,Juna - Roommate NPC +6119,Forest,Friendsanity: Juna 5 <3,FRIENDSANITY,Juna - Roommate NPC +6120,Forest,Friendsanity: Juna 6 <3,FRIENDSANITY,Juna - Roommate NPC +6121,Forest,Friendsanity: Juna 7 <3,FRIENDSANITY,Juna - Roommate NPC +6122,Forest,Friendsanity: Juna 8 <3,FRIENDSANITY,Juna - Roommate NPC +6123,Forest,Friendsanity: Juna 9 <3,FRIENDSANITY,Juna - Roommate NPC +6124,Forest,Friendsanity: Juna 10 <3,FRIENDSANITY,Juna - Roommate NPC +6125,Town,Friendsanity: Riley 1 <3,FRIENDSANITY,Custom NPC - Riley +6126,Town,Friendsanity: Riley 2 <3,FRIENDSANITY,Custom NPC - Riley +6127,Town,Friendsanity: Riley 3 <3,FRIENDSANITY,Custom NPC - Riley +6128,Town,Friendsanity: Riley 4 <3,FRIENDSANITY,Custom NPC - Riley +6129,Town,Friendsanity: Riley 5 <3,FRIENDSANITY,Custom NPC - Riley +6130,Town,Friendsanity: Riley 6 <3,FRIENDSANITY,Custom NPC - Riley +6131,Town,Friendsanity: Riley 7 <3,FRIENDSANITY,Custom NPC - Riley +6132,Town,Friendsanity: Riley 8 <3,FRIENDSANITY,Custom NPC - Riley +6133,Town,Friendsanity: Riley 9 <3,FRIENDSANITY,Custom NPC - Riley +6134,Town,Friendsanity: Riley 10 <3,FRIENDSANITY,Custom NPC - Riley +6135,Town,Friendsanity: Riley 11 <3,FRIENDSANITY,Custom NPC - Riley +6136,Town,Friendsanity: Riley 12 <3,FRIENDSANITY,Custom NPC - Riley +6137,Town,Friendsanity: Riley 13 <3,FRIENDSANITY,Custom NPC - Riley +6138,Town,Friendsanity: Riley 14 <3,FRIENDSANITY,Custom NPC - Riley +7001,Pierre's General Store,Premium Pack,BACKPACK,Bigger Backpack +7002,Carpenter Shop,Tractor Garage Blueprint,BUILDING_BLUEPRINT,Tractor Mod +7003,The Deep Woods Depth 100,Pet the Deep Woods Unicorn,MANDATORY,DeepWoods +7004,The Deep Woods Depth 50,Breaking Up Deep Woods Gingerbread House,MANDATORY,DeepWoods +7005,The Deep Woods Depth 50,Drinking From Deep Woods Fountain,MANDATORY,DeepWoods +7006,The Deep Woods Depth 100,Deep Woods Treasure Chest,MANDATORY,DeepWoods +7007,The Deep Woods Depth 100,Deep Woods Trash Bin,MANDATORY,DeepWoods +7008,The Deep Woods Depth 50,Chop Down a Deep Woods Iridium Tree,MANDATORY,DeepWoods +7009,The Deep Woods Depth 10,The Deep Woods: Depth 10,ELEVATOR,DeepWoods +7010,The Deep Woods Depth 20,The Deep Woods: Depth 20,ELEVATOR,DeepWoods +7011,The Deep Woods Depth 30,The Deep Woods: Depth 30,ELEVATOR,DeepWoods +7012,The Deep Woods Depth 40,The Deep Woods: Depth 40,ELEVATOR,DeepWoods +7013,The Deep Woods Depth 50,The Deep Woods: Depth 50,ELEVATOR,DeepWoods +7014,The Deep Woods Depth 60,The Deep Woods: Depth 60,ELEVATOR,DeepWoods +7015,The Deep Woods Depth 70,The Deep Woods: Depth 70,ELEVATOR,DeepWoods +7016,The Deep Woods Depth 80,The Deep Woods: Depth 80,ELEVATOR,DeepWoods +7017,The Deep Woods Depth 90,The Deep Woods: Depth 90,ELEVATOR,DeepWoods +7018,The Deep Woods Depth 100,The Deep Woods: Depth 100,ELEVATOR,DeepWoods +7019,The Deep Woods Depth 50,Purify an Infested Lichtung,MANDATORY,DeepWoods +7020,Skull Cavern Floor 25,Skull Cavern: Floor 25,ELEVATOR,Skull Cavern Elevator +7021,Skull Cavern Floor 50,Skull Cavern: Floor 50,ELEVATOR,Skull Cavern Elevator +7022,Skull Cavern Floor 75,Skull Cavern: Floor 75,ELEVATOR,Skull Cavern Elevator +7023,Skull Cavern Floor 100,Skull Cavern: Floor 100,ELEVATOR,Skull Cavern Elevator +7024,Skull Cavern Floor 125,Skull Cavern: Floor 125,ELEVATOR,Skull Cavern Elevator +7025,Skull Cavern Floor 150,Skull Cavern: Floor 150,ELEVATOR,Skull Cavern Elevator +7026,Skull Cavern Floor 175,Skull Cavern: Floor 175,ELEVATOR,Skull Cavern Elevator +7027,Skull Cavern Floor 200,Skull Cavern: Floor 200,ELEVATOR,Skull Cavern Elevator +7501,Town,Missing Envelope,"MANDATORY,QUEST",Ayeisha - The Postal Worker (Custom NPC) +7502,Town,Lost Emerald Ring,"MANDATORY,QUEST",Ayeisha - The Postal Worker (Custom NPC) +7503,Forest,Mr.Ginger's request,"MANDATORY,QUEST",Mister Ginger (cat npc) +7504,Forest,Juna's Drink Request,"MANDATORY,QUEST",Juna - Roommate NPC +7505,Forest,Juna's BFF Request,"MANDATORY,QUEST",Juna - Roommate NPC +7506,Forest,Juna's Monster Mash,SPECIAL_ORDER_BOARD,Juna - Roommate NPC diff --git a/worlds/stardew_valley/data/museum_data.py b/worlds/stardew_valley/data/museum_data.py index bc3197d1..eb42a17d 100644 --- a/worlds/stardew_valley/data/museum_data.py +++ b/worlds/stardew_valley/data/museum_data.py @@ -6,7 +6,8 @@ from typing import List, Tuple, Union, Optional from . import common_data as common from .game_item import GameItem from .monster_data import Monster -from .region_data import SVRegion +from ..strings.region_names import Region +from ..strings.geode_names import Geode @dataclass(frozen=True) @@ -40,14 +41,6 @@ class MuseumItem(GameItem): f" Monsters: {self.monsters}) " -class Geode: - geode = "Geode" - frozen_geode = "Frozen Geode" - magma_geode = "Magma Geode" - omni_geode = "Omni Geode" - artifact_trove = "Artifact Trove" - - unlikely = () all_artifact_items: List[MuseumItem] = [] @@ -92,205 +85,205 @@ def create_mineral(name: str, class Artifact: - dwarf_scroll_i = create_artifact("Dwarf Scroll I", 96, 5.6, SVRegion.mines_floor_20, + dwarf_scroll_i = create_artifact("Dwarf Scroll I", 96, 5.6, Region.mines_floor_20, monsters=unlikely) - dwarf_scroll_ii = create_artifact("Dwarf Scroll II", 97, 3, SVRegion.mines_floor_20, + dwarf_scroll_ii = create_artifact("Dwarf Scroll II", 97, 3, Region.mines_floor_20, monsters=unlikely) - dwarf_scroll_iii = create_artifact("Dwarf Scroll III", 98, 7.5, SVRegion.mines_floor_60, + dwarf_scroll_iii = create_artifact("Dwarf Scroll III", 98, 7.5, Region.mines_floor_60, monsters=Monster.blue_slime) - dwarf_scroll_iv = create_artifact("Dwarf Scroll IV", 99, 4, SVRegion.mines_floor_100) - chipped_amphora = create_artifact("Chipped Amphora", 100, 6.7, SVRegion.town, + dwarf_scroll_iv = create_artifact("Dwarf Scroll IV", 99, 4, Region.mines_floor_100) + chipped_amphora = create_artifact("Chipped Amphora", 100, 6.7, Region.town, geodes=Geode.artifact_trove) - arrowhead = create_artifact("Arrowhead", 101, 8.5, (SVRegion.mountain, SVRegion.forest, SVRegion.bus_stop), + arrowhead = create_artifact("Arrowhead", 101, 8.5, (Region.mountain, Region.forest, Region.bus_stop), geodes=Geode.artifact_trove) - ancient_doll = create_artifact("Ancient Doll", 103, 13.1, (SVRegion.mountain, SVRegion.forest, SVRegion.bus_stop), + ancient_doll = create_artifact("Ancient Doll", 103, 13.1, (Region.mountain, Region.forest, Region.bus_stop), geodes=(Geode.artifact_trove, common.fishing_chest)) - elvish_jewelry = create_artifact("Elvish Jewelry", 104, 5.3, SVRegion.forest, + elvish_jewelry = create_artifact("Elvish Jewelry", 104, 5.3, Region.forest, geodes=(Geode.artifact_trove, common.fishing_chest)) - chewing_stick = create_artifact("Chewing Stick", 105, 10.3, (SVRegion.mountain, SVRegion.forest, SVRegion.town), + chewing_stick = create_artifact("Chewing Stick", 105, 10.3, (Region.mountain, Region.forest, Region.town), geodes=(Geode.artifact_trove, common.fishing_chest)) - ornamental_fan = create_artifact("Ornamental Fan", 106, 7.4, (SVRegion.beach, SVRegion.forest, SVRegion.town), + ornamental_fan = create_artifact("Ornamental Fan", 106, 7.4, (Region.beach, Region.forest, Region.town), geodes=(Geode.artifact_trove, common.fishing_chest)) - dinosaur_egg = create_artifact("Dinosaur Egg", 107, 11.4, (SVRegion.mountain, SVRegion.skull_cavern), + dinosaur_egg = create_artifact("Dinosaur Egg", 107, 11.4, (Region.mountain, Region.skull_cavern), geodes=common.fishing_chest, monsters=Monster.pepper_rex) - rare_disc = create_artifact("Rare Disc", 108, 5.6, SVRegion.stardew_valley, + rare_disc = create_artifact("Rare Disc", 108, 5.6, Region.stardew_valley, geodes=(Geode.artifact_trove, common.fishing_chest), monsters=unlikely) - ancient_sword = create_artifact("Ancient Sword", 109, 5.8, (SVRegion.forest, SVRegion.mountain), + ancient_sword = create_artifact("Ancient Sword", 109, 5.8, (Region.forest, Region.mountain), geodes=(Geode.artifact_trove, common.fishing_chest)) - rusty_spoon = create_artifact("Rusty Spoon", 110, 9.6, SVRegion.town, + rusty_spoon = create_artifact("Rusty Spoon", 110, 9.6, Region.town, geodes=(Geode.artifact_trove, common.fishing_chest)) - rusty_spur = create_artifact("Rusty Spur", 111, 15.6, SVRegion.farm, + rusty_spur = create_artifact("Rusty Spur", 111, 15.6, Region.farm, geodes=(Geode.artifact_trove, common.fishing_chest)) - rusty_cog = create_artifact("Rusty Cog", 112, 9.6, SVRegion.mountain, + rusty_cog = create_artifact("Rusty Cog", 112, 9.6, Region.mountain, geodes=(Geode.artifact_trove, common.fishing_chest)) - chicken_statue = create_artifact("Chicken Statue", 113, 13.5, SVRegion.farm, + chicken_statue = create_artifact("Chicken Statue", 113, 13.5, Region.farm, geodes=(Geode.artifact_trove, common.fishing_chest)) - ancient_seed = create_artifact("Ancient Seed", 114, 8.4, (SVRegion.forest, SVRegion.mountain), + ancient_seed = create_artifact("Ancient Seed", 114, 8.4, (Region.forest, Region.mountain), geodes=(Geode.artifact_trove, common.fishing_chest), monsters=unlikely) - prehistoric_tool = create_artifact("Prehistoric Tool", 115, 11.1, (SVRegion.mountain, SVRegion.forest, SVRegion.bus_stop), + prehistoric_tool = create_artifact("Prehistoric Tool", 115, 11.1, (Region.mountain, Region.forest, Region.bus_stop), geodes=(Geode.artifact_trove, common.fishing_chest)) - dried_starfish = create_artifact("Dried Starfish", 116, 12.5, SVRegion.beach, + dried_starfish = create_artifact("Dried Starfish", 116, 12.5, Region.beach, geodes=(Geode.artifact_trove, common.fishing_chest)) - anchor = create_artifact("Anchor", 117, 8.5, SVRegion.beach, geodes=(Geode.artifact_trove, common.fishing_chest)) - glass_shards = create_artifact("Glass Shards", 118, 11.5, SVRegion.beach, + anchor = create_artifact("Anchor", 117, 8.5, Region.beach, geodes=(Geode.artifact_trove, common.fishing_chest)) + glass_shards = create_artifact("Glass Shards", 118, 11.5, Region.beach, geodes=(Geode.artifact_trove, common.fishing_chest)) - bone_flute = create_artifact("Bone Flute", 119, 6.3, (SVRegion.mountain, SVRegion.forest, SVRegion.town), + bone_flute = create_artifact("Bone Flute", 119, 6.3, (Region.mountain, Region.forest, Region.town), geodes=(Geode.artifact_trove, common.fishing_chest)) prehistoric_handaxe = create_artifact("Prehistoric Handaxe", 120, 13.7, - (SVRegion.mountain, SVRegion.forest, SVRegion.bus_stop), + (Region.mountain, Region.forest, Region.bus_stop), geodes=Geode.artifact_trove) - dwarvish_helm = create_artifact("Dwarvish Helm", 121, 8.7, SVRegion.mines_floor_20, - geodes=(Geode.geode, Geode.omni_geode, Geode.artifact_trove)) - dwarf_gadget = create_artifact("Dwarf Gadget", 122, 9.7, SVRegion.mines_floor_60, - geodes=(Geode.magma_geode, Geode.omni_geode, Geode.artifact_trove)) - ancient_drum = create_artifact("Ancient Drum", 123, 9.5, (SVRegion.bus_stop, SVRegion.forest, SVRegion.town), - geodes=(Geode.frozen_geode, Geode.omni_geode, Geode.artifact_trove)) - golden_mask = create_artifact("Golden Mask", 124, 6.7, SVRegion.desert, + dwarvish_helm = create_artifact("Dwarvish Helm", 121, 8.7, Region.mines_floor_20, + geodes=(Geode.geode, Geode.omni, Geode.artifact_trove)) + dwarf_gadget = create_artifact("Dwarf Gadget", 122, 9.7, Region.mines_floor_60, + geodes=(Geode.magma, Geode.omni, Geode.artifact_trove)) + ancient_drum = create_artifact("Ancient Drum", 123, 9.5, (Region.bus_stop, Region.forest, Region.town), + geodes=(Geode.frozen, Geode.omni, Geode.artifact_trove)) + golden_mask = create_artifact("Golden Mask", 124, 6.7, Region.desert, geodes=Geode.artifact_trove) - golden_relic = create_artifact("Golden Relic", 125, 9.7, SVRegion.desert, + golden_relic = create_artifact("Golden Relic", 125, 9.7, Region.desert, geodes=Geode.artifact_trove) - strange_doll_green = create_artifact("Strange Doll (Green)", 126, 10, SVRegion.town, + strange_doll_green = create_artifact("Strange Doll (Green)", 126, 10, Region.town, geodes=common.secret_note) - strange_doll = create_artifact("Strange Doll", 127, 10, SVRegion.desert, + strange_doll = create_artifact("Strange Doll", 127, 10, Region.desert, geodes=common.secret_note) prehistoric_scapula = create_artifact("Prehistoric Scapula", 579, 6.2, - (SVRegion.dig_site, SVRegion.forest, SVRegion.town)) + (Region.dig_site, Region.forest, Region.town)) prehistoric_tibia = create_artifact("Prehistoric Tibia", 580, 16.6, - (SVRegion.dig_site, SVRegion.forest, SVRegion.railroad)) - prehistoric_skull = create_artifact("Prehistoric Skull", 581, 3.9, (SVRegion.dig_site, SVRegion.mountain)) - skeletal_hand = create_artifact("Skeletal Hand", 582, 7.9, (SVRegion.dig_site, SVRegion.backwoods, SVRegion.beach)) - prehistoric_rib = create_artifact("Prehistoric Rib", 583, 15, (SVRegion.dig_site, SVRegion.farm, SVRegion.town), + (Region.dig_site, Region.forest, Region.railroad)) + prehistoric_skull = create_artifact("Prehistoric Skull", 581, 3.9, (Region.dig_site, Region.mountain)) + skeletal_hand = create_artifact("Skeletal Hand", 582, 7.9, (Region.dig_site, Region.backwoods, Region.beach)) + prehistoric_rib = create_artifact("Prehistoric Rib", 583, 15, (Region.dig_site, Region.farm, Region.town), monsters=Monster.pepper_rex) - prehistoric_vertebra = create_artifact("Prehistoric Vertebra", 584, 12.7, (SVRegion.dig_site, SVRegion.bus_stop), + prehistoric_vertebra = create_artifact("Prehistoric Vertebra", 584, 12.7, (Region.dig_site, Region.bus_stop), monsters=Monster.pepper_rex) - skeletal_tail = create_artifact("Skeletal Tail", 585, 5.1, (SVRegion.dig_site, SVRegion.mines_floor_20), + skeletal_tail = create_artifact("Skeletal Tail", 585, 5.1, (Region.dig_site, Region.mines_floor_20), geodes=common.fishing_chest) - nautilus_fossil = create_artifact("Nautilus Fossil", 586, 6.9, (SVRegion.dig_site, SVRegion.beach), + nautilus_fossil = create_artifact("Nautilus Fossil", 586, 6.9, (Region.dig_site, Region.beach), geodes=common.fishing_chest) - amphibian_fossil = create_artifact("Amphibian Fossil", 587, 6.3, (SVRegion.dig_site, SVRegion.forest, SVRegion.mountain), + amphibian_fossil = create_artifact("Amphibian Fossil", 587, 6.3, (Region.dig_site, Region.forest, Region.mountain), geodes=common.fishing_chest) palm_fossil = create_artifact("Palm Fossil", 588, 10.2, - (SVRegion.dig_site, SVRegion.desert, SVRegion.forest, SVRegion.beach)) - trilobite = create_artifact("Trilobite", 589, 7.4, (SVRegion.dig_site, SVRegion.desert, SVRegion.forest, SVRegion.beach)) + (Region.dig_site, Region.desert, Region.forest, Region.beach)) + trilobite = create_artifact("Trilobite", 589, 7.4, (Region.dig_site, Region.desert, Region.forest, Region.beach)) class Mineral: - quartz = create_mineral("Quartz", 80, SVRegion.mines_floor_20, + quartz = create_mineral("Quartz", 80, Region.mines_floor_20, monsters=Monster.stone_golem) - fire_quartz = create_mineral("Fire Quartz", 82, SVRegion.mines_floor_100, - geodes=(Geode.magma_geode, Geode.omni_geode, common.fishing_chest), + fire_quartz = create_mineral("Fire Quartz", 82, Region.mines_floor_100, + geodes=(Geode.magma, Geode.omni, common.fishing_chest), difficulty=1.0 / 12.0) - frozen_tear = create_mineral("Frozen Tear", 84, SVRegion.mines_floor_60, - geodes=(Geode.frozen_geode, Geode.omni_geode, common.fishing_chest), + frozen_tear = create_mineral("Frozen Tear", 84, Region.mines_floor_60, + geodes=(Geode.frozen, Geode.omni, common.fishing_chest), monsters=unlikely, difficulty=1.0 / 12.0) - earth_crystal = create_mineral("Earth Crystal", 86, SVRegion.mines_floor_20, - geodes=(Geode.geode, Geode.omni_geode, common.fishing_chest), + earth_crystal = create_mineral("Earth Crystal", 86, Region.mines_floor_20, + geodes=(Geode.geode, Geode.omni, common.fishing_chest), monsters=Monster.duggy, difficulty=1.0 / 12.0) - emerald = create_mineral("Emerald", 60, SVRegion.mines_floor_100, + emerald = create_mineral("Emerald", 60, Region.mines_floor_100, geodes=common.fishing_chest) - aquamarine = create_mineral("Aquamarine", 62, SVRegion.mines_floor_60, + aquamarine = create_mineral("Aquamarine", 62, Region.mines_floor_60, geodes=common.fishing_chest) - ruby = create_mineral("Ruby", 64, SVRegion.mines_floor_100, + ruby = create_mineral("Ruby", 64, Region.mines_floor_100, geodes=common.fishing_chest) - amethyst = create_mineral("Amethyst", 66, SVRegion.mines_floor_20, + amethyst = create_mineral("Amethyst", 66, Region.mines_floor_20, geodes=common.fishing_chest) - topaz = create_mineral("Topaz", 68, SVRegion.mines_floor_20, + topaz = create_mineral("Topaz", 68, Region.mines_floor_20, geodes=common.fishing_chest) - jade = create_mineral("Jade", 70, SVRegion.mines_floor_60, + jade = create_mineral("Jade", 70, Region.mines_floor_60, geodes=common.fishing_chest) - diamond = create_mineral("Diamond", 72, SVRegion.mines_floor_60, + diamond = create_mineral("Diamond", 72, Region.mines_floor_60, geodes=common.fishing_chest) - prismatic_shard = create_mineral("Prismatic Shard", 74, SVRegion.perfect_skull_cavern, + prismatic_shard = create_mineral("Prismatic Shard", 74, Region.skull_cavern_100, geodes=unlikely, monsters=unlikely) - alamite = create_mineral("Alamite", 538, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - bixite = create_mineral("Bixite", 539, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode), + alamite = create_mineral("Alamite", 538, Region.town, + geodes=(Geode.geode, Geode.omni)) + bixite = create_mineral("Bixite", 539, Region.town, + geodes=(Geode.magma, Geode.omni), monsters=unlikely) - baryte = create_mineral("Baryte", 540, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - aerinite = create_mineral("Aerinite", 541, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - calcite = create_mineral("Calcite", 542, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - dolomite = create_mineral("Dolomite", 543, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - esperite = create_mineral("Esperite", 544, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - fluorapatite = create_mineral("Fluorapatite", 545, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - geminite = create_mineral("Geminite", 546, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - helvite = create_mineral("Helvite", 547, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - jamborite = create_mineral("Jamborite", 548, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - jagoite = create_mineral("Jagoite", 549, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - kyanite = create_mineral("Kyanite", 550, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - lunarite = create_mineral("Lunarite", 551, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - malachite = create_mineral("Malachite", 552, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - neptunite = create_mineral("Neptunite", 553, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - lemon_stone = create_mineral("Lemon Stone", 554, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - nekoite = create_mineral("Nekoite", 555, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - orpiment = create_mineral("Orpiment", 556, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - petrified_slime = create_mineral("Petrified Slime", 557, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - thunder_egg = create_mineral("Thunder Egg", 558, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - pyrite = create_mineral("Pyrite", 559, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - ocean_stone = create_mineral("Ocean Stone", 560, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - ghost_crystal = create_mineral("Ghost Crystal", 561, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - tigerseye = create_mineral("Tigerseye", 562, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - jasper = create_mineral("Jasper", 563, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - opal = create_mineral("Opal", 564, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - fire_opal = create_mineral("Fire Opal", 565, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - celestine = create_mineral("Celestine", 566, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - marble = create_mineral("Marble", 567, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - sandstone = create_mineral("Sandstone", 568, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - granite = create_mineral("Granite", 569, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - basalt = create_mineral("Basalt", 570, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - limestone = create_mineral("Limestone", 571, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - soapstone = create_mineral("Soapstone", 572, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - hematite = create_mineral("Hematite", 573, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - mudstone = create_mineral("Mudstone", 574, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - obsidian = create_mineral("Obsidian", 575, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) - slate = create_mineral("Slate", 576, SVRegion.town, - geodes=(Geode.geode, Geode.omni_geode)) - fairy_stone = create_mineral("Fairy Stone", 577, SVRegion.town, - geodes=(Geode.frozen_geode, Geode.omni_geode)) - star_shards = create_mineral("Star Shards", 578, SVRegion.town, - geodes=(Geode.magma_geode, Geode.omni_geode)) + baryte = create_mineral("Baryte", 540, Region.town, + geodes=(Geode.magma, Geode.omni)) + aerinite = create_mineral("Aerinite", 541, Region.town, + geodes=(Geode.frozen, Geode.omni)) + calcite = create_mineral("Calcite", 542, Region.town, + geodes=(Geode.geode, Geode.omni)) + dolomite = create_mineral("Dolomite", 543, Region.town, + geodes=(Geode.magma, Geode.omni)) + esperite = create_mineral("Esperite", 544, Region.town, + geodes=(Geode.frozen, Geode.omni)) + fluorapatite = create_mineral("Fluorapatite", 545, Region.town, + geodes=(Geode.frozen, Geode.omni)) + geminite = create_mineral("Geminite", 546, Region.town, + geodes=(Geode.frozen, Geode.omni)) + helvite = create_mineral("Helvite", 547, Region.town, + geodes=(Geode.magma, Geode.omni)) + jamborite = create_mineral("Jamborite", 548, Region.town, + geodes=(Geode.geode, Geode.omni)) + jagoite = create_mineral("Jagoite", 549, Region.town, + geodes=(Geode.geode, Geode.omni)) + kyanite = create_mineral("Kyanite", 550, Region.town, + geodes=(Geode.frozen, Geode.omni)) + lunarite = create_mineral("Lunarite", 551, Region.town, + geodes=(Geode.frozen, Geode.omni)) + malachite = create_mineral("Malachite", 552, Region.town, + geodes=(Geode.geode, Geode.omni)) + neptunite = create_mineral("Neptunite", 553, Region.town, + geodes=(Geode.magma, Geode.omni)) + lemon_stone = create_mineral("Lemon Stone", 554, Region.town, + geodes=(Geode.magma, Geode.omni)) + nekoite = create_mineral("Nekoite", 555, Region.town, + geodes=(Geode.geode, Geode.omni)) + orpiment = create_mineral("Orpiment", 556, Region.town, + geodes=(Geode.geode, Geode.omni)) + petrified_slime = create_mineral("Petrified Slime", 557, Region.town, + geodes=(Geode.geode, Geode.omni)) + thunder_egg = create_mineral("Thunder Egg", 558, Region.town, + geodes=(Geode.geode, Geode.omni)) + pyrite = create_mineral("Pyrite", 559, Region.town, + geodes=(Geode.frozen, Geode.omni)) + ocean_stone = create_mineral("Ocean Stone", 560, Region.town, + geodes=(Geode.frozen, Geode.omni)) + ghost_crystal = create_mineral("Ghost Crystal", 561, Region.town, + geodes=(Geode.frozen, Geode.omni)) + tigerseye = create_mineral("Tigerseye", 562, Region.town, + geodes=(Geode.magma, Geode.omni)) + jasper = create_mineral("Jasper", 563, Region.town, + geodes=(Geode.magma, Geode.omni)) + opal = create_mineral("Opal", 564, Region.town, + geodes=(Geode.frozen, Geode.omni)) + fire_opal = create_mineral("Fire Opal", 565, Region.town, + geodes=(Geode.magma, Geode.omni)) + celestine = create_mineral("Celestine", 566, Region.town, + geodes=(Geode.geode, Geode.omni)) + marble = create_mineral("Marble", 567, Region.town, + geodes=(Geode.frozen, Geode.omni)) + sandstone = create_mineral("Sandstone", 568, Region.town, + geodes=(Geode.geode, Geode.omni)) + granite = create_mineral("Granite", 569, Region.town, + geodes=(Geode.geode, Geode.omni)) + basalt = create_mineral("Basalt", 570, Region.town, + geodes=(Geode.magma, Geode.omni)) + limestone = create_mineral("Limestone", 571, Region.town, + geodes=(Geode.geode, Geode.omni)) + soapstone = create_mineral("Soapstone", 572, Region.town, + geodes=(Geode.frozen, Geode.omni)) + hematite = create_mineral("Hematite", 573, Region.town, + geodes=(Geode.frozen, Geode.omni)) + mudstone = create_mineral("Mudstone", 574, Region.town, + geodes=(Geode.geode, Geode.omni)) + obsidian = create_mineral("Obsidian", 575, Region.town, + geodes=(Geode.magma, Geode.omni)) + slate = create_mineral("Slate", 576, Region.town, + geodes=(Geode.geode, Geode.omni)) + fairy_stone = create_mineral("Fairy Stone", 577, Region.town, + geodes=(Geode.frozen, Geode.omni)) + star_shards = create_mineral("Star Shards", 578, Region.town, + geodes=(Geode.magma, Geode.omni)) dwarf_scrolls = (Artifact.dwarf_scroll_i, Artifact.dwarf_scroll_ii, Artifact.dwarf_scroll_iii, Artifact.dwarf_scroll_iv) diff --git a/worlds/stardew_valley/data/recipe_data.py b/worlds/stardew_valley/data/recipe_data.py new file mode 100644 index 00000000..dc1b490b --- /dev/null +++ b/worlds/stardew_valley/data/recipe_data.py @@ -0,0 +1,190 @@ +from typing import Dict, List + +from ..strings.animal_product_names import AnimalProduct +from ..strings.artisan_good_names import ArtisanGood +from ..strings.crop_names import Fruit, Vegetable +from ..strings.fish_names import Fish, WaterItem +from ..strings.flower_names import Flower +from ..strings.forageable_names import Forageable +from ..strings.ingredient_names import Ingredient +from ..strings.food_names import Meal, Beverage +from ..strings.region_names import Region +from ..strings.season_names import Season +from ..strings.skill_names import Skill +from ..strings.villager_names import NPC + + +class RecipeSource: + pass + + +class StarterSource(RecipeSource): + pass + + +class QueenOfSauceSource(RecipeSource): + year: int + season: str + day: int + + def __init__(self, year: int, season: str, day: int): + self.year = year + self.season = season + self.day = day + + +class FriendshipSource(RecipeSource): + friend: str + hearts: int + + def __init__(self, friend: str, hearts: int): + self.friend = friend + self.hearts = hearts + + +class SkillSource(RecipeSource): + skill: str + level: int + + def __init__(self, skill: str, level: int): + self.skill = skill + self.level = level + + +class ShopSource(RecipeSource): + region: str + price: int + + def __init__(self, region: str, price: int): + self.region = region + self.price = price + + +class ShopTradeSource(ShopSource): + currency: str + + +class CookingRecipe: + meal: str + ingredients: Dict[str, int] + source: RecipeSource + + def __init__(self, meal: str, ingredients: Dict[str, int], source: RecipeSource): + self.meal = meal + self.ingredients = ingredients + self.source = source + + def __repr__(self): + return f"{self.meal} (Source: {self.source} |" \ + f" Ingredients: {self.ingredients})" + + +all_cooking_recipes: List[CookingRecipe] = [] + + +def friendship_recipe(name: str, friend: str, hearts: int, ingredients: Dict[str, int]) -> CookingRecipe: + source = FriendshipSource(friend, hearts) + return create_recipe(name, ingredients, source) + + +def skill_recipe(name: str, skill: str, level: int, ingredients: Dict[str, int]) -> CookingRecipe: + source = SkillSource(skill, level) + return create_recipe(name, ingredients, source) + + +def shop_recipe(name: str, region: str, price: int, ingredients: Dict[str, int]) -> CookingRecipe: + source = ShopSource(region, price) + return create_recipe(name, ingredients, source) + + +def queen_of_sauce_recipe(name: str, year: int, season: str, day: int, ingredients: Dict[str, int]) -> CookingRecipe: + source = QueenOfSauceSource(year, season, day) + return create_recipe(name, ingredients, source) + + +def starter_recipe(name: str, ingredients: Dict[str, int]) -> CookingRecipe: + source = StarterSource() + return create_recipe(name, ingredients, source) + + +def create_recipe(name: str, ingredients: Dict[str, int], source: RecipeSource) -> CookingRecipe: + recipe = CookingRecipe(name, ingredients, source) + all_cooking_recipes.append(recipe) + return recipe + + +algae_soup = friendship_recipe(Meal.algae_soup, NPC.clint, 3, {WaterItem.green_algae: 4}) +artichoke_dip = queen_of_sauce_recipe(Meal.artichoke_dip, 1, Season.fall, 28, {Vegetable.artichoke: 1, AnimalProduct.cow_milk: 1}) +baked_fish = queen_of_sauce_recipe(Meal.baked_fish, 1, Season.summer, 7, {Fish.sunfish: 1, Fish.bream: 1, Ingredient.wheat_flour: 1}) +bean_hotpot = friendship_recipe(Meal.bean_hotpot, NPC.clint, 7, {Vegetable.green_bean: 2}) +blackberry_cobbler_ingredients = {Forageable.blackberry: 2, Ingredient.sugar: 1, Ingredient.wheat_flour: 1} +blackberry_cobbler_qos = queen_of_sauce_recipe(Meal.blackberry_cobbler, 2, Season.fall, 14, blackberry_cobbler_ingredients) +blueberry_tart = friendship_recipe(Meal.blueberry_tart, NPC.pierre, 3, {Fruit.blueberry: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.any_egg: 1}) +bread = queen_of_sauce_recipe(Meal.bread, 1, Season.summer, 28, {Ingredient.wheat_flour: 1}) +cheese_cauliflower = friendship_recipe(Meal.cheese_cauliflower, NPC.pam, 3, {Vegetable.cauliflower: 1, ArtisanGood.cheese: 1}) +chocolate_cake_ingredients = {Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.chicken_egg: 1} +chocolate_cake_qos = queen_of_sauce_recipe(Meal.chocolate_cake, 1, Season.winter, 14, chocolate_cake_ingredients) +chowder = friendship_recipe(Meal.chowder, NPC.willy, 3, {WaterItem.clam: 1, AnimalProduct.cow_milk: 1}) +complete_breakfast = queen_of_sauce_recipe(Meal.complete_breakfast, 2, Season.spring, 21, {Meal.fried_egg: 1, AnimalProduct.milk: 1, Meal.hashbrowns: 1, Meal.pancakes: 1}) +crab_cakes_ingredients = {Fish.crab: 1, Ingredient.wheat_flour: 1, AnimalProduct.chicken_egg: 1, Ingredient.oil: 1} +crab_cakes_qos = queen_of_sauce_recipe(Meal.crab_cakes, 2, Season.fall, 21, crab_cakes_ingredients) +cranberry_candy = queen_of_sauce_recipe(Meal.cranberry_candy, 1, Season.winter, 28, {Fruit.cranberries: 1, Fruit.apple: 1, Ingredient.sugar: 1}) +crispy_bass = friendship_recipe(Meal.crispy_bass, NPC.kent, 3, {Fish.largemouth_bass: 1, Ingredient.wheat_flour: 1, Ingredient.oil: 1}) +dish_o_the_sea = skill_recipe(Meal.dish_o_the_sea, Skill.fishing, 3, {Fish.sardine: 2, Meal.hashbrowns: 1}) +eggplant_parmesan = friendship_recipe(Meal.eggplant_parmesan, NPC.lewis, 7, {Vegetable.eggplant: 1, Vegetable.tomato: 1}) +escargot = friendship_recipe(Meal.escargot, NPC.willy, 5, {Fish.snail: 1, Vegetable.garlic: 1}) +farmer_lunch = skill_recipe(Meal.farmer_lunch, Skill.farming, 3, {Meal.omelet: 2, Vegetable.parsnip: 1}) +fiddlehead_risotto = queen_of_sauce_recipe(Meal.fiddlehead_risotto, 2, Season.fall, 28, {Ingredient.oil: 1, Forageable.fiddlehead_fern: 1, Vegetable.garlic: 1}) +fish_taco = friendship_recipe(Meal.fish_taco, NPC.linus, 7, {Fish.tuna: 1, Meal.tortilla: 1, Vegetable.red_cabbage: 1, ArtisanGood.mayonnaise: 1}) +fried_calamari = friendship_recipe(Meal.fried_calamari, NPC.jodi, 3, {Fish.squid: 1, Ingredient.wheat_flour: 1, Ingredient.oil: 1}) +fried_eel = friendship_recipe(Meal.fried_eel, NPC.george, 3, {Fish.eel: 1, Ingredient.oil: 1}) +fried_egg = starter_recipe(Meal.fried_egg, {AnimalProduct.chicken_egg: 1}) +fried_mushroom = friendship_recipe(Meal.fried_mushroom, NPC.demetrius, 3, {Forageable.common_mushroom: 1, Forageable.morel: 1, Ingredient.oil: 1}) +fruit_salad = queen_of_sauce_recipe(Meal.fruit_salad, 2, Season.fall, 7, {Fruit.blueberry: 1, Fruit.melon: 1, Fruit.apricot: 1}) +ginger_ale = shop_recipe(Beverage.ginger_ale, Region.volcano_dwarf_shop, 1000, {Forageable.ginger: 3, Ingredient.sugar: 1}) +glazed_yams = queen_of_sauce_recipe(Meal.glazed_yams, 1, Season.fall, 21, {Vegetable.yam: 1, Ingredient.sugar: 1}) +hashbrowns = queen_of_sauce_recipe(Meal.hashbrowns, 2, Season.spring, 14, {Vegetable.potato: 1, Ingredient.oil: 1}) +ice_cream = friendship_recipe(Meal.ice_cream, NPC.jodi, 7, {AnimalProduct.cow_milk: 1, Ingredient.sugar: 1}) +maki_roll = queen_of_sauce_recipe(Meal.maki_roll, 1, Season.summer, 21, {Fish.any: 1, WaterItem.seaweed: 1, Ingredient.rice: 1}) +maple_bar = queen_of_sauce_recipe(Meal.maple_bar, 2, Season.summer, 14, {ArtisanGood.maple_syrup: 1, Ingredient.sugar: 1, Ingredient.wheat_flour: 1}) +miners_treat = skill_recipe(Meal.miners_treat, Skill.mining, 3, {Forageable.cave_carrot: 2, Ingredient.sugar: 1, AnimalProduct.cow_milk: 1}) +omelet = queen_of_sauce_recipe(Meal.omelet, 1, Season.spring, 28, {AnimalProduct.chicken_egg: 1, AnimalProduct.cow_milk: 1}) +pale_broth = friendship_recipe(Meal.pale_broth, NPC.marnie, 3, {WaterItem.white_algae: 2}) +pancakes = queen_of_sauce_recipe(Meal.pancakes, 1, Season.summer, 14, {Ingredient.wheat_flour: 1, AnimalProduct.chicken_egg: 1}) +parsnip_soup = friendship_recipe(Meal.parsnip_soup, NPC.caroline, 3, {Vegetable.parsnip: 1, AnimalProduct.cow_milk: 1, Ingredient.vinegar: 1}) +pepper_poppers = friendship_recipe(Meal.pepper_poppers, NPC.shane, 3, {Fruit.hot_pepper: 1, ArtisanGood.cheese: 1}) +pink_cake_ingredients = {Fruit.melon: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.chicken_egg: 1} +pink_cake_qos = queen_of_sauce_recipe(Meal.pink_cake, 2, Season.summer, 21, pink_cake_ingredients) +pizza_ingredients = {Ingredient.wheat_flour: 1, Vegetable.tomato: 1, ArtisanGood.cheese: 1} +pizza_qos = queen_of_sauce_recipe(Meal.pizza, 2, Season.spring, 7, pizza_ingredients) +pizza_saloon = shop_recipe(Meal.pizza, Region.saloon, 150, pizza_ingredients) +plum_pudding = queen_of_sauce_recipe(Meal.plum_pudding, 1, Season.winter, 7, {Forageable.wild_plum: 2, Ingredient.wheat_flour: 1, Ingredient.sugar: 1}) +poppyseed_muffin = queen_of_sauce_recipe(Meal.poppyseed_muffin, 2, Season.winter, 7, {Flower.poppy: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1}) +pumpkin_pie_ingredients = {Vegetable.pumpkin: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1, AnimalProduct.cow_milk: 1} +pumpkin_pie_qos = queen_of_sauce_recipe(Meal.pumpkin_pie, 1, Season.winter, 21, pumpkin_pie_ingredients) +red_plate = friendship_recipe(Meal.red_plate, NPC.emily, 7, {Vegetable.red_cabbage: 1, Vegetable.radish: 1}) +rhubarb_pie = friendship_recipe(Meal.rhubarb_pie, NPC.marnie, 7, {Fruit.rhubarb: 1, Ingredient.wheat_flour: 1, Ingredient.sugar: 1}) +rice_pudding = friendship_recipe(Meal.rice_pudding, NPC.evelyn, 7, {AnimalProduct.milk: 1, Ingredient.sugar: 1, Ingredient.rice: 1}) +roasted_hazelnuts = queen_of_sauce_recipe(Meal.roasted_hazelnuts, 2, Season.summer, 28, {Forageable.hazelnut: 3}) +roots_platter = skill_recipe(Meal.roots_platter, Skill.combat, 3, {Forageable.cave_carrot: 1, Forageable.winter_root: 1}) +salad = friendship_recipe(Meal.salad, NPC.emily, 3, {Forageable.leek: 1, Forageable.dandelion: 1, Ingredient.vinegar: 1}) +salmon_dinner = friendship_recipe(Meal.salmon_dinner, NPC.gus, 3, {Fish.salmon: 1, Vegetable.amaranth: 1, Vegetable.kale: 1}) +sashimi = friendship_recipe(Meal.sashimi, NPC.linus, 3, {Fish.any: 1}) +spaghetti = friendship_recipe(Meal.spaghetti, NPC.lewis, 3, {Vegetable.tomato: 1, Ingredient.wheat_flour: 1}) +stir_fry_ingredients = {Forageable.cave_carrot: 1, Forageable.common_mushroom: 1, Vegetable.kale: 1, Ingredient.sugar: 1} +stir_fry_qos = queen_of_sauce_recipe(Meal.stir_fry, 1, Season.spring, 7, stir_fry_ingredients) +stuffing = friendship_recipe(Meal.stuffing, NPC.pam, 7, {Meal.bread: 1, Fruit.cranberries: 1, Forageable.hazelnut: 1}) +survival_burger = skill_recipe(Meal.survival_burger, Skill.foraging, 2, {Meal.bread: 1, Forageable.cave_carrot: 1, Vegetable.eggplant: 1}) +tortilla_ingredients = {Vegetable.corn: 1} +tortilla_qos = queen_of_sauce_recipe(Meal.tortilla, 1, Season.fall, 7, tortilla_ingredients) +tortilla_saloon = shop_recipe(Meal.tortilla, Region.saloon, 100, tortilla_ingredients) +triple_shot_espresso = shop_recipe(Beverage.triple_shot_espresso, Region.saloon, 5000, {Beverage.coffee: 3}) +tropical_curry = shop_recipe(Meal.tropical_curry, Region.island_resort, 2000, {Forageable.coconut: 1, Fruit.pineapple: 1, Fruit.hot_pepper: 1}) +vegetable_medley = friendship_recipe(Meal.vegetable_medley, NPC.caroline, 7, {Vegetable.tomato: 1, Vegetable.beet: 1}) + + + + + + + diff --git a/worlds/stardew_valley/data/resource_packs.csv b/worlds/stardew_valley/data/resource_packs.csv deleted file mode 100644 index 0508ee35..00000000 --- a/worlds/stardew_valley/data/resource_packs.csv +++ /dev/null @@ -1,39 +0,0 @@ -name,default_amount,scaling_factor,classification,groups -Money,1000,500,useful,BASE_RESOURCE -Stone,50,25,filler,BASE_RESOURCE -Wood,50,25,filler,BASE_RESOURCE -Hardwood,10,5,useful,BASE_RESOURCE -Fiber,30,15,filler,BASE_RESOURCE -Coal,10,5,filler,BASE_RESOURCE -Clay,10,5,filler,BASE_RESOURCE -Warp Totem: Beach,5,2,filler,WARP_TOTEM -Warp Totem: Desert,5,2,filler,WARP_TOTEM -Warp Totem: Farm,5,2,filler,WARP_TOTEM -Warp Totem: Island,5,2,filler,WARP_TOTEM -Warp Totem: Mountains,5,2,filler,WARP_TOTEM -Geode,12,6,filler,GEODE -Frozen Geode,8,4,filler,GEODE -Magma Geode,6,3,filler,GEODE -Omni Geode,4,2,useful,GEODE -Copper Ore,75,25,filler,ORE -Iron Ore,50,25,filler,ORE -Gold Ore,25,13,useful,ORE -Iridium Ore,10,5,useful,ORE -Quartz,10,5,filler,ORE -Basic Fertilizer,30,10,filler,FERTILIZER -Basic Retaining Soil,30,10,filler,FERTILIZER -Speed-Gro,30,10,filler,FERTILIZER -Quality Fertilizer,20,8,filler,FERTILIZER -Quality Retaining Soil,20,8,filler,FERTILIZER -Deluxe Speed-Gro,20,8,filler,FERTILIZER -Deluxe Fertilizer,10,4,useful,FERTILIZER -Deluxe Retaining Soil,10,4,useful,FERTILIZER -Hyper Speed-Gro,10,4,useful,FERTILIZER -Tree Fertilizer,10,4,filler,FERTILIZER -Spring Seeds,30,10,filler,SEED -Summer Seeds,30,10,filler,SEED -Fall Seeds,30,10,filler,SEED -Winter Seeds,30,10,filler,SEED -Mahogany Seed,5,2,filler,SEED -Bait,30,10,filler,FISHING_RESOURCE -Crab Pot,3,1,filler,FISHING_RESOURCE \ No newline at end of file diff --git a/worlds/stardew_valley/data/villagers_data.py b/worlds/stardew_valley/data/villagers_data.py index 14582d25..330d5eb9 100644 --- a/worlds/stardew_valley/data/villagers_data.py +++ b/worlds/stardew_valley/data/villagers_data.py @@ -1,6 +1,9 @@ from dataclasses import dataclass -from typing import Set, List, FrozenSet, Tuple -from .region_data import SVRegion +from typing import List, Tuple, Optional, Dict +from ..strings.region_names import Region +from ..mods.mod_data import ModNames +from ..strings.season_names import Season +from ..strings.villager_names import NPC, ModNPC @dataclass(frozen=True) @@ -11,28 +14,31 @@ class Villager: birthday: str gifts: Tuple[str] available: bool + mod_name: Optional[str] def __repr__(self): return f"{self.name} [Bachelor: {self.bachelor}] [Available from start: {self.available}]" \ f"(Locations: {self.locations} |" \ f" Birthday: {self.birthday} |" \ - f" Gifts: {self.gifts}) " + f" Gifts: {self.gifts}) |" \ + f" Mod: {self.mod_name}" -town = (SVRegion.town,) -beach = (SVRegion.beach,) -forest = (SVRegion.forest,) -mountain = (SVRegion.mountain,) -hospital = (SVRegion.hospital,) -carpenter = (SVRegion.carpenter,) -alex_house = (SVRegion.alex_house,) -elliott_house = (SVRegion.elliott_house,) -ranch = (SVRegion.ranch,) -mines = (SVRegion.mines,) -desert = (SVRegion.desert,) -oasis = (SVRegion.desert,) -sewers = (SVRegion.sewers,) -island = (SVRegion.ginger_island,) +town = (Region.town,) +beach = (Region.beach,) +forest = (Region.forest,) +mountain = (Region.mountain,) +hospital = (Region.hospital,) +carpenter = (Region.carpenter,) +alex_house = (Region.alex_house,) +elliott_house = (Region.elliott_house,) +ranch = (Region.ranch,) +mines_dwarf_shop = (Region.mines_dwarf_shop,) +desert = (Region.desert,) +oasis = (Region.oasis,) +sewers = (Region.sewer,) +island = (Region.island_east,) +secret_woods = (Region.secret_woods,) golden_pumpkin = ("Golden Pumpkin",) # magic_rock_candy = ("Magic Rock Candy",) @@ -202,49 +208,138 @@ super_cucumber = ("Super Cucumber",) void_essence = ("Void Essence",) wizard_loves = purple_mushroom + solar_essence + super_cucumber + void_essence +#Custom NPC Items and Loves + +blueberry = ("Blueberry",) +chanterelle = ("Chanterelle",) +garlic = ("Garlic",) +omelet = ("Omelet",) +wild_plum = ("Wild Plum",) +rhubarb = ("Rhubarb",) +fried_mushroom = ("Fried Mushroom",) +eggplant_parmesan = ("Eggplant Parmesan",) +maki_roll = ("Maki Roll",) +red_plate = ("Red Plate",) +baked_fish = ("Baked Fish",) +cheese = ("Cheese",) +eel = ("Eel",) +flounder = ("Flounder",) +salmon = ("Salmon",) +sashimi = ("Sashimi",) +tuna = ("Tuna",) +energy_tonic = ("Energy Tonic",) +kale = ("Kale",) +muscle_remedy = ("Muscle Remedy",) +vegetable_medley = ("Vegetable Medley",) +trilobite = ("Trilobite",) +golden_mask = ("Golden Mask",) +rainbow_shell = ("Rainbow Shell",) +blue_jazz = ("Blue Jazz",) +honey = ("Honey",) +apple = ("Apple",) +dwarf_gadget = ("Dwarf Gadget",) +dwarvish_helm = ("Dwarvish Helm",) +fire_quartz = ("Fire Quartz",) +jasper = ("Jasper",) +opal = ("Opal",) +rare_disc = ("Rare Disc",) +ancient_doll = ("Ancient Doll",) +elvish_jewelry = ("Elvish Jewelry",) +dinosaur_egg = ("Dinosaur Egg",) +strange_doll = ("Strange Doll",) +joja_cola = ("Joja Cola",) +hashbrowns = ("Hashbrowns",) +jelly = ("Jelly",) +ghost_crystal = ("Ghost Crystal",) +prehistoric_scapula = ("Prehistoric Scapula",) +cherry = ("Cherry",) +golden_relic = ("Golden Relic",) + +ayeisha_loves = blackberry_cobbler + blueberry + chanterelle + emerald + omelet + sweet_pea + wild_plum + rhubarb + \ + fried_mushroom + eggplant_parmesan +shiko_loves = maki_roll + red_plate + ruby + salad + wine +wellwick_loves = fairy_rose + solar_essence + void_essence + wine +mister_ginger_loves = baked_fish + cheese + eel + flounder + goat_cheese + lobster + salmon + sashimi + tuna +delores_loves = aquamarine + blueberry + energy_tonic + green_tea + kale + muscle_remedy + red_plate + \ + roots_platter + salad + vegetable_medley +yoba_loves = golden_mask + rainbow_shell +eugene_loves = blue_jazz + fairy_rose + green_tea + honey + poppy + poppyseed_muffin + \ + salad + summer_spangle + sunflower + tulip +jasper_loves = apple + blueberry + diamond + dwarf_gadget + dwarvish_helm + fire_quartz + jasper + \ + miners_treat + opal + rare_disc +juna_loves = ancient_doll + elvish_jewelry + dinosaur_egg + strange_doll + joja_cola + hashbrowns + pancakes + \ + pink_cake + jelly + ghost_crystal + prehistoric_scapula + cherry + + all_villagers: List[Villager] = [] def villager(name: str, bachelor: bool, locations: Tuple[str, ...], birthday: str, gifts: Tuple[str, ...], - available: bool) -> Villager: - npc = Villager(name, bachelor, locations, birthday, gifts, available) + available: bool, mod_name: Optional[str] = None) -> Villager: + npc = Villager(name, bachelor, locations, birthday, gifts, available, mod_name) all_villagers.append(npc) return npc -josh = villager("Alex", True, town + alex_house, "Summer", universal_loves + complete_breakfast + salmon_dinner, True) -elliott = villager("Elliott", True, town + beach + elliott_house, "Fall", universal_loves + elliott_loves, True) -harvey = villager("Harvey", True, town + hospital, "Winter", universal_loves + harvey_loves, True) -sam = villager("Sam", True, town, "Summer", universal_loves + sam_loves, True) -sebastian = villager("Sebastian", True, carpenter, "Winter", universal_loves + sebastian_loves, True) -shane = villager("Shane", True, ranch, "Spring", universal_loves + shane_loves, True) -best_girl = villager("Abigail", True, town, "Fall", universal_loves + abigail_loves, True) -emily = villager("Emily", True, town, "Spring", universal_loves + emily_loves, True) -hoe = villager("Haley", True, town, "Spring", universal_loves_no_prismatic_shard + haley_loves, True) -leah = villager("Leah", True, forest, "Winter", universal_loves + leah_loves, True) -nerd = villager("Maru", True, carpenter, "Summer", universal_loves + maru_loves, True) -penny = villager("Penny", True, town, "Fall", universal_loves_no_rabbit_foot + penny_loves, True) -caroline = villager("Caroline", False, town, "Winter", universal_loves + caroline_loves, True) -clint = villager("Clint", False, town, "Winter", universal_loves + clint_loves, True) -demetrius = villager("Demetrius", False, carpenter, "Summer", universal_loves + demetrius_loves, True) -dwarf = villager("Dwarf", False, mines, "Summer", universal_loves + dwarf_loves, False) -gilf = villager("Evelyn", False, town, "Winter", universal_loves + evelyn_loves, True) -boomer = villager("George", False, town, "Fall", universal_loves + george_loves, True) -gus = villager("Gus", False, town, "Summer", universal_loves + gus_loves, True) -jas = villager("Jas", False, ranch, "Summer", universal_loves + jas_loves, True) -jodi = villager("Jodi", False, town, "Fall", universal_loves + jodi_loves, True) -kent = villager("Kent", False, town, "Spring", universal_loves + kent_loves, False) -krobus = villager("Krobus", False, sewers, "Winter", universal_loves + krobus_loves, False) -leo = villager("Leo", False, island, "Summer", universal_loves + leo_loves, False) -lewis = villager("Lewis", False, town, "Spring", universal_loves + lewis_loves, True) -linus = villager("Linus", False, mountain, "Winter", universal_loves + linus_loves, True) -marnie = villager("Marnie", False, ranch, "Fall", universal_loves + marnie_loves, True) -pam = villager("Pam", False, town, "Spring", universal_loves + pam_loves, True) -pierre = villager("Pierre", False, town, "Spring", universal_loves + pierre_loves, True) -milf = villager("Robin", False, carpenter, "Fall", universal_loves + robin_loves, True) -sandy = villager("Sandy", False, oasis, "Fall", universal_loves + sandy_loves, False) -vincent = villager("Vincent", False, town, "Spring", universal_loves + vincent_loves, True) -willy = villager("Willy", False, beach, "Summer", universal_loves + willy_loves, True) -wizard = villager("Wizard", False, forest, "Winter", universal_loves + wizard_loves, True) +josh = villager(NPC.alex, True, town + alex_house, Season.summer, universal_loves + complete_breakfast + salmon_dinner, True) +elliott = villager(NPC.elliott, True, town + beach + elliott_house, Season.fall, universal_loves + elliott_loves, True) +harvey = villager(NPC.harvey, True, town + hospital, Season.winter, universal_loves + harvey_loves, True) +sam = villager(NPC.sam, True, town, Season.summer, universal_loves + sam_loves, True) +sebastian = villager(NPC.sebastian, True, carpenter, Season.winter, universal_loves + sebastian_loves, True) +shane = villager(NPC.shane, True, ranch, Season.spring, universal_loves + shane_loves, True) +best_girl = villager(NPC.abigail, True, town, Season.fall, universal_loves + abigail_loves, True) +emily = villager(NPC.emily, True, town, Season.spring, universal_loves + emily_loves, True) +hoe = villager(NPC.haley, True, town, Season.spring, universal_loves_no_prismatic_shard + haley_loves, True) +leah = villager(NPC.leah, True, forest, Season.winter, universal_loves + leah_loves, True) +nerd = villager(NPC.maru, True, carpenter + hospital + town, Season.summer, universal_loves + maru_loves, True) +penny = villager(NPC.penny, True, town, Season.fall, universal_loves_no_rabbit_foot + penny_loves, True) +caroline = villager(NPC.caroline, False, town, Season.winter, universal_loves + caroline_loves, True) +clint = villager(NPC.clint, False, town, Season.winter, universal_loves + clint_loves, True) +demetrius = villager(NPC.demetrius, False, carpenter, Season.summer, universal_loves + demetrius_loves, True) +dwarf = villager(NPC.dwarf, False, mines_dwarf_shop, Season.summer, universal_loves + dwarf_loves, False) +gilf = villager(NPC.evelyn, False, town, Season.winter, universal_loves + evelyn_loves, True) +boomer = villager(NPC.george, False, town, Season.fall, universal_loves + george_loves, True) +gus = villager(NPC.gus, False, town, Season.summer, universal_loves + gus_loves, True) +jas = villager(NPC.jas, False, ranch, Season.summer, universal_loves + jas_loves, True) +jodi = villager(NPC.jodi, False, town, Season.fall, universal_loves + jodi_loves, True) +kent = villager(NPC.kent, False, town, Season.spring, universal_loves + kent_loves, False) +krobus = villager(NPC.krobus, False, sewers, Season.winter, universal_loves + krobus_loves, False) +leo = villager(NPC.leo, False, island, Season.summer, universal_loves + leo_loves, False) +lewis = villager(NPC.lewis, False, town, Season.spring, universal_loves + lewis_loves, True) +linus = villager(NPC.linus, False, mountain, Season.winter, universal_loves + linus_loves, True) +marnie = villager(NPC.marnie, False, ranch, Season.fall, universal_loves + marnie_loves, True) +pam = villager(NPC.pam, False, town, Season.spring, universal_loves + pam_loves, True) +pierre = villager(NPC.pierre, False, town, Season.spring, universal_loves + pierre_loves, True) +milf = villager(NPC.robin, False, carpenter, Season.fall, universal_loves + robin_loves, True) +sandy = villager(NPC.sandy, False, oasis, Season.fall, universal_loves + sandy_loves, False) +vincent = villager(NPC.vincent, False, town, Season.spring, universal_loves + vincent_loves, True) +willy = villager(NPC.willy, False, beach, Season.summer, universal_loves + willy_loves, True) +wizard = villager(NPC.wizard, False, forest, Season.winter, universal_loves + wizard_loves, True) + +# Custom NPCs +alec = villager(ModNPC.alec, True, forest, Season.winter, universal_loves + trilobite, True, ModNames.alec) +ayeisha = villager(ModNPC.ayeisha, False, town, Season.summer, universal_loves + ayeisha_loves, True, ModNames.ayeisha) +delores = villager(ModNPC.delores, True, forest, Season.winter, universal_loves + delores_loves, True, ModNames.delores) +eugene = villager(ModNPC.eugene, True, forest, Season.spring, universal_loves + eugene_loves, True, ModNames.eugene) +jasper = villager(ModNPC.jasper, True, town, Season.fall, universal_loves + jasper_loves, True, ModNames.jasper) +juna = villager(ModNPC.juna, False, forest, Season.summer, universal_loves + juna_loves, True, ModNames.juna) +kitty = villager(ModNPC.mr_ginger, False, forest, Season.summer, universal_loves + mister_ginger_loves, True, ModNames.ginger) +shiko = villager(ModNPC.shiko, True, town, Season.winter, universal_loves + shiko_loves, True, ModNames.shiko) +wellwick = villager(ModNPC.wellwick, True, forest, Season.winter, universal_loves + wellwick_loves, True, ModNames.shiko) +yoba = villager(ModNPC.yoba, False, secret_woods, Season.spring, universal_loves + yoba_loves, False, ModNames.yoba) +riley = villager(ModNPC.riley, True, town, Season.spring, universal_loves, True, ModNames.riley) + +all_villagers_by_name: Dict[str, Villager] = {villager.name: villager for villager in all_villagers} +all_villagers_by_mod: Dict[str, List[Villager]] = {} +all_villagers_by_mod_by_name: Dict[str, Dict[str, Villager]] = {} +for npc in all_villagers: + mod = npc.mod_name + name = npc.name + if mod in all_villagers_by_mod: + all_villagers_by_mod[mod].append(npc) + all_villagers_by_mod_by_name[mod][name] = npc + else: + all_villagers_by_mod[mod] = [npc] + all_villagers_by_mod_by_name[mod] = {} + all_villagers_by_mod_by_name[mod][name] = npc -all_villagers_by_name = {item.name: item for item in all_villagers} diff --git a/worlds/stardew_valley/docs/en_Stardew Valley.md b/worlds/stardew_valley/docs/en_Stardew Valley.md index a8aa098b..b460096e 100644 --- a/worlds/stardew_valley/docs/en_Stardew Valley.md +++ b/worlds/stardew_valley/docs/en_Stardew Valley.md @@ -9,27 +9,29 @@ config file. A vast number of optional objectives in stardew valley can be shuffled around the multiworld. Most of these are optional, and the player can customize their experience in their YAML file. -For these objectives, if they have a vanilla reward, this reward will instead be an item in the multiworld. For the remaining number of such objectives, there are a number of "Resource Pack" items, which are simply a stack of an item that may be useful to the player. +For these objectives, if they have a vanilla reward, this reward will instead be an item in the multiworld. For the remaining number of such objectives, there are a number of "Resource Pack" items, which are simply an item or a stack of items that may be useful to the player. ## What is the goal of Stardew Valley? The player can choose from a number of goals, using their YAML settings. -- Complete the Community Center -- Succeed Grandpa's Evaluation with 4 lit candles -- Reach the bottom of the Pelican Town Mineshaft -- Complete the "Cryptic Note" quest, by meeting Mr Qi on floor 100 of the Skull Cavern -- Get the achievement "Master Angler", which requires catching every fish in the game -- Get the achievement "A Complete Collection", which requires donating all the artifacts and minerals to the museum -- Get the achievement "Full House", which requires getting married and having two kids. +- Complete the [Community Center](https://stardewvalleywiki.com/Bundles) +- Succeed [Grandpa's Evaluation](https://stardewvalleywiki.com/Grandpa) with 4 lit candles +- Reach the bottom of the [Pelican Town Mineshaft](https://stardewvalleywiki.com/The_Mines) +- Complete the [Cryptic Note](https://stardewvalleywiki.com/Secret_Notes#Secret_Note_.2310) quest, by meeting Mr Qi on floor 100 of the Skull Cavern +- Get the achievement [Master Angler](https://stardewvalleywiki.com/Fish), which requires catching every fish in the game +- Get the achievement [A Complete Collection](https://stardewvalleywiki.com/Museum), which requires donating all the artifacts and minerals to the museum +- Get the achievement [Full House](https://stardewvalleywiki.com/Children), which requires getting married and having two kids. +- Get recognized as the [Greatest Walnut Hunter](https://stardewvalleywiki.com/Golden_Walnut) by Mr Qi, which requires finding all 130 golden walnuts on ginger island +- Achieve [Perfection](https://stardewvalleywiki.com/Perfection) in your save file ## What are location check in Stardew Valley? Location checks in Stardew Valley always include: -- Community Center Bundles -- Mineshaft chest rewards -- Story Quests -- Traveling Merchant items -- Isolated objectives such as the beach bridge, Old Master Cannoli, Grim Reaper Statue, etc +- [Community Center Bundles](https://stardewvalleywiki.com/Bundles) +- [Mineshaft chest rewards](https://stardewvalleywiki.com/The_Mines#Remixed_Rewards) +- [Story Quests](https://stardewvalleywiki.com/Quests#List_of_Story_Quests) +- [Traveling Merchant items](https://stardewvalleywiki.com/Traveling_Cart) +- Isolated objectives such as the [beach bridge](https://stardewvalleywiki.com/The_Beach#Tide_Pools), [Old Master Cannoli](https://stardewvalleywiki.com/Secret_Woods#Old_Master_Cannoli), [Grim Reaper Statue](https://stardewvalleywiki.com/Golden_Scythe), etc There also are a number of location checks that are optional, and individual players choose to include them or not in their shuffling: - Tools and Fishing Rod Upgrades @@ -39,6 +41,9 @@ There also are a number of location checks that are optional, and individual pla - Skill Levels - Arcade Machines - Help Wanted quests +- Participating in Festivals +- Special Orders from the town board, or from Mr Qi +- Cropsanity: Growing and harvesting individual crop types - Fishsanity: Catching individual fish - Museumsanity: Donating individual items to the museum, or reaching the museum milestones for donations - Friendsanity: Reaching specific friendship levels with NPCs @@ -46,22 +51,24 @@ There also are a number of location checks that are optional, and individual pla ## Which items can be in another player's world? Every normal reward from the above locations can be in another player's world. -For the locations which do not include a normal reward, Resource Packs are instead added to the pool. These can contain ores, seeds, fertilizers, warp totems, etc. +For the locations which do not include a normal reward, Resource Packs and traps are instead added to the pool. Traps are optional. A player can enable some settings that will add some items to the pool that are relevant to progression - Seasons Randomizer: - All 4 seasons will be items, and one of them will be selected randomly and be added to the player's start inventory - At the end of each month, the player can choose the next season, instead of following the vanilla season order. On Seasons Randomizer, they can only choose from the seasons they have received. -- Seed Shuffle: - - Every single seed in the game starts off locked and cannot be purchased from any merchant. Their unlocks are received as multiworld items. +- Cropsanity: + - Every single seed in the game starts off locked and cannot be purchased from any merchant. Their unlocks are received as multiworld items. Growing each seed and harvesting the resulting crop sends a location check - The way merchants sell seeds is considerably changed. Pierre sells fewer seeds at a high price, while Joja sells unlimited seeds but in huge discount packs, not individually. - Museumsanity: - The items that are normally obtained from museum donation milestones are added to the item pool. Some items, like the magic rock candy, are duplicated for convenience. - The Traveling Merchant now sells artifacts and minerals, with a bias towards undonated ones, to mitigate randomness. She will sell these items as the player receives "Traveling Merchant Metal Detector" items. +- TV Channels +- Babies There are a few extra vanilla items, which are added to the pool for convenience, but do not have a matching location. These include -- Wizard Buildings -- Return Scepter +- [Wizard Buildings](https://stardewvalleywiki.com/Wizard%27s_Tower#Buildings) +- [Return Scepter](https://stardewvalleywiki.com/Return_Scepter) And lastly, some Archipelago-exclusive items exist in the pool, which are designed around game balance and QoL. These include: - Arcade Machine buffs (Only if the arcade machines are randomized) @@ -80,6 +87,39 @@ Some items will be directly attached to the letter, while some others will inste In some cases, like receiving Carpenter and Wizard buildings, the player will still need to go ask Robin to construct the building that they have received, so they can choose its position. This construction will be completely free. +## Mods + +Starting in version 4.x.x, some Stardew Valley mods unrelated to Archipelago are officially "supported". +This means that, for these specific mods, if you decide to include them in your yaml settings, the multiworld will be generated with the assumption that you will install and play with these mods. +The multiworld will contain related items and locations for these mods, the specifics will vary from mod to mod + +List of supported mods: + +- Skills + - [Luck Skill](https://www.nexusmods.com/stardewvalley/mods/521) + - [Magic](https://www.nexusmods.com/stardewvalley/mods/2007) + - [Socializing Skill](https://www.nexusmods.com/stardewvalley/mods/14142) + - [Archaeology](https://www.nexusmods.com/stardewvalley/mods/15793) + - [Cooking Skill](https://www.nexusmods.com/stardewvalley/mods/522) + - [Binning Skill](https://www.nexusmods.com/stardewvalley/mods/14073) +- NPCs + - [Ayeisha - The Postal Worker (Custom NPC)](https://www.nexusmods.com/stardewvalley/mods/6427) + - [Mister Ginger (cat npc)](https://www.nexusmods.com/stardewvalley/mods/5295) + - [Juna - Roommate NPC](https://www.nexusmods.com/stardewvalley/mods/8606) + - [Professor Jasper Thomas](https://www.nexusmods.com/stardewvalley/mods/5599) + - [Alec Revisited](https://www.nexusmods.com/stardewvalley/mods/10697) + - [Custom NPC - Yoba](https://www.nexusmods.com/stardewvalley/mods/14871) + - [Custom NPC Eugene](https://www.nexusmods.com/stardewvalley/mods/9222) + - ['Prophet' Wellwick](https://www.nexusmods.com/stardewvalley/mods/6462) + - [Shiko - New Custom NPC](https://www.nexusmods.com/stardewvalley/mods/3732) + - [Delores - Custom NPC](https://www.nexusmods.com/stardewvalley/mods/5510) + - [Custom NPC - Riley](https://www.nexusmods.com/stardewvalley/mods/5811) +- Other + - [DeepWoods](https://www.nexusmods.com/stardewvalley/mods/2571) + - [Tractor Mod](https://www.nexusmods.com/stardewvalley/mods/1401) + - [Bigger Backpack](https://www.nexusmods.com/stardewvalley/mods/1845) + - [Skull Cavern Elevator](https://www.nexusmods.com/stardewvalley/mods/963) + ## Multiplayer You cannot play an Archipelago Slot in multiplayer at the moment. There is no short-terms plans to support that feature. diff --git a/worlds/stardew_valley/docs/setup_en.md b/worlds/stardew_valley/docs/setup_en.md index 722c2db0..19736697 100644 --- a/worlds/stardew_valley/docs/setup_en.md +++ b/worlds/stardew_valley/docs/setup_en.md @@ -4,15 +4,16 @@ - Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/)) - SMAPI ([Mod loader for Stardew Valley](https://smapi.io/)) -- [StardewArchipelago Mod Release 3.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) - - It is important to use a mod release of version 3.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet. +- [StardewArchipelago Mod Release 4.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) + - It is important to use a mod release of version 4.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet. ## Optional Software - Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) - (Only for the TextClient) - Other Stardew Valley Mods [Nexus Mods](https://www.nexusmods.com/stardewvalley) - - It is **not** recommended to further mod Stardew Valley, altough it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs. - - The more mods you have, and the bigger they are, the more likely things are to break. + - For Supported mods (see related section in this page), it is recommend to install them from the mods archive available with the StardewArchipelago mod release + - It is **not** recommended to further mod Stardew Valley with unsupported mods, altough it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs. + - The more unsupported mods you have, and the bigger they are, the more likely things are to break. ## Configuring your YAML file @@ -76,6 +77,46 @@ Lastly, you can also run Archipelago commands `!help` from the in game chat box, It is important to note that the Stardew Valley chat is fairly limited in its capabilities. For example, it doesn't allow scrolling up to see history that has been pushed off screen. The SMAPI console running alonside your game will have the full history as well and may be better suited to read older messages. For a better chat experience, you can also use the official Archipelago Text Client, altough it will not allow you to run Stardew-exclusive commands. +### Playing with supported mods + +To include supported mods in your multiworld slot, you need to include a section in your yaml settings called "mods". +This section must be an array with the **exact** names of every mod you wish to include. Any improperly typed mod name will be ignored. +![image](https://i.imgur.com/uOHtXmU.png) + +These mods will then be included in the multiworld generation, and considered in logic. For example, the Magic mod includes a spell that allow a player to teleport, and, if included, teleporting can be required to reach checks. + +Furthermore, as mod development can be unpredictable, the generator and the StardewArchipelago client are designed and tested for a very specific version of any supported mod. When installing them, you must choose the correct version, or you will not be able to play. + +A Zip archive of **every supported mod** is included in the [StardewArchipelago Mod Releases](https://github.com/agilbert1412/StardewArchipelago/releases) alongside the main mod, which should all have the correct versions available. The archive also contains recommended configs for customizable mods. + +The archive also contains every dependency for these mods, but dependency versions are less strict. + +If you can load the supported mod on the correct version, the exact version of a dependency is not important. + +#### All supported mod exact names and required versions: + - "DeepWoods" -> 3.0.0-beta + - "Tractor Mod" -> 4.16.4 + - "Bigger Backpack" -> 6.0.0 + - "Skull Cavern Elevator" -> 1.5.0 + - "Luck Skill" -> 1.2.4 + - "Magic" -> 0.8.2 + - "Socializing Skill" -> 1.1.5 + - "Archaeology" -> 1.5.0 + - "Cooking Skill" -> 1.4.5 + - "Binning Skill" -> 1.2.7 + - "Ayeisha - The Postal Worker (Custom NPC)" -> 0.5.0-alpha + - "Mister Ginger (cat npc)" -> 1.5.9 + - "Juna - Roommate NPC" -> 2.1.3 + - "Professor Jasper Thomas" -> 1.7.6 + - "Alec Revisited" -> 2.1.0 + - "Custom NPC - Yoba" -> 1.0.0 + - "Custom NPC Eugene" -> 1.3.1 + - "'Prophet' Wellwick" -> 1.0.0 + - "Shiko - New Custom NPC" -> 1.1.0 + - "Delores - Custom NPC" -> 1.1.2 + - "Custom NPC - Riley" -> 1.2.2 + + ### Multiplayer -You cannot play an Archipelago Slot in multiplayer at the moment. There is no short-terms plans to support that feature. \ No newline at end of file +You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-terms plans to support that feature. \ No newline at end of file diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index 3f7c53b5..38435207 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -1,21 +1,17 @@ -import bisect import csv import enum -import itertools import logging -import math -import typing -from collections import OrderedDict from dataclasses import dataclass, field -from functools import cached_property from pathlib import Path from random import Random -from typing import Dict, List, Protocol, Union, Set, Optional, FrozenSet +from typing import Dict, List, Protocol, Union, Set, Optional from BaseClasses import Item, ItemClassification from . import options, data from .data.villagers_data import all_villagers +from .mods.mod_data import ModNames from .options import StardewOptions +from .strings.ap_names.buff_names import Buff ITEM_CODE_OFFSET = 717000 @@ -49,12 +45,26 @@ class Group(enum.Enum): ORE = enum.auto() FERTILIZER = enum.auto() SEED = enum.auto() - SEED_SHUFFLE = enum.auto() + CROPSANITY = enum.auto() FISHING_RESOURCE = enum.auto() SEASON = enum.auto() TRAVELING_MERCHANT_DAY = enum.auto() MUSEUM = enum.auto() FRIENDSANITY = enum.auto() + FESTIVAL = enum.auto() + RARECROW = enum.auto() + TRAP = enum.auto() + MAXIMUM_ONE = enum.auto() + EXACTLY_TWO = enum.auto + DEPRECATED = enum.auto() + RESOURCE_PACK_USEFUL = enum.auto() + SPECIAL_ORDER_BOARD = enum.auto() + SPECIAL_ORDER_QI = enum.auto() + BABY = enum.auto() + GINGER_ISLAND = enum.auto() + WALNUT_PURCHASE = enum.auto() + TV_CHANNEL = enum.auto() + MAGIC_SPELL = enum.auto() @dataclass(frozen=True) @@ -62,6 +72,7 @@ class ItemData: code_without_offset: Optional[int] name: str classification: ItemClassification + mod_name: Optional[str] = None groups: Set[Group] = field(default_factory=frozenset) def __post_init__(self): @@ -77,59 +88,6 @@ class ItemData: return bool(groups.intersection(self.groups)) -@dataclass(frozen=True) -class ResourcePackData: - name: str - default_amount: int = 1 - scaling_factor: int = 1 - classification: ItemClassification = ItemClassification.filler - groups: FrozenSet[Group] = frozenset() - - def as_item_data(self, counter: itertools.count) -> [ItemData]: - return [ItemData(next(counter), self.create_item_name(quantity), self.classification, - {Group.RESOURCE_PACK} | self.groups) - for quantity in self.scale_quantity.values()] - - def create_item_name(self, quantity: int) -> str: - return f"Resource Pack: {quantity} {self.name}" - - @cached_property - def scale_quantity(self) -> typing.OrderedDict[int, int]: - """Discrete scaling of the resource pack quantities. - 100 is default, 200 is double, 50 is half (if the scaling_factor allows it). - """ - levels = math.ceil(self.default_amount / self.scaling_factor) * 2 - first_level = self.default_amount % self.scaling_factor - if first_level == 0: - first_level = self.scaling_factor - quantities = sorted(set(range(first_level, self.scaling_factor * levels, self.scaling_factor)) - | {self.default_amount * 2}) - - return OrderedDict({round(quantity / self.default_amount * 100): quantity - for quantity in quantities - if quantity <= self.default_amount * 2}) - - def calculate_quantity(self, multiplier: int) -> int: - scales = list(self.scale_quantity) - left_scale = bisect.bisect_left(scales, multiplier) - closest_scale = min([scales[left_scale], scales[left_scale - 1]], - key=lambda x: abs(multiplier - x)) - return self.scale_quantity[closest_scale] - - def create_name_from_multiplier(self, multiplier: int) -> str: - return self.create_item_name(self.calculate_quantity(multiplier)) - - -class FriendshipPackData(ResourcePackData): - def create_item_name(self, quantity: int) -> str: - return f"Friendship Bonus ({quantity} <3)" - - def as_item_data(self, counter: itertools.count) -> [ItemData]: - item_datas = super().as_item_data(counter) - return [ItemData(item.code_without_offset, item.name, item.classification, {Group.FRIENDSHIP_PACK}) - for item in item_datas] - - class StardewItemFactory(Protocol): def __call__(self, name: Union[str, ItemData]) -> Item: raise NotImplementedError @@ -148,29 +106,11 @@ def load_item_csv(): id = int(item["id"]) if item["id"] else None classification = ItemClassification[item["classification"]] groups = {Group[group] for group in item["groups"].split(",") if group} - items.append(ItemData(id, item["name"], classification, groups)) + mod_name = str(item["mod_name"]) if item["mod_name"] else None + items.append(ItemData(id, item["name"], classification, mod_name, groups)) return items -def load_resource_pack_csv() -> List[ResourcePackData]: - try: - from importlib.resources import files - except ImportError: - from importlib_resources import files # noqa - - resource_packs = [] - with files(data).joinpath("resource_packs.csv").open() as file: - resource_pack_reader = csv.DictReader(file) - for resource_pack in resource_pack_reader: - groups = frozenset(Group[group] for group in resource_pack["groups"].split(",") if group) - resource_packs.append(ResourcePackData(resource_pack["name"], - int(resource_pack["default_amount"]), - int(resource_pack["scaling_factor"]), - ItemClassification[resource_pack["classification"]], - groups)) - return resource_packs - - events = [ ItemData(None, "Victory", ItemClassification.progression), ItemData(None, "Month End", ItemClassification.progression), @@ -193,26 +133,29 @@ def initialize_item_table(): item_table.update({item.name: item for item in all_items}) -friendship_pack = FriendshipPackData("Friendship Bonus", default_amount=2, classification=ItemClassification.useful) -all_resource_packs = load_resource_pack_csv() - initialize_item_table() initialize_groups() -def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item], world_options: StardewOptions, +def create_items(item_factory: StardewItemFactory, locations_count: int, items_to_exclude: List[Item], + world_options: StardewOptions, random: Random) -> List[Item]: - items = create_unique_items(item_factory, world_options, random) + items = [] + unique_items = create_unique_items(item_factory, world_options, random) for item in items_to_exclude: - if item in items: - items.remove(item) + if item in unique_items: + unique_items.remove(item) - assert len(items) <= locations_count, \ - "There should be at least as many locations as there are mandatory items" - logger.debug(f"Created {len(items)} unique items") + assert len(unique_items) <= locations_count, f"There should be at least as many locations [{locations_count}] as there are mandatory items [{len(unique_items)}]" + items += unique_items + logger.debug(f"Created {len(unique_items)} unique items") - resource_pack_items = fill_with_resource_packs(item_factory, world_options, random, locations_count - len(items)) + unique_filler_items = create_unique_filler_items(item_factory, world_options, random, locations_count - len(items)) + items += unique_filler_items + logger.debug(f"Created {len(unique_filler_items)} unique filler items") + + resource_pack_items = fill_with_resource_packs_and_traps(item_factory, world_options, random, items, locations_count) items += resource_pack_items logger.debug(f"Created {len(resource_pack_items)} resource packs") @@ -226,25 +169,31 @@ def create_unique_items(item_factory: StardewItemFactory, world_options: Stardew create_backpack_items(item_factory, world_options, items) create_mine_rewards(item_factory, items, random) - create_mine_elevators(item_factory, world_options, items) + create_elevators(item_factory, world_options, items) create_tools(item_factory, world_options, items) create_skills(item_factory, world_options, items) - create_wizard_buildings(item_factory, items) + create_wizard_buildings(item_factory, world_options, items) create_carpenter_buildings(item_factory, world_options, items) items.append(item_factory("Beach Bridge")) + items.append(item_factory("Dark Talisman")) + create_tv_channels(item_factory, items) create_special_quest_rewards(item_factory, items) - create_stardrops(item_factory, items) + create_stardrops(item_factory, world_options, items) create_museum_items(item_factory, world_options, items) create_arcade_machine_items(item_factory, world_options, items) items.append(item_factory(random.choice(items_by_group[Group.GALAXY_WEAPONS]))) - items.append( - item_factory(friendship_pack.create_name_from_multiplier(world_options[options.ResourcePackMultiplier]))) create_player_buffs(item_factory, world_options, items) - items.extend(create_traveling_merchant_items(item_factory)) + create_traveling_merchant_items(item_factory, items) items.append(item_factory("Return Scepter")) - items.extend(create_seasons(item_factory, world_options)) - items.extend(create_seeds(item_factory, world_options)) + create_seasons(item_factory, world_options, items) + create_seeds(item_factory, world_options, items) create_friendsanity_items(item_factory, world_options, items) + create_festival_rewards(item_factory, world_options, items) + create_babies(item_factory, items, random) + create_special_order_board_rewards(item_factory, world_options, items) + create_special_order_qi_rewards(item_factory, world_options, items) + create_walnut_purchase_rewards(item_factory, world_options, items) + create_magic_mod_spells(item_factory, world_options, items) return items @@ -253,6 +202,8 @@ def create_backpack_items(item_factory: StardewItemFactory, world_options: Stard if (world_options[options.BackpackProgression] == options.BackpackProgression.option_progressive or world_options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive): items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2) + if ModNames.big_backpack in world_options[options.Mods]: + items.append(item_factory("Progressive Backpack")) def create_mine_rewards(item_factory: StardewItemFactory, items: List[Item], random: Random): @@ -269,12 +220,15 @@ def create_mine_rewards(item_factory: StardewItemFactory, items: List[Item], ran items.append(item_factory("Skull Key")) -def create_mine_elevators(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): - if (world_options[options.TheMinesElevatorsProgression] == - options.TheMinesElevatorsProgression.option_progressive or - world_options[options.TheMinesElevatorsProgression] == - options.TheMinesElevatorsProgression.option_progressive_from_previous_floor): - items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24]) +def create_elevators(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if world_options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla: + return + + items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24]) + if ModNames.deepwoods in world_options[options.Mods]: + items.extend([item_factory(item) for item in ["Progressive Wood Obelisk Sigils"] * 10]) + if ModNames.skull_cavern_elevator in world_options[options.Mods]: + items.extend([item_factory(item) for item in ["Progressive Skull Cavern Elevator"] * 8]) def create_tools(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): @@ -285,20 +239,25 @@ def create_tools(item_factory: StardewItemFactory, world_options: StardewOptions def create_skills(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): if world_options[options.SkillProgression] == options.SkillProgression.option_progressive: - items.extend([item_factory(item) for item in items_by_group[Group.SKILL_LEVEL_UP] * 10]) + for item in items_by_group[Group.SKILL_LEVEL_UP]: + if item.mod_name not in world_options[options.Mods] and item.mod_name is not None: + continue + items.extend(item_factory(item) for item in [item.name] * 10) -def create_wizard_buildings(item_factory: StardewItemFactory, items: List[Item]): +def create_wizard_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): items.append(item_factory("Earth Obelisk")) items.append(item_factory("Water Obelisk")) items.append(item_factory("Desert Obelisk")) - items.append(item_factory("Island Obelisk")) items.append(item_factory("Junimo Hut")) items.append(item_factory("Gold Clock")) + if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false: + items.append(item_factory("Island Obelisk")) + if ModNames.deepwoods in world_options[options.Mods]: + items.append(item_factory("Woods Obelisk")) -def create_carpenter_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, - items: List[Item]): +def create_carpenter_buildings(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): if world_options[options.BuildingProgression] in {options.BuildingProgression.option_progressive, options.BuildingProgression.option_progressive_early_shipping_bin}: items.append(item_factory("Progressive Coop")) @@ -319,6 +278,8 @@ def create_carpenter_buildings(item_factory: StardewItemFactory, world_options: items.append(item_factory("Progressive House")) items.append(item_factory("Progressive House")) items.append(item_factory("Progressive House")) + if ModNames.tractor in world_options[options.Mods]: + items.append(item_factory("Tractor Garage")) def create_special_quest_rewards(item_factory: StardewItemFactory, items: List[Item]): @@ -329,9 +290,13 @@ def create_special_quest_rewards(item_factory: StardewItemFactory, items: List[I items.append(item_factory("Iridium Snake Milk")) -def create_stardrops(item_factory: StardewItemFactory, items: List[Item]): +def create_stardrops(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): items.append(item_factory("Stardrop")) # The Mines level 100 items.append(item_factory("Stardrop")) # Old Master Cannoli + if world_options[options.Fishsanity] != options.Fishsanity.option_none: + items.append(item_factory("Stardrop")) #Master Angler Stardrop + if ModNames.deepwoods in world_options[options.Mods]: + items.append(item_factory("Stardrop")) # Petting the Unicorn def create_museum_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): @@ -352,24 +317,40 @@ def create_friendsanity_items(item_factory: StardewItemFactory, world_options: S exclude_non_bachelors = world_options[options.Friendsanity] == options.Friendsanity.option_bachelors exclude_locked_villagers = world_options[options.Friendsanity] == options.Friendsanity.option_starting_npcs or \ world_options[options.Friendsanity] == options.Friendsanity.option_bachelors - exclude_post_marriage_hearts = world_options[options.Friendsanity] != options.Friendsanity.option_all_with_marriage + include_post_marriage_hearts = world_options[options.Friendsanity] == options.Friendsanity.option_all_with_marriage + exclude_ginger_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true + heart_size = world_options[options.FriendsanityHeartSize] for villager in all_villagers: + if villager.mod_name not in world_options[options.Mods] and villager.mod_name is not None: + continue if not villager.available and exclude_locked_villagers: continue if not villager.bachelor and exclude_non_bachelors: continue + if villager.name == "Leo" and exclude_ginger_island: + continue + heart_cap = 8 if villager.bachelor else 10 + if include_post_marriage_hearts and villager.bachelor: + heart_cap = 14 for heart in range(1, 15): - if villager.bachelor and exclude_post_marriage_hearts and heart > 8: - continue - if villager.bachelor or heart < 11: - items.append(item_factory(f"{villager.name}: 1 <3")) + if heart > heart_cap: + break + if heart % heart_size == 0 or heart == heart_cap: + items.append(item_factory(f"{villager.name} <3")) if not exclude_non_bachelors: for heart in range(1, 6): - items.append(item_factory(f"Pet: 1 <3")) + if heart % heart_size == 0 or heart == 5: + items.append(item_factory(f"Pet <3")) -def create_arcade_machine_items(item_factory: StardewItemFactory, world_options: StardewOptions, - items: List[Item]): +def create_babies(item_factory: StardewItemFactory, items: List[Item], random: Random): + baby_items = [item for item in items_by_group[Group.BABY]] + for i in range(2): + chosen_baby = random.choice(baby_items) + items.append(item_factory(chosen_baby)) + + +def create_arcade_machine_items(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling: items.append(item_factory("JotPK: Progressive Boots")) items.append(item_factory("JotPK: Progressive Boots")) @@ -387,47 +368,160 @@ def create_arcade_machine_items(item_factory: StardewItemFactory, world_options: def create_player_buffs(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]): - number_of_buffs: int = world_options[options.NumberOfPlayerBuffs] - items.extend(item_factory(item) for item in ["Movement Speed Bonus"] * number_of_buffs) - items.extend(item_factory(item) for item in ["Luck Bonus"] * number_of_buffs) + number_of_movement_buffs: int = world_options[options.NumberOfMovementBuffs] + number_of_luck_buffs: int = world_options[options.NumberOfLuckBuffs] + items.extend(item_factory(item) for item in [Buff.movement] * number_of_movement_buffs) + items.extend(item_factory(item) for item in [Buff.luck] * number_of_luck_buffs) -def create_traveling_merchant_items(item_factory: StardewItemFactory) -> List[Item]: - return [ - *(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]), - *(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6), - *(item_factory(item) for item in ["Traveling Merchant Discount"] * 8), - ] +def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]): + items.extend([*(item_factory(item) for item in items_by_group[Group.TRAVELING_MERCHANT_DAY]), + *(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6), + *(item_factory(item) for item in ["Traveling Merchant Discount"] * 8)]) -def create_seasons(item_factory: StardewItemFactory, world_options: StardewOptions) -> List[Item]: +def create_seasons(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): if world_options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled: - return [] + return if world_options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive: - return [item_factory(item) for item in ["Progressive Season"] * 3] + items.extend([item_factory(item) for item in ["Progressive Season"] * 3]) + return - return [item_factory(item) for item in items_by_group[Group.SEASON]] + items.extend([item_factory(item) for item in items_by_group[Group.SEASON]]) -def create_seeds(item_factory: StardewItemFactory, world_options: StardewOptions) -> List[Item]: - if world_options[options.SeedShuffle] == options.SeedShuffle.option_disabled: +def create_seeds(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if world_options[options.Cropsanity] == options.Cropsanity.option_disabled: + return + + include_ginger_island = world_options[options.ExcludeGingerIsland] != options.ExcludeGingerIsland.option_true + seed_items = [item_factory(item) for item in items_by_group[Group.CROPSANITY] if include_ginger_island or Group.GINGER_ISLAND not in item.groups] + items.extend(seed_items) + + +def create_festival_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if world_options[options.FestivalLocations] == options.FestivalLocations.option_disabled: + return + + items.extend([*[item_factory(item) for item in items_by_group[Group.FESTIVAL] if item.classification != ItemClassification.filler], + item_factory("Stardrop")]) + + +def create_walnut_purchase_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + return + + items.extend([item_factory("Boat Repair"), + item_factory("Open Professor Snail Cave"), + item_factory("Ostrich Incubator Recipe"), + *[item_factory(item) for item in items_by_group[Group.WALNUT_PURCHASE]]]) + + + +def create_special_order_board_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled: + return + + items.extend([item_factory(item) for item in items_by_group[Group.SPECIAL_ORDER_BOARD]]) + + +def create_special_order_qi_rewards(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if (world_options[options.SpecialOrderLocations] != options.SpecialOrderLocations.option_board_qi or + world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true): + return + qi_gem_rewards = ["100 Qi Gems", "10 Qi Gems", "40 Qi Gems", "25 Qi Gems", "25 Qi Gems", + "40 Qi Gems", "20 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems"] + qi_gem_items = [item_factory(reward) for reward in qi_gem_rewards] + items.extend(qi_gem_items) + + +def create_tv_channels(item_factory: StardewItemFactory, items: List[Item]): + items.extend([item_factory(item) for item in items_by_group[Group.TV_CHANNEL]]) + + +def create_filler_festival_rewards(item_factory: StardewItemFactory, world_options: StardewOptions) -> List[Item]: + if world_options[options.FestivalLocations] == options.FestivalLocations.option_disabled: return [] - return [item_factory(item) for item in items_by_group[Group.SEED_SHUFFLE]] + return [item_factory(item) for item in items_by_group[Group.FESTIVAL] if + item.classification == ItemClassification.filler] -def fill_with_resource_packs(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random, - required_resource_pack: int) -> List[Item]: - resource_pack_multiplier = world_options[options.ResourcePackMultiplier] +def create_magic_mod_spells(item_factory: StardewItemFactory, world_options: StardewOptions, items: List[Item]): + if ModNames.magic not in world_options[options.Mods]: + return [] + items.extend([item_factory(item) for item in items_by_group[Group.MAGIC_SPELL]]) - if resource_pack_multiplier == 0: - return [item_factory(cola) for cola in ["Joja Cola"] * required_resource_pack] +def create_unique_filler_items(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random, + available_item_slots: int) -> List[Item]: items = [] - for i in range(required_resource_pack): - resource_pack = random.choice(all_resource_packs) - items.append(item_factory(resource_pack.create_name_from_multiplier(resource_pack_multiplier))) + items.extend(create_filler_festival_rewards(item_factory, world_options)) + + if len(items) > available_item_slots: + items = random.sample(items, available_item_slots) + return items + + +def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random, + items_already_added: List[Item], + number_locations: int) -> List[Item]: + include_traps = world_options[options.TrapItems] != options.TrapItems.option_no_traps + all_filler_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK]] + all_filler_packs.extend(items_by_group[Group.TRASH]) + if include_traps: + all_filler_packs.extend(items_by_group[Group.TRAP]) + items_already_added_names = [item.name for item in items_already_added] + useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL] + if pack.name not in items_already_added_names] + trap_items = [pack for pack in items_by_group[Group.TRAP] + if pack.name not in items_already_added_names and + (pack.mod_name is None or pack.mod_name in world_options[options.Mods])] + + priority_filler_items = [] + priority_filler_items.extend(useful_resource_packs) + if include_traps: + priority_filler_items.extend(trap_items) + + all_filler_packs = remove_excluded_packs(all_filler_packs, world_options) + priority_filler_items = remove_excluded_packs(priority_filler_items, world_options) + + number_priority_items = len(priority_filler_items) + required_resource_pack = number_locations - len(items_already_added) + if required_resource_pack < number_priority_items: + chosen_priority_items = [item_factory(resource_pack) for resource_pack in + random.sample(priority_filler_items, required_resource_pack)] + return chosen_priority_items + + items = [] + chosen_priority_items = [item_factory(resource_pack) for resource_pack in priority_filler_items] + items.extend(chosen_priority_items) + required_resource_pack -= number_priority_items + all_filler_packs = [filler_pack for filler_pack in all_filler_packs + if Group.MAXIMUM_ONE not in filler_pack.groups or + filler_pack.name not in [priority_item.name for priority_item in priority_filler_items]] + + while required_resource_pack > 0: + resource_pack = random.choice(all_filler_packs) + exactly_2 = Group.EXACTLY_TWO in resource_pack.groups + while exactly_2 and required_resource_pack == 1: + resource_pack = random.choice(all_filler_packs) + exactly_2 = Group.EXACTLY_TWO in resource_pack.groups + items.append(item_factory(resource_pack)) + required_resource_pack -= 1 + if exactly_2: + items.append(item_factory(resource_pack)) + required_resource_pack -= 1 + if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups: + all_filler_packs.remove(resource_pack) return items + + +def remove_excluded_packs(packs, world_options): + included_packs = [pack for pack in packs if Group.DEPRECATED not in pack.groups] + if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + included_packs = [pack for pack in included_packs if Group.GINGER_ISLAND not in pack.groups] + return included_packs diff --git a/worlds/stardew_valley/locations.py b/worlds/stardew_valley/locations.py index 13af203b..74b336b8 100644 --- a/worlds/stardew_valley/locations.py +++ b/worlds/stardew_valley/locations.py @@ -8,6 +8,8 @@ from . import options, data from .data.fish_data import legendary_fish, special_fish, all_fish from .data.museum_data import all_museum_items from .data.villagers_data import all_villagers +from .strings.goal_names import Goal +from .strings.region_names import Region LOCATION_CODE_OFFSET = 717000 @@ -32,7 +34,8 @@ class LocationTags(enum.Enum): TRASH_CAN_UPGRADE = enum.auto() FISHING_ROD_UPGRADE = enum.auto() THE_MINES_TREASURE = enum.auto() - THE_MINES_ELEVATOR = enum.auto() + CROPSANITY = enum.auto() + ELEVATOR = enum.auto() SKILL_LEVEL = enum.auto() FARMING_LEVEL = enum.auto() FISHING_LEVEL = enum.auto() @@ -51,6 +54,19 @@ class LocationTags(enum.Enum): MUSEUM_MILESTONES = enum.auto() MUSEUM_DONATIONS = enum.auto() FRIENDSANITY = enum.auto() + FESTIVAL = enum.auto() + FESTIVAL_HARD = enum.auto() + SPECIAL_ORDER_BOARD = enum.auto() + SPECIAL_ORDER_QI = enum.auto() + GINGER_ISLAND = enum.auto() + WALNUT_PURCHASE = enum.auto() + # Skill Mods + LUCK_LEVEL = enum.auto() + BINNING_LEVEL = enum.auto() + COOKING_LEVEL = enum.auto() + SOCIALIZING_LEVEL = enum.auto() + MAGIC_LEVEL = enum.auto() + ARCHAEOLOGY_LEVEL = enum.auto() @dataclass(frozen=True) @@ -58,6 +74,7 @@ class LocationData: code_without_offset: Optional[int] region: str name: str + mod_name: Optional[str] = None tags: FrozenSet[LocationTags] = frozenset() @property @@ -81,6 +98,7 @@ def load_location_csv() -> List[LocationData]: return [LocationData(int(location["id"]) if location["id"] else None, location["region"], location["name"], + str(location["mod_name"]) if location["mod_name"] else None, frozenset(LocationTags[group] for group in location["tags"].split(",") if group)) @@ -88,13 +106,15 @@ def load_location_csv() -> List[LocationData]: events_locations = [ - LocationData(None, "Stardew Valley", "Succeed Grandpa's Evaluation"), - LocationData(None, "Community Center", "Complete Community Center"), - LocationData(None, "The Mines - Floor 120", "Reach the Bottom of The Mines"), - LocationData(None, "Skull Cavern", "Complete Quest Cryptic Note"), - LocationData(None, "Stardew Valley", "Catch Every Fish"), - LocationData(None, "Stardew Valley", "Complete the Museum Collection"), - LocationData(None, "Stardew Valley", "Full House"), + LocationData(None, Region.farm_house, Goal.grandpa_evaluation), + LocationData(None, Region.community_center, Goal.community_center), + LocationData(None, Region.mines_floor_120, Goal.bottom_of_the_mines), + LocationData(None, Region.skull_cavern_100, Goal.cryptic_note), + LocationData(None, Region.farm, Goal.master_angler), + LocationData(None, Region.museum, Goal.complete_museum), + LocationData(None, Region.farm_house, Goal.full_house), + LocationData(None, Region.island_west, Goal.greatest_walnut_hunter), + LocationData(None, Region.qi_walnut_room, Goal.perfection), ] all_locations = load_location_csv() + events_locations @@ -113,6 +133,15 @@ def initialize_groups(): initialize_groups() +def extend_cropsanity_locations(randomized_locations: List[LocationData], world_options): + if world_options[options.Cropsanity] == options.Cropsanity.option_disabled: + return + + cropsanity_locations = locations_by_tag[LocationTags.CROPSANITY] + cropsanity_locations = filter_ginger_island(world_options, cropsanity_locations) + randomized_locations.extend(cropsanity_locations) + + def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_number_of_quests: int): for i in range(0, desired_number_of_quests): batch = i // 7 @@ -128,19 +157,29 @@ def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_ randomized_locations.append(location_table[f"Help Wanted: Gathering {batch + 1}"]) -def extend_fishsanity_locations(randomized_locations: List[LocationData], fishsanity: int, random: Random): +def extend_fishsanity_locations(randomized_locations: List[LocationData], world_options, random: Random): prefix = "Fishsanity: " - if fishsanity == options.Fishsanity.option_none: + if world_options[options.Fishsanity] == options.Fishsanity.option_none: return - elif fishsanity == options.Fishsanity.option_legendaries: + elif world_options[options.Fishsanity] == options.Fishsanity.option_legendaries: randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish) - elif fishsanity == options.Fishsanity.option_special: + elif world_options[options.Fishsanity] == options.Fishsanity.option_special: randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish) - elif fishsanity == options.Fishsanity.option_randomized: - randomized_locations.extend(location_table[f"{prefix}{fish.name}"] - for fish in all_fish if random.random() < 0.4) - elif fishsanity == options.Fishsanity.option_all: - randomized_locations.extend(location_table[f"{prefix}{fish.name}"] for fish in all_fish) + elif world_options[options.Fishsanity] == options.Fishsanity.option_randomized: + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if random.random() < 0.4] + randomized_locations.extend(filter_ginger_island(world_options, fish_locations)) + elif world_options[options.Fishsanity] == options.Fishsanity.option_all: + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish] + randomized_locations.extend(filter_ginger_island(world_options, fish_locations)) + elif world_options[options.Fishsanity] == options.Fishsanity.option_exclude_legendaries: + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish not in legendary_fish] + randomized_locations.extend(filter_ginger_island(world_options, fish_locations)) + elif world_options[options.Fishsanity] == options.Fishsanity.option_exclude_hard_fish: + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish.difficulty < 80] + randomized_locations.extend(filter_ginger_island(world_options, fish_locations)) + elif world_options[options.Fishsanity] == options.Fishsanity.option_only_easy_fish: + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in all_fish if fish.difficulty < 50] + randomized_locations.extend(filter_ginger_island(world_options, fish_locations)) def extend_museumsanity_locations(randomized_locations: List[LocationData], museumsanity: int, random: Random): @@ -156,26 +195,98 @@ def extend_museumsanity_locations(randomized_locations: List[LocationData], muse randomized_locations.extend(location_table[f"{prefix}{museum_item.name}"] for museum_item in all_museum_items) -def extend_friendsanity_locations(randomized_locations: List[LocationData], friendsanity: int): - if friendsanity == options.Friendsanity.option_none: +def extend_friendsanity_locations(randomized_locations: List[LocationData], world_options: options.StardewOptions): + if world_options[options.Friendsanity] == options.Friendsanity.option_none: return - exclude_non_bachelors = friendsanity == options.Friendsanity.option_bachelors - exclude_locked_villagers = friendsanity == options.Friendsanity.option_starting_npcs or \ - friendsanity == options.Friendsanity.option_bachelors - exclude_post_marriage_hearts = friendsanity != options.Friendsanity.option_all_with_marriage + + exclude_leo = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true + exclude_non_bachelors = world_options[options.Friendsanity] == options.Friendsanity.option_bachelors + exclude_locked_villagers = world_options[options.Friendsanity] == options.Friendsanity.option_starting_npcs or \ + world_options[options.Friendsanity] == options.Friendsanity.option_bachelors + include_post_marriage_hearts = world_options[options.Friendsanity] == options.Friendsanity.option_all_with_marriage + heart_size = world_options[options.FriendsanityHeartSize] for villager in all_villagers: + if villager.mod_name not in world_options[options.Mods] and villager.mod_name is not None: + continue if not villager.available and exclude_locked_villagers: continue if not villager.bachelor and exclude_non_bachelors: continue + if villager.name == "Leo" and exclude_leo: + continue + heart_cap = 8 if villager.bachelor else 10 + if include_post_marriage_hearts and villager.bachelor: + heart_cap = 14 for heart in range(1, 15): - if villager.bachelor and exclude_post_marriage_hearts and heart > 8: - continue - if villager.bachelor or heart < 11: + if heart > heart_cap: + break + if heart % heart_size == 0 or heart == heart_cap: randomized_locations.append(location_table[f"Friendsanity: {villager.name} {heart} <3"]) if not exclude_non_bachelors: for heart in range(1, 6): - randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"]) + if heart % heart_size == 0 or heart == 5: + randomized_locations.append(location_table[f"Friendsanity: Pet {heart} <3"]) + + +def extend_festival_locations(randomized_locations: List[LocationData], festival_option: int): + if festival_option == options.FestivalLocations.option_disabled: + return + + festival_locations = locations_by_tag[LocationTags.FESTIVAL] + randomized_locations.extend(festival_locations) + extend_hard_festival_locations(randomized_locations, festival_option) + + +def extend_hard_festival_locations(randomized_locations, festival_option: int): + if festival_option != options.FestivalLocations.option_hard: + return + + hard_festival_locations = locations_by_tag[LocationTags.FESTIVAL_HARD] + randomized_locations.extend(hard_festival_locations) + + +def extend_special_order_locations(randomized_locations: List[LocationData], world_options): + if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled: + return + + include_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false + board_locations = filter_disabled_locations(world_options, locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]) + randomized_locations.extend(board_locations) + if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_board_qi and include_island: + include_arcade = world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_disabled + qi_orders = [location for location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI] if include_arcade or LocationTags.JUNIMO_KART not in location.tags] + randomized_locations.extend(qi_orders) + + +def extend_walnut_purchase_locations(randomized_locations: List[LocationData], world_options): + if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + return + randomized_locations.append(location_table["Repair Ticket Machine"]) + randomized_locations.append(location_table["Repair Boat Hull"]) + randomized_locations.append(location_table["Repair Boat Anchor"]) + randomized_locations.append(location_table["Open Professor Snail Cave"]) + randomized_locations.append(location_table["Complete Island Field Office"]) + randomized_locations.extend(locations_by_tag[LocationTags.WALNUT_PURCHASE]) + + +def extend_mandatory_locations(randomized_locations: List[LocationData], world_options): + mandatory_locations = [location for location in locations_by_tag[LocationTags.MANDATORY]] + filtered_mandatory_locations = filter_disabled_locations(world_options, mandatory_locations) + randomized_locations.extend(filtered_mandatory_locations) + + +def extend_backpack_locations(randomized_locations: List[LocationData], world_options): + backpack_locations = [location for location in locations_by_tag[LocationTags.BACKPACK]] + filtered_backpack_locations = filter_modded_locations(world_options, backpack_locations) + randomized_locations.extend(filtered_backpack_locations) + + +def extend_elevator_locations(randomized_locations: List[LocationData], world_options): + if world_options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla: + return + elevator_locations = [location for location in locations_by_tag[LocationTags.ELEVATOR]] + filtered_elevator_locations = filter_modded_locations(world_options, elevator_locations) + randomized_locations.extend(filtered_elevator_locations) def create_locations(location_collector: StardewLocationCollector, @@ -183,33 +294,55 @@ def create_locations(location_collector: StardewLocationCollector, random: Random): randomized_locations = [] - randomized_locations.extend(locations_by_tag[LocationTags.MANDATORY]) - - if not world_options[options.BackpackProgression] == options.BackpackProgression.option_vanilla: - randomized_locations.extend(locations_by_tag[LocationTags.BACKPACK]) + extend_mandatory_locations(randomized_locations, world_options) + extend_backpack_locations(randomized_locations, world_options) if not world_options[options.ToolProgression] == options.ToolProgression.option_vanilla: randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE]) - if not world_options[options.TheMinesElevatorsProgression] == options.TheMinesElevatorsProgression.option_vanilla: - randomized_locations.extend(locations_by_tag[LocationTags.THE_MINES_ELEVATOR]) + extend_elevator_locations(randomized_locations, world_options) if not world_options[options.SkillProgression] == options.SkillProgression.option_vanilla: - randomized_locations.extend(locations_by_tag[LocationTags.SKILL_LEVEL]) + for location in locations_by_tag[LocationTags.SKILL_LEVEL]: + if location.mod_name is None or location.mod_name in world_options[options.Mods]: + randomized_locations.append(location_table[location.name]) if not world_options[options.BuildingProgression] == options.BuildingProgression.option_vanilla: - randomized_locations.extend(locations_by_tag[LocationTags.BUILDING_BLUEPRINT]) + for location in locations_by_tag[LocationTags.BUILDING_BLUEPRINT]: + if location.mod_name is None or location.mod_name in world_options[options.Mods]: + randomized_locations.append(location_table[location.name]) - if not world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_disabled: + if world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_disabled: randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE_VICTORY]) if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling: randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE]) + extend_cropsanity_locations(randomized_locations, world_options) extend_help_wanted_quests(randomized_locations, world_options[options.HelpWantedLocations]) - extend_fishsanity_locations(randomized_locations, world_options[options.Fishsanity], random) + extend_fishsanity_locations(randomized_locations, world_options, random) extend_museumsanity_locations(randomized_locations, world_options[options.Museumsanity], random) - extend_friendsanity_locations(randomized_locations, world_options[options.Friendsanity]) + extend_friendsanity_locations(randomized_locations, world_options) + + extend_festival_locations(randomized_locations, world_options[options.FestivalLocations]) + extend_special_order_locations(randomized_locations, world_options) + extend_walnut_purchase_locations(randomized_locations, world_options) for location_data in randomized_locations: location_collector(location_data.name, location_data.code, location_data.region) + + +def filter_ginger_island(world_options: options.StardewOptions, locations: List[LocationData]) -> List[LocationData]: + include_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_false + return [location for location in locations if include_island or LocationTags.GINGER_ISLAND not in location.tags] + + +def filter_modded_locations(world_options: options.StardewOptions, locations: List[LocationData]) -> List[LocationData]: + current_mod_names = world_options[options.Mods] + return [location for location in locations if location.mod_name is None or location.mod_name in current_mod_names] + + +def filter_disabled_locations(world_options: options.StardewOptions, locations: List[LocationData]) -> List[LocationData]: + locations_first_pass = filter_ginger_island(world_options, locations) + locations_second_pass = filter_modded_locations(world_options, locations_first_pass) + return locations_second_pass diff --git a/worlds/stardew_valley/logic.py b/worlds/stardew_valley/logic.py index 41f83572..f44ea0c5 100644 --- a/worlds/stardew_valley/logic.py +++ b/worlds/stardew_valley/logic.py @@ -1,99 +1,84 @@ from __future__ import annotations +import math from dataclasses import dataclass, field -from typing import Dict, Union, Optional, Iterable, Sized, Tuple, List +from typing import Dict, Union, Optional, Iterable, Sized, List, Set from . import options from .data import all_fish, FishItem, all_purchasable_seeds, SeedItem, all_crops, CropItem from .data.bundle_data import BundleItem -from .data.museum_data import all_museum_items, MuseumItem -from .data.region_data import SVRegion -from .data.villagers_data import all_villagers_by_name +from .data.fish_data import island_fish +from .data.museum_data import all_museum_items, MuseumItem, all_artifact_items, dwarf_scrolls +from .data.recipe_data import all_cooking_recipes, CookingRecipe, RecipeSource, FriendshipSource, QueenOfSauceSource, \ + StarterSource, ShopSource, SkillSource +from .data.villagers_data import all_villagers_by_name, Villager from .items import all_items, Group +from .mods.logic.buildings import get_modded_building_rules +from .mods.logic.quests import get_modded_quest_rules +from .mods.logic.special_orders import get_modded_special_orders_rules +from .mods.logic.skullcavernelevator import has_skull_cavern_elevator_to_floor +from .mods.mod_data import ModNames +from .mods.logic import magic, skills from .options import StardewOptions +from .regions import vanilla_regions from .stardew_rule import False_, Reach, Or, True_, Received, Count, And, Has, TotalReceived, StardewRule +from .strings.animal_names import Animal, coop_animals, barn_animals +from .strings.animal_product_names import AnimalProduct +from .strings.ap_names.buff_names import Buff +from .strings.ap_names.transport_names import Transportation +from .strings.artisan_good_names import ArtisanGood +from .strings.building_names import Building +from .strings.calendar_names import Weekday +from .strings.craftable_names import Craftable +from .strings.crop_names import Fruit, Vegetable, all_fruits, all_vegetables +from .strings.fertilizer_names import Fertilizer +from .strings.festival_check_names import FestivalCheck +from .strings.fish_names import Fish, Trash, WaterItem +from .strings.flower_names import Flower +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.material_names import Material +from .strings.machine_names import Machine +from .strings.food_names import Meal, Beverage +from .strings.metal_names import Ore, MetalBar, Mineral, Fossil +from .strings.monster_drop_names import Loot +from .strings.performance_names import Performance +from .strings.quest_names import Quest +from .strings.region_names import Region +from .strings.season_names import Season +from .strings.seed_names import Seed +from .strings.skill_names import Skill, ModSkill +from .strings.special_order_names import SpecialOrder +from .strings.spells import MagicSpell +from .strings.tool_names import Tool, ToolMaterial, APTool +from .strings.tv_channel_names import Channel +from .strings.villager_names import NPC +from .strings.wallet_item_names import Wallet +from .strings.weapon_names import Weapon +MAX_MONTHS = 12 MONEY_PER_MONTH = 15000 MISSING_ITEM = "THIS ITEM IS MISSING" tool_materials = { - "Copper": 1, - "Iron": 2, - "Gold": 3, - "Iridium": 4 + ToolMaterial.copper: 1, + ToolMaterial.iron: 2, + ToolMaterial.gold: 3, + ToolMaterial.iridium: 4 } -tool_prices = { - "Copper": 2000, - "Iron": 5000, - "Gold": 10000, - "Iridium": 25000 +tool_upgrade_prices = { + ToolMaterial.copper: 2000, + ToolMaterial.iron: 5000, + ToolMaterial.gold: 10000, + ToolMaterial.iridium: 25000 } -skill_level_per_month_end = { - 0: { - "Farming": 2, - "Fishing": 2, - "Foraging": 2, - "Mining": 2, - "Combat": 2, - }, - 1: { - "Farming": 4, - "Fishing": 4, - "Foraging": 4, - "Mining": 4, - "Combat": 3, - }, - 2: { - "Farming": 7, - "Fishing": 5, - "Foraging": 5, - "Mining": 5, - "Combat": 4, - }, - 3: { - "Farming": 7, - "Fishing": 7, - "Foraging": 6, - "Mining": 7, - "Combat": 5, - }, - 4: { - "Farming": 10, - "Fishing": 10, - "Foraging": 10, - "Mining": 10, - "Combat": 10, - }, -} -month_end_per_skill_level: Dict[Tuple[str, int], int] = {} -month_end_per_total_level: Dict[int, int] = {} - - -def initialize_season_per_skill_level(): - current_level = { - "Farming": 0, - "Fishing": 0, - "Foraging": 0, - "Mining": 0, - "Combat": 0, - } - for month_end, skills in skill_level_per_month_end.items(): - for skill, expected_level in skills.items(): - for level_up in range(current_level[skill] + 1, expected_level + 1): - skill_level = (skill, level_up) - if skill_level not in month_end_per_skill_level: - month_end_per_skill_level[skill_level] = month_end - level_up = 0 - for level_up in range(level_up + 1, sum(skills.values()) + 1): - if level_up not in month_end_per_total_level: - month_end_per_total_level[level_up] = month_end - - -initialize_season_per_skill_level() -week_days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - +fishing_regions = [Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west] @dataclass(frozen=True, repr=False) class StardewLogic: @@ -101,400 +86,456 @@ class StardewLogic: options: StardewOptions item_rules: Dict[str, StardewRule] = field(default_factory=dict) + sapling_rules: Dict[str, StardewRule] = field(default_factory=dict) tree_fruit_rules: Dict[str, StardewRule] = field(default_factory=dict) seed_rules: Dict[str, StardewRule] = field(default_factory=dict) + cooking_rules: Dict[str, StardewRule] = field(default_factory=dict) crop_rules: Dict[str, StardewRule] = field(default_factory=dict) fish_rules: Dict[str, StardewRule] = field(default_factory=dict) museum_rules: Dict[str, StardewRule] = field(default_factory=dict) building_rules: Dict[str, StardewRule] = field(default_factory=dict) quest_rules: Dict[str, StardewRule] = field(default_factory=dict) + festival_rules: Dict[str, StardewRule] = field(default_factory=dict) + special_order_rules: Dict[str, StardewRule] = field(default_factory=dict) def __post_init__(self): self.fish_rules.update({fish.name: self.can_catch_fish(fish) for fish in all_fish}) self.museum_rules.update({donation.name: self.can_find_museum_item(donation) for donation in all_museum_items}) - self.tree_fruit_rules.update({ - "Apple": self.has_lived_months(1) & (self.has_season("Fall") | self.can_reach_region(SVRegion.greenhouse)), - "Apricot": self.has_lived_months(1) & (self.has_season("Spring") | self.can_reach_region(SVRegion.greenhouse)), - "Cherry": self.has_lived_months(1) & (self.has_season("Spring") | self.can_reach_region(SVRegion.greenhouse)), - "Orange": self.has_lived_months(1) & (self.has_season("Summer") | self.can_reach_region(SVRegion.greenhouse)), - "Peach": self.has_lived_months(1) & (self.has_season("Summer") | self.can_reach_region(SVRegion.greenhouse)), - "Pomegranate": self.has_lived_months(1) & (self.has_season("Fall") | self.can_reach_region(SVRegion.greenhouse)), - "Banana Sapling": self.can_reach_region(SVRegion.ginger_island), - "Mango Sapling": self.can_reach_region(SVRegion.ginger_island), - "Banana": self.has("Banana Sapling") & (self.has_season("Summer") | self.can_reach_region(SVRegion.greenhouse)), - "Mango": self.has("Mango Sapling") & (self.has_season("Summer") | self.can_reach_region(SVRegion.greenhouse)), + for recipe in all_cooking_recipes: + can_cook_rule = self.can_cook(recipe) + if recipe.meal in self.cooking_rules: + can_cook_rule = can_cook_rule | self.cooking_rules[recipe.meal] + self.cooking_rules[recipe.meal] = can_cook_rule + + self.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.tree_fruit_rules.update({ + Fruit.apple: self.can_plant_and_grow_item(Season.fall), + Fruit.apricot: self.can_plant_and_grow_item(Season.spring), + Fruit.cherry: self.can_plant_and_grow_item(Season.spring), + Fruit.orange: self.can_plant_and_grow_item(Season.summer), + Fruit.peach: self.can_plant_and_grow_item(Season.summer), + Fruit.pomegranate: self.can_plant_and_grow_item(Season.fall), + Fruit.banana: self.can_plant_and_grow_item(Season.summer), + Fruit.mango: self.can_plant_and_grow_item(Season.summer), + }) + + for tree_fruit in self.tree_fruit_rules: + existing_rules = self.tree_fruit_rules[tree_fruit] + sapling = f"{tree_fruit} Sapling" + self.tree_fruit_rules[tree_fruit] = existing_rules & self.has(sapling) & self.has_lived_months(1) + self.seed_rules.update({seed.name: self.can_buy_seed(seed) for seed in all_purchasable_seeds}) self.crop_rules.update({crop.name: self.can_grow_crop(crop) for crop in all_crops}) self.crop_rules.update({ - "Coffee Bean": (self.has_season("Spring") | self.has_season("Summer")) & self.has_traveling_merchant(), + Seed.coffee: (self.has_season(Season.spring) | self.has_season( + Season.summer)) & self.has_traveling_merchant(), + Fruit.ancient_fruit: (self.received("Ancient Seeds") | self.received("Ancient Seeds Recipe")) & + self.can_reach_region(Region.greenhouse) & self.has(Machine.seed_maker), }) self.item_rules.update({ - "Aged Roe": self.has("Preserves Jar") & self.has("Roe"), - "Algae Soup": self.can_cook() & self.has("Green Algae") & self.has_relationship("Clint", 3), - "Any Egg": self.has("Chicken Egg") | self.has("Duck Egg"), - "Artichoke Dip": self.can_cook() & self.has_season("Fall") & self.has("Artichoke") & self.has("Cow Milk"), - "Artifact Trove": self.has("Omni Geode") & self.can_reach_region(SVRegion.desert), - "Bait": self.has_skill_level("Fishing", 2), - "Bat Wing": self.can_mine_in_the_mines_floor_41_80() | self.can_mine_in_the_skull_cavern(), - "Battery Pack": self.has("Lightning Rod"), - "Bean Hotpot": self.can_cook() & self.has_relationship("Clint", 7) & self.has("Green Bean"), - "Bee House": self.has_skill_level("Farming", 3) & self.has("Iron Bar") & self.has("Maple Syrup"), - "Beer": (self.has("Keg") & self.has("Wheat")) | self.can_spend_money(400), - "Blackberry": self.has_season("Fall"), - "Blackberry Cobbler": self.can_cook() & self.has_season("Fall") & self.has_year_two() & - self.has("Blackberry") & self.has("Sugar") & self.has("Wheat Flour"), - "Blueberry Tart": self.has("Blueberry") & self.has("Any Egg") & self.has_relationship("Pierre", 3), - "Bouquet": self.has_relationship("Bachelor", 8), - "Bread": self.can_spend_money(120) | (self.can_spend_money(100) & self.can_cook()), - "Broken CD": self.can_crab_pot(), - "Broken Glasses": self.can_crab_pot(), - "Bug Meat": self.can_mine_in_the_mines_floor_1_40(), - "Cactus Fruit": self.can_reach_region(SVRegion.desert), - "Cave Carrot": self.can_mine_to_floor(10), - "Caviar": self.has("Preserves Jar") & self.has("Sturgeon Roe"), - "Chanterelle": self.has_season("Fall") & self.can_reach_region(SVRegion.secret_woods), - "Cheese Press": self.has_skill_level("Farming", 6) & self.has("Hardwood") & self.has("Copper Bar"), - "Cheese": (self.has("Cow Milk") & self.has("Cheese Press")) | - (self.can_reach_region(SVRegion.desert) & self.has("Emerald")), - "Cheese Cauliflower": self.has(["Cheese", "Cauliflower"]) & self.has_relationship("Pam", 3) & - self.can_cook(), - "Chicken": self.has_building("Coop"), - "Chicken Egg": self.has(["Egg", "Egg (Brown)", "Large Egg", "Large Egg (Brown)"], 1), - "Chocolate Cake": self.can_cook() & self.has_season("Winter") & self.has("Wheat Flour") & self.has( - "Sugar") & self.has("Any Egg"), - "Chowder": self.can_cook() & self.has_relationship("Willy", 3) & self.has(["Clam", "Cow Milk"]), - "Clam": True_(), - "Clay": True_(), - "Glazed Yams": self.can_cook() & self.has_season("Fall") & self.has("Yam") & self.has("Sugar"), - "Cloth": (self.has("Wool") & self.has("Loom")) | - (self.can_reach_region(SVRegion.desert) & self.has("Aquamarine")), - "Coal": True_(), - "Cockle": True_(), - "Coconut": self.can_reach_region(SVRegion.desert), - "Coffee": (self.has("Keg") & self.has("Coffee Bean")) | self.has("Coffee Maker") | - self.can_spend_money(300) | self.has("Hot Java Ring"), - "Coffee Maker": False_(), - "Common Mushroom": self.has_season("Fall") | - (self.has_season("Spring") & self.can_reach_region(SVRegion.secret_woods)), - "Complete Breakfast": self.can_cook() & self.has_season("Spring") & self.has_lived_months(4) & - self.has("Fried Egg") & self.has("Cow Milk") & self.has("Hashbrowns") | self.has( - "Pancakes"), - "Copper Bar": self.can_smelt("Copper Ore"), - "Copper Ore": self.can_mine_in_the_mines_floor_1_40() | self.can_mine_in_the_skull_cavern(), - "Coral": self.can_reach_region(SVRegion.tide_pools) | self.has_season("Summer"), - "Cow": self.has_building("Barn"), - "Cow Milk": self.has("Milk") | self.has("Large Milk"), - "Crab": self.can_crab_pot(), - "Crab Cakes": self.can_mine_in_the_skull_cavern() | - (self.can_cook() & self.has_season("Fall") & self.has_year_two() & self.has("Crab") & - self.has("Wheat Flour") & self.has("Chicken Egg") & self.has("Oil")), - "Crab Pot": self.has_skill_level("Fishing", 3), - "Cranberry Candy": self.can_cook() & self.has_season("Winter") & self.has("Cranberries") & - self.has("Apple") & self.has("Sugar"), - "Crayfish": self.can_crab_pot(), - "Crispy Bass": self.can_cook() & self.has_relationship("Kent", 3) & self.has("Largemouth Bass") & - self.has("Wheat Flour") & self.has("Oil"), - "Crocus": self.has_season("Winter"), - "Crystal Fruit": self.has_season("Winter"), - "Daffodil": self.has_season("Spring"), - "Dandelion": self.has_season("Spring"), - "Dish O' The Sea": self.can_cook() & self.has_skill_level("Fishing", 3) & - self.has(["Sardine", "Hashbrowns"]), - "Dorado": self.can_fish(78) & self.has_season("Summer"), - "Dried Starfish": self.can_fish() & self.can_reach_region(SVRegion.beach), - "Driftwood": self.can_crab_pot(), - "Duck Egg": self.has("Duck"), - "Duck Feather": self.has("Duck"), - "Duck": self.has_building("Big Coop"), - "Egg": self.has("Chicken"), - "Egg (Brown)": self.has("Chicken"), - "Eggplant Parmesan": self.can_cook() & self.has_relationship("Lewis", 7) & self.has("Eggplant") & self.has( - "Tomato"), - "Escargot": self.can_cook() & self.has_relationship("Willy", 5) & self.has("Snail") & self.has("Garlic"), - "Farmer's Lunch": self.can_cook() & self.has_skill_level("Farming", 3) & self.has("Omelet") & self.has( - "Parsnip"), - "Fiber": True_(), - "Fiddlehead Fern": self.can_reach_region(SVRegion.secret_woods) & self.has_season("Summer"), - "Fiddlehead Risotto": self.can_cook() & self.has_season("Fall") & self.has("Oil") & - self.has("Fiddlehead Fern") & self.has("Garlic"), + ArtisanGood.aged_roe: self.can_preserves_jar(AnimalProduct.roe), + AnimalProduct.any_egg: self.has(AnimalProduct.chicken_egg) | self.has(AnimalProduct.duck_egg), + Fish.any: Or([self.can_catch_fish(fish) for fish in all_fish]), + Geode.artifact_trove: self.has(Geode.omni) & self.can_reach_region(Region.desert), + Craftable.bait: (self.has_skill_level(Skill.fishing, 2) & self.has(Loot.bug_meat)) | self.has(Machine.worm_bin), + Fertilizer.basic: (self.has(Material.sap) & self.has_farming_level(1)) | (self.has_lived_months(1) & self.can_spend_money_at(Region.pierre_store, 100)), + Fertilizer.quality: (self.has_farming_level(9) & self.has(Material.sap) & self.has(Fish.any)) | (self.has_year_two() & self.can_spend_money_at(Region.pierre_store, 150)), + Fertilizer.deluxe: False_(), + # self.received("Deluxe Fertilizer Recipe") & self.has(MetalBar.iridium) & self.has(SVItem.sap), + Fertilizer.tree: self.has_skill_level(Skill.foraging, 7) & self.has(Material.fiber) & self.has(Material.stone), + Loot.bat_wing: self.can_mine_in_the_mines_floor_41_80() | self.can_mine_in_the_skull_cavern(), + ArtisanGood.battery_pack: (self.has(Machine.lightning_rod) & self.has_any_season_not_winter()) | self.has(Machine.solar_panel), + Machine.bee_house: self.has_farming_level(3) & self.has(MetalBar.iron) & self.has(ArtisanGood.maple_syrup) & self.has(Material.coal) & self.has(Material.wood), + Beverage.beer: self.can_keg(Vegetable.wheat) | self.can_spend_money_at(Region.saloon, 400), + Forageable.blackberry: self.can_forage(Season.fall), + Craftable.bomb: self.has_skill_level(Skill.mining, 6) & self.has(Material.coal) & self.has(Ore.iron), + Fossil.bone_fragment: self.can_reach_region(Region.dig_site), + Gift.bouquet: self.has_relationship(Generic.bachelor, 8) & self.can_spend_money_at(Region.pierre_store, 100), + Meal.bread: self.can_spend_money_at(Region.saloon, 120), + Trash.broken_cd: self.can_crab_pot(), + Trash.broken_glasses: self.can_crab_pot(), + Loot.bug_meat: self.can_mine_in_the_mines_floor_1_40(), + Forageable.cactus_fruit: self.can_forage(Generic.any, Region.desert), + Machine.cask: self.has_house(3) & self.can_reach_region(Region.cellar) & self.has(Material.wood) & self.has(Material.hardwood), + Forageable.cave_carrot: self.can_forage(Generic.any, Region.mines_floor_10, True), + ArtisanGood.caviar: self.can_preserves_jar(AnimalProduct.sturgeon_roe), + Forageable.chanterelle: self.can_forage(Season.fall, Region.secret_woods), + Machine.cheese_press: self.has_farming_level(6) & self.has(Material.wood) & self.has(Material.stone) & self.has(Material.hardwood) & self.has(MetalBar.copper), + ArtisanGood.cheese: (self.has(AnimalProduct.cow_milk) & self.has(Machine.cheese_press)) | (self.can_reach_region(Region.desert) & self.has(Mineral.emerald)), + Craftable.cherry_bomb: self.has_skill_level(Skill.mining, 1) & self.has(Material.coal) & self.has(Ore.copper), + Animal.chicken: self.can_buy_animal(Animal.chicken), + AnimalProduct.chicken_egg: self.has([AnimalProduct.egg, AnimalProduct.brown_egg, AnimalProduct.large_egg, AnimalProduct.large_brown_egg], 1), + Material.cinder_shard: self.can_reach_region(Region.volcano_floor_5), + WaterItem.clam: self.can_forage(Generic.any, Region.beach), + Material.clay: self.can_reach_any_region([Region.farm, Region.beach, Region.quarry]) & self.has_tool(Tool.hoe), + ArtisanGood.cloth: (self.has(AnimalProduct.wool) & self.has(Machine.loom)) | (self.can_reach_region(Region.desert) & self.has(Mineral.aquamarine)), + Material.coal: self.can_mine_in_the_mines_floor_41_80() | self.can_do_panning(), + WaterItem.cockle: self.can_forage(Generic.any, Region.beach), + Forageable.coconut: self.can_forage(Generic.any, Region.desert), + Beverage.coffee: self.can_keg(Seed.coffee) | self.has(Machine.coffee_maker) | (self.can_spend_money_at(Region.saloon, 300)) | self.has("Hot Java Ring"), + Machine.coffee_maker: self.received(Machine.coffee_maker), + Forageable.common_mushroom: self.can_forage(Season.fall) | (self.can_forage(Season.spring, Region.secret_woods)), + MetalBar.copper: self.can_smelt(Ore.copper), + Ore.copper: self.can_mine_in_the_mines_floor_1_40() | self.can_mine_in_the_skull_cavern() | self.can_do_panning(), + WaterItem.coral: self.can_forage(Generic.any, Region.tide_pools) | self.can_forage(Season.summer, Region.beach), + Animal.cow: self.can_buy_animal(Animal.cow), + AnimalProduct.cow_milk: self.has(AnimalProduct.milk) | self.has(AnimalProduct.large_milk), + Fish.crab: self.can_crab_pot(Region.beach), + Machine.crab_pot: self.has_skill_level(Skill.fishing, 3) & (self.can_spend_money_at(Region.fish_shop, 1500) | (self.has(MetalBar.iron) & self.has(Material.wood))), + Fish.crayfish: self.can_crab_pot(Region.town), + Forageable.crocus: self.can_forage(Season.winter), + Forageable.crystal_fruit: self.can_forage(Season.winter), + Forageable.daffodil: self.can_forage(Season.spring), + Forageable.dandelion: self.can_forage(Season.spring), + Animal.dinosaur: self.has_building(Building.big_coop) & self.has(AnimalProduct.dinosaur_egg), + Forageable.dragon_tooth: self.can_forage(Generic.any, Region.volcano_floor_10), + "Dried Starfish": self.can_fish() & self.can_reach_region(Region.beach), + Trash.driftwood: self.can_crab_pot(), + AnimalProduct.duck_egg: self.has_animal(Animal.duck), + AnimalProduct.duck_feather: self.has_happy_animal(Animal.duck), + Animal.duck: self.can_buy_animal(Animal.duck), + AnimalProduct.egg: self.has_animal(Animal.chicken), + AnimalProduct.brown_egg: self.has_animal(Animal.chicken), + "Energy Tonic": self.can_reach_region(Region.hospital) & self.can_spend_money(1000), + Material.fiber: True_(), + Forageable.fiddlehead_fern: self.can_forage(Season.summer, Region.secret_woods), + "Magic Rock Candy": self.can_reach_region(Region.desert) & self.has("Prismatic Shard"), "Fishing Chest": self.can_fish_chests(), - "Fish Taco": self.can_cook() & self.has_relationship("Linus", 7) & self.has("Tuna") & self.has("Tortilla") & - self.has("Red Cabbage") & self.has("Mayonnaise"), - "Fried Calamari": self.can_cook() & self.has_relationship("Jodi", 3) & self.has("Squid") & - self.has("Wheat Flour") & self.has("Oil"), - "Fried Eel": self.can_cook() & self.has_relationship("George", 3) & self.has("Eel") & self.has("Oil"), - "Fried Egg": self.can_cook() & self.has("Any Egg"), - "Fried Mushroom": self.can_cook() & self.has_relationship("Demetrius", 3) & self.has( - "Morel") & self.has("Common Mushroom"), - "Frozen Geode": self.can_mine_in_the_mines_floor_41_80(), - "Fruit Salad": self.can_cook() & self.has_season("Fall") & self.has_year_two() & self.has("Blueberry") & - self.has("Melon") & self.has("Apricot"), - "Furnace": self.has("Stone") & self.has("Copper Ore"), - "Geode": self.can_mine_in_the_mines_floor_1_40(), - "Ginger": self.can_reach_region(SVRegion.ginger_island), - "Ginger Ale": self.can_cook() & self.can_reach_region(SVRegion.ginger_island) & self.has("Ginger") & self.has( - "Sugar"), - "Goat Cheese": self.has("Goat Milk") & self.has("Cheese Press"), - "Goat Milk": self.has("Goat"), - "Goat": self.has_building("Big Barn"), - "Gold Bar": self.can_smelt("Gold Ore"), - "Gold Ore": self.can_mine_in_the_mines_floor_81_120() | self.can_mine_in_the_skull_cavern(), - "Golden Pumpkin": self.has_season("Fall") | self.has("Artifact Trove"), - "Green Algae": self.can_fish(), - "Green Tea": self.has("Keg") & self.has("Tea Leaves"), - "Hardwood": self.has_tool("Axe", "Copper"), - "Hashbrowns": self.can_cook() & self.can_spend_money(50) & self.has("Potato"), - "Hazelnut": self.has_season("Fall"), - "Holly": self.has_season("Winter"), - "Honey": self.can_reach_region(SVRegion.desert) | - (self.has("Bee House") & - (self.has_season("Spring") | self.has_season("Summer") | self.has_season("Fall"))), - "Hot Java Ring": self.can_reach_region(SVRegion.ginger_island), - "Ice Cream": (self.has_season("Summer") & self.can_reach_region(SVRegion.town)) | self.can_reach_region( - "The Desert"), - # | (self.can_cook() & self.has_relationship("Jodi", 7) & self.has("Cow Milk") & self.has("Sugar")), - "Iridium Bar": self.can_smelt("Iridium Ore"), - "Iridium Ore": self.can_mine_in_the_skull_cavern(), - "Iron Bar": self.can_smelt("Iron Ore"), - "Iron Ore": self.can_mine_in_the_mines_floor_41_80() | self.can_mine_in_the_skull_cavern(), - "Jelly": self.has("Preserves Jar"), + Craftable.flute_block: self.has_relationship(NPC.robin, 6) & self.can_reach_region(Region.carpenter) & self.has(Material.wood) & self.has(Ore.copper) & self.has(Material.fiber), + Geode.frozen: self.can_mine_in_the_mines_floor_41_80(), + Machine.furnace: self.has(Material.stone) & self.has(Ore.copper), + Geode.geode: self.can_mine_in_the_mines_floor_1_40(), + Forageable.ginger: self.can_forage(Generic.any, Region.island_west, True), + ArtisanGood.goat_cheese: self.has(AnimalProduct.goat_milk) & self.has(Machine.cheese_press), + AnimalProduct.goat_milk: self.has(Animal.goat), + Animal.goat: self.can_buy_animal(Animal.goat), + MetalBar.gold: self.can_smelt(Ore.gold), + Ore.gold: self.can_mine_in_the_mines_floor_81_120() | self.can_mine_in_the_skull_cavern() | self.can_do_panning(), + Geode.golden_coconut: self.can_reach_region(Region.island_north), + Gift.golden_pumpkin: self.has_season(Season.fall) | self.can_open_geode(Geode.artifact_trove), + WaterItem.green_algae: self.can_fish_in_freshwater(), + ArtisanGood.green_tea: self.can_keg(Vegetable.tea_leaves), + Material.hardwood: self.has_tool(Tool.axe, ToolMaterial.copper) & (self.can_reach_region(Region.secret_woods) | self.can_reach_region(Region.island_south)), + Forageable.hay: self.has_building(Building.silo) & self.has_tool(Tool.scythe), + Forageable.hazelnut: self.can_forage(Season.fall), + Forageable.holly: self.can_forage(Season.winter), + ArtisanGood.honey: self.can_spend_money_at(Region.oasis, 200) | (self.has(Machine.bee_house) & self.has_any_season_not_winter()), + "Hot Java Ring": self.can_reach_region(Region.volcano_floor_10), + Meal.ice_cream: (self.has_season(Season.summer) & self.can_spend_money_at(Region.town, 250)) | self.can_spend_money_at(Region.oasis, 240), + # | (self.can_cook() & self.has_relationship(NPC.jodi, 7) & self.has(AnimalProduct.cow_milk) & self.has(Ingredient.sugar)), + MetalBar.iridium: self.can_smelt(Ore.iridium), + Ore.iridium: self.can_mine_in_the_skull_cavern(), + MetalBar.iron: self.can_smelt(Ore.iron), + Ore.iron: self.can_mine_in_the_mines_floor_41_80() | self.can_mine_in_the_skull_cavern() | self.can_do_panning(), + ArtisanGood.jelly: self.has_jelly(), + Trash.joja_cola: self.can_spend_money_at(Region.saloon, 75), "JotPK Small Buff": self.has_jotpk_power_level(2), "JotPK Medium Buff": self.has_jotpk_power_level(4), "JotPK Big Buff": self.has_jotpk_power_level(7), "JotPK Max Buff": self.has_jotpk_power_level(9), - "Juice": self.has("Keg"), + ArtisanGood.juice: self.has_juice(), "Junimo Kart Small Buff": self.has_junimo_kart_power_level(2), "Junimo Kart Medium Buff": self.has_junimo_kart_power_level(4), "Junimo Kart Big Buff": self.has_junimo_kart_power_level(6), "Junimo Kart Max Buff": self.has_junimo_kart_power_level(8), - "Keg": self.has_skill_level("Farming", 8) & self.has("Iron Bar") & self.has("Copper Bar") & self.has( - "Oak Resin"), - "Large Egg": self.has("Chicken"), - "Large Egg (Brown)": self.has("Chicken"), - "Large Goat Milk": self.has("Goat"), - "Large Milk": self.has("Cow"), - "Leek": self.has_season("Spring"), - "Lightning Rod": self.has_skill_level("Foraging", 6), - "Lobster": self.can_crab_pot(), - "Loom": self.has_skill_level("Farming", 7) & self.has("Pine Tar"), - "Magic Rock Candy": self.can_reach_region(SVRegion.desert) & self.has("Prismatic Shard"), - "Magma Geode": self.can_mine_in_the_mines_floor_81_120() | - (self.has("Lava Eel") & self.has_building("Fish Pond")), - "Maki Roll": self.can_cook() & self.can_fish(), - "Maple Bar": self.can_cook() & self.has_season("Summer") & self.has_year_two() & self.has("Maple Syrup") & - self.has("Sugar") & self.has("Wheat Flour"), - "Maple Syrup": self.has("Tapper"), - "Mayonnaise": self.has("Mayonnaise Machine") & self.has("Chicken Egg"), - "Mayonnaise Machine": self.has_skill_level("Farming", 2) & self.has("Wood") & self.has("Stone") & - self.has("Earth Crystal") & self.has("Copper Bar"), - "Mead": self.has("Keg") & self.has("Honey"), - "Milk": self.has("Cow"), - "Miner's Treat": self.can_cook() & self.has_skill_level("Mining", 3) & self.has("Cow Milk") & self.has( - "Cave Carrot"), - "Morel": self.can_reach_region(SVRegion.secret_woods), - "Mussel": True_(), - "Nautilus Shell": self.has_season("Winter"), - "Oak Resin": self.has("Tapper"), - "Oil": True_(), - "Oil Maker": self.has_skill_level("Farming", 8) & self.has("Hardwood") & self.has("Gold Bar"), - "Omelet": self.can_cook() & self.can_spend_money(100) & self.has("Any Egg") & self.has("Cow Milk"), - "Omni Geode": self.can_mine_in_the_mines_floor_41_80() | - self.can_reach_region(SVRegion.desert) | - self.can_do_panning() | - self.received("Rusty Key") | - (self.has("Octopus") & self.has_building("Fish Pond")) | - self.can_reach_region(SVRegion.ginger_island), - "Ostrich": self.has_building("Barn") & self.has("Ostrich Egg"), - "Ostrich Egg": self.can_reach_region(SVRegion.ginger_island), - "Oyster": True_(), - "Pale Ale": self.has("Keg") & self.has("Hops"), - "Pale Broth": self.can_cook() & self.has_relationship("Marnie", 3) & self.has("White Algae"), - "Pancakes": self.can_cook() & self.can_spend_money(100) & self.has("Any Egg"), - "Parsnip Soup": self.can_cook() & self.has_relationship("Caroline", 3) & self.has( - "Parsnip") & self.has("Cow Milk"), - "Pearl": (self.has("Blobfish") & self.has_building("Fish Pond")) | - (self.has_lived_months(4) & self.has("Artifact Trove")), - "Pepper Poppers": self.can_cook() & self.has("Cheese") & self.has( - "Hot Pepper") & self.has_relationship("Shane", 3), - "Periwinkle": self.can_crab_pot(), - "Pickles": self.has("Preserves Jar"), - "Pig": self.has_building("Deluxe Barn"), - "Piña Colada": False_(), # self.can_reach_region(SVRegion.ginger_island), - "Pine Tar": self.has("Tapper"), - "Pink Cake": self.can_cook() & self.has_season("Summer") & self.has("Melon") & self.has( - "Wheat Flour") & self.has("Sugar") & self.has("Any Egg"), - "Pizza": self.can_spend_money(600), - "Plum Pudding": self.can_cook() & self.has_season("Winter") & self.has("Wild Plum") & - self.has("Wheat Flour") & self.has("Sugar"), - "Poppyseed Muffin": self.can_cook() & self.has_season("Winter") & self.has_year_two() & - self.has("Poppy") & self.has("Wheat Flour") & self.has("Sugar"), - "Preserves Jar": self.has_skill_level("Farming", 4), - "Pumpkin Pie": self.can_cook() & self.has_season("Winter") & self.has("Wheat Flour") & - self.has("Cow Milk") & self.has("Sugar"), - "Purple Mushroom": self.can_mine_in_the_mines_floor_81_120() | self.can_mine_in_the_skull_cavern(), - "Rabbit": self.has_building("Deluxe Coop"), - "Rabbit's Foot": self.has("Rabbit"), - "Radioactive Bar": self.can_smelt("Radioactive Ore"), - "Radioactive Ore": self.can_mine_perfectly_in_the_skull_cavern() & self.can_reach_region(SVRegion.ginger_island), - "Rainbow Shell": self.has_season("Summer"), - "Rain Totem": self.has_skill_level("Foraging", 9), - "Recycling Machine": self.has_skill_level("Fishing", 4) & self.has("Wood") & - self.has("Stone") & self.has("Iron Bar"), - "Red Mushroom": self.can_reach_region(SVRegion.secret_woods) & ( - self.has_season("Summer") | self.has_season("Fall")), - "Refined Quartz": self.can_smelt("Quartz") | self.can_smelt("Fire Quartz") | - (self.has("Recycling Machine") & (self.has("Broken CD") | self.has("Broken Glasses"))), - "Rhubarb Pie": self.can_cook() & self.has_relationship("Marnie", 7) & self.has("Rhubarb") & - self.has("Wheat Flour") & self.has("Sugar"), - "Rice": True_(), - "Rice Pudding": self.can_cook() & self.has_relationship("Evelyn", 7) & self.has("Cow Milk") & - self.has("Sugar") & self.has("Rice"), - "Roe": self.can_fish() & self.has_building("Fish Pond"), - "Roots Platter": self.can_cook() & self.has_skill_level("Combat", 3) & - self.has("Cave Carrot") & self.has("Winter Root"), - "Roasted Hazelnuts": self.can_cook() & self.has_season("Summer") & self.has("Hazelnut"), - "Salad": self.can_spend_money(220), - # | (self.can_cook() & self.has_relationship("Emily", 3) & self.has("Leek") & self.has("Dandelion") & self.has("Vinegar")), - "Salmonberry": self.has_season("Spring"), - "Salmon Dinner": self.can_cook() & self.has_relationship("Gus", 3) & self.has("Salmon") & self.has( - "Amaranth") & self.has("Kale"), - "Sashimi": self.can_fish() & self.can_cook() & self.has_relationship("Linus", 3), - "Sea Urchin": self.can_reach_region(SVRegion.tide_pools) | self.has_season("Summer"), - "Seaweed": self.can_fish() | self.can_reach_region(SVRegion.tide_pools), - "Secret Note": self.received("Magnifying Glass"), - "Sheep": self.has_building("Deluxe Barn"), - "Shrimp": self.can_crab_pot(), - "Slime": self.can_mine_in_the_mines_floor_1_40(), - "Snail": self.can_crab_pot(), - "Snow Yam": self.has_season("Winter"), - "Soggy Newspaper": self.can_crab_pot(), - "Solar Essence": self.can_mine_in_the_mines_floor_41_80() | self.can_mine_in_the_skull_cavern(), - "Spaghetti": self.can_spend_money(240), - "Spice Berry": self.has_season("Summer"), - "Spring Onion": self.has_season("Spring"), - "Squid Ink": self.can_mine_in_the_mines_floor_81_120() | ( - self.has_building("Fish Pond") & self.has("Squid")), - "Staircase": self.has_skill_level("Mining", 2), - "Stir Fry": self.can_cook() & self.has_season("Spring") & self.has("Cave Carrot") & - self.has("Common Mushroom") & self.has("Kale") & self.has("Oil"), - "Stone": self.has_tool("Pickaxe"), - "Stuffing": self.has_season("Winter") | - (self.can_cook() & self.has_relationship("Pam", 7) & self.has("Bread") & - self.has("Cranberries") & self.has("Hazelnut")), - "Sturgeon Roe": self.has("Sturgeon") & self.has_building("Fish Pond"), - "Sugar": True_(), - "Survival Burger": self.can_cook() & self.has_skill_level("Foraging", 2) & - self.has(["Bread", "Cave Carrot", "Eggplant"]), - "Sweet Pea": self.has_season("Summer"), - "Tapper": self.has_skill_level("Foraging", 3), - "Tea Bush": self.has_relationship("Caroline", 2), - "Tea Leaves": self.has_lived_months(1) & self.has("Tea Bush"), - "Tortilla": self.can_cook() & self.can_spend_money(100) & self.has("Corn"), - "Trash": self.can_crab_pot(), - "Triple Shot Espresso": (self.has("Hot Java Ring") | - (self.can_cook() & self.can_spend_money(5000) & self.has("Coffee"))), - "Tropical Curry": False_(), - # self.can_cook() & self.can_reach_region(SVRegion.ginger_island) & self.has("Coconut") & self.has("Pineapple") & self.has("Hot Pepper"), - "Truffle Oil": self.has("Truffle") & self.has("Oil Maker"), - "Truffle": self.has("Pig") & self.has_spring_summer_or_fall(), - "Vegetable Medley": self.can_cook() & self.has_relationship("Caroline", 7) & self.has("Tomato") & self.has( - "Beet"), - "Vinegar": True_(), - "Void Egg": self.can_meet("Krobus") | (self.has_building("Fish Pond") & self.has("Void Salmon")), - "Void Essence": self.can_mine_in_the_mines_floor_81_120() | self.can_mine_in_the_skull_cavern(), - "Void Mayonnaise": self.has("Mayonnaise Machine") & self.has("Void Egg"), - "Wheat Flour": True_(), - "White Algae": self.can_fish() & self.can_mine_in_the_mines_floor_1_40(), - "Wild Horseradish": self.has_season("Spring"), - "Wild Plum": self.has_season("Fall"), - "Wilted Bouquet": self.has("Furnace") & self.has("Bouquet") & self.has("Coal"), - "Wine": self.has("Keg"), - "Winter Root": self.has_season("Winter"), - "Wood": self.has_tool("Axe"), - "Wool": self.has("Rabbit") | self.has("Sheep"), - "Hay": self.has_building("Silo"), + Machine.keg: self.has_farming_level(8) & self.has(Material.wood) & self.has(MetalBar.iron) & self.has(MetalBar.copper) & self.has(ArtisanGood.oak_resin), + AnimalProduct.large_egg: self.has_happy_animal(Animal.chicken), + AnimalProduct.large_brown_egg: self.has_happy_animal(Animal.chicken), + AnimalProduct.large_goat_milk: self.has_happy_animal(Animal.goat), + AnimalProduct.large_milk: self.has_happy_animal(Animal.cow), + Forageable.leek: self.can_forage(Season.spring), + Craftable.life_elixir: self.has_skill_level(Skill.combat, 2) & self.has(Forageable.red_mushroom) & self.has(Forageable.purple_mushroom) & self.has(Forageable.morel) & self.has(Forageable.chanterelle), + Machine.lightning_rod: self.has_skill_level(Skill.foraging, 6) & self.has(MetalBar.iron) & self.has(MetalBar.quartz) & self.has(Loot.bat_wing), + Fish.lobster: self.can_crab_pot(Region.beach), + Machine.loom: self.has_farming_level(7) & self.has(Material.wood) & self.has(Material.fiber) & self.has(ArtisanGood.pine_tar), + Forageable.magma_cap: self.can_forage(Generic.any, Region.volcano_floor_5), + Geode.magma: self.can_mine_in_the_mines_floor_81_120() | (self.has(Fish.lava_eel) & self.has_building(Building.fish_pond)), + ArtisanGood.maple_syrup: self.has(Machine.tapper), + ArtisanGood.mayonnaise: self.has(Machine.mayonnaise_machine) & self.has(AnimalProduct.chicken_egg), + Machine.mayonnaise_machine: self.has_farming_level(2) & self.has(Material.wood) & self.has(Material.stone) & self.has("Earth Crystal") & self.has(MetalBar.copper), + ArtisanGood.mead: self.can_keg(ArtisanGood.honey), + Craftable.mega_bomb: self.has_skill_level(Skill.mining, 8) & self.has(Ore.gold) & self.has(Loot.solar_essence) & self.has(Loot.void_essence), + Gift.mermaid_pendant: self.can_reach_region(Region.tide_pools) & self.has_relationship(Generic.bachelor, 10) & self.has_house(1) & self.has(Craftable.rain_totem), + AnimalProduct.milk: self.has_animal(Animal.cow), + Craftable.monster_musk: self.has_prismatic_jelly_reward_access() & self.has(Loot.slime) & self.has(Loot.bat_wing), + Forageable.morel: self.can_forage(Season.spring, Region.secret_woods), + "Muscle Remedy": self.can_reach_region(Region.hospital) & self.can_spend_money(1000), + Fish.mussel: self.can_forage(Generic.any, Region.beach) or self.has(Fish.mussel_node), + Fish.mussel_node: self.can_reach_region(Region.island_west), + WaterItem.nautilus_shell: self.can_forage(Season.winter, Region.beach), + ArtisanGood.oak_resin: self.has(Machine.tapper), + Ingredient.oil: self.can_spend_money_at(Region.pierre_store, 200) | (self.has(Machine.oil_maker) & (self.has(Vegetable.corn) | self.has(Flower.sunflower) | self.has(Seed.sunflower))), + Machine.oil_maker: self.has_farming_level(8) & self.has(Loot.slime) & self.has(Material.hardwood) & self.has(MetalBar.gold), + Craftable.oil_of_garlic: (self.has_skill_level(Skill.combat, 6) & self.has(Vegetable.garlic) & self.has(Ingredient.oil)) | (self.can_spend_money_at(Region.mines_dwarf_shop, 3000)), + Geode.omni: self.can_mine_in_the_mines_floor_41_80() | self.can_reach_region(Region.desert) | self.can_do_panning() | self.received(Wallet.rusty_key) | (self.has(Fish.octopus) & self.has_building(Building.fish_pond)) | self.can_reach_region(Region.volcano_floor_10), + Animal.ostrich: self.has_building(Building.barn) & self.has(AnimalProduct.ostrich_egg) & self.has(Machine.ostrich_incubator), + AnimalProduct.ostrich_egg: self.can_forage(Generic.any, Region.island_north, True), + Machine.ostrich_incubator: self.received("Ostrich Incubator Recipe") & self.has(Fossil.bone_fragment) & self.has(Material.hardwood) & self.has(Material.cinder_shard), + Fish.oyster: self.can_forage(Generic.any, Region.beach), + ArtisanGood.pale_ale: self.can_keg(Vegetable.hops), + Gift.pearl: (self.has(Fish.blobfish) & self.has_building(Building.fish_pond)) | self.can_open_geode(Geode.artifact_trove), + Fish.periwinkle: self.can_crab_pot(Region.town), + ArtisanGood.pickles: self.has_pickle(), + Animal.pig: self.can_buy_animal(Animal.pig), + Beverage.pina_colada: self.can_spend_money_at(Region.island_resort, 600), + ArtisanGood.pine_tar: self.has(Machine.tapper), + Meal.pizza: self.can_spend_money_at(Region.saloon, 600), + Machine.preserves_jar: self.has_farming_level(4) & self.has(Material.wood) & self.has(Material.stone) & self.has(Material.coal), + Forageable.purple_mushroom: self.can_forage(Generic.any, Region.mines_floor_95) | self.can_forage(Generic.any, Region.skull_cavern_25), + Animal.rabbit: self.can_buy_animal(Animal.rabbit), + AnimalProduct.rabbit_foot: self.has_happy_animal(Animal.rabbit), + MetalBar.radioactive: self.can_smelt(Ore.radioactive), + Ore.radioactive: self.can_mine_perfectly() & self.can_reach_region(Region.qi_walnut_room), + Forageable.rainbow_shell: self.can_forage(Season.summer, Region.beach), + Craftable.rain_totem: self.has_skill_level(Skill.foraging, 9) & self.has(Material.hardwood) & self.has(ArtisanGood.truffle_oil) & self.has(ArtisanGood.pine_tar), + Machine.recycling_machine: self.has_skill_level(Skill.fishing, 4) & self.has(Material.wood) & self.has(Material.stone) & self.has(MetalBar.iron), + Forageable.red_mushroom: self.can_forage(Season.summer, Region.secret_woods) | self.can_forage(Season.fall, Region.secret_woods), + MetalBar.quartz: self.can_smelt("Quartz") | self.can_smelt("Fire Quartz") | + (self.has(Machine.recycling_machine) & (self.has(Trash.broken_cd) | self.has(Trash.broken_glasses))), + Ingredient.rice: self.can_spend_money_at(Region.pierre_store, 200) | ( + self.has_building(Building.mill) & self.has(Vegetable.unmilled_rice)), + AnimalProduct.roe: self.can_fish() & self.has_building(Building.fish_pond), + Meal.salad: self.can_spend_money_at(Region.saloon, 220), + # | (self.can_cook() & self.has_relationship(NPC.emily, 3) & self.has(Forageable.leek) & self.has(Forageable.dandelion) & + # self.has(Ingredient.vinegar)), + Forageable.salmonberry: self.can_forage(Season.spring), + Material.sap: self.can_chop_trees(), + Craftable.scarecrow: self.has_farming_level(1) & self.has(Material.wood) & self.has(Material.coal) & self.has(Material.fiber), + WaterItem.sea_urchin: self.can_forage(Generic.any, Region.tide_pools), + WaterItem.seaweed: (self.can_fish() & self.can_reach_region(Region.beach)) | self.can_reach_region( + Region.tide_pools), + Forageable.secret_note: self.received(Wallet.magnifying_glass) & (self.can_chop_trees() | self.can_mine_in_the_mines_floor_1_40()), + Machine.seed_maker: self.has_farming_level(9) & self.has(Material.wood) & self.has(MetalBar.gold) & self.has( + Material.coal), + Animal.sheep: self.can_buy_animal(Animal.sheep), + Fish.shrimp: self.can_crab_pot(Region.beach), + Loot.slime: self.can_mine_in_the_mines_floor_1_40(), + Weapon.any_slingshot: self.received(Weapon.slingshot) | self.received(Weapon.master_slingshot), + Fish.snail: self.can_crab_pot(Region.town), + Forageable.snow_yam: self.can_forage(Season.winter, Region.beach, True), + Trash.soggy_newspaper: self.can_crab_pot(), + Loot.solar_essence: self.can_mine_in_the_mines_floor_41_80() | self.can_mine_in_the_skull_cavern(), + Machine.solar_panel: self.received("Solar Panel Recipe") & self.has(MetalBar.quartz) & self.has( + MetalBar.iron) & self.has(MetalBar.gold), + Meal.spaghetti: self.can_spend_money_at(Region.saloon, 240), + Forageable.spice_berry: self.can_forage(Season.summer), + Forageable.spring_onion: self.can_forage(Season.spring), + AnimalProduct.squid_ink: self.can_mine_in_the_mines_floor_81_120() | (self.has_building(Building.fish_pond) & self.has(Fish.squid)), + Craftable.staircase: self.has_skill_level(Skill.mining, 2) & self.has(Material.stone), + Material.stone: self.has_tool(Tool.pickaxe), + Meal.strange_bun: self.has_relationship(NPC.shane, 7) & self.has(Ingredient.wheat_flour) & self.has(Fish.periwinkle) & self.has(ArtisanGood.void_mayonnaise), + AnimalProduct.sturgeon_roe: self.has(Fish.sturgeon) & self.has_building(Building.fish_pond), + Ingredient.sugar: self.can_spend_money_at(Region.pierre_store, 100) | ( + self.has_building(Building.mill) & self.has(Vegetable.beet)), + Forageable.sweet_pea: self.can_forage(Season.summer), + Machine.tapper: self.has_skill_level(Skill.foraging, 3) & self.has(Material.wood) & self.has(MetalBar.copper), + Vegetable.tea_leaves: self.has(Sapling.tea) & self.has_lived_months(2) & self.has_any_season_not_winter(), + Sapling.tea: self.has_relationship(NPC.caroline, 2) & self.has(Material.fiber) & self.has(Material.wood), + Trash.trash: self.can_crab_pot(), + Beverage.triple_shot_espresso: self.has("Hot Java Ring"), + ArtisanGood.truffle_oil: self.has(AnimalProduct.truffle) & self.has(Machine.oil_maker), + AnimalProduct.truffle: self.has_animal(Animal.pig) & self.has_any_season_not_winter(), + Ingredient.vinegar: self.can_spend_money_at(Region.pierre_store, 200), + AnimalProduct.void_egg: self.can_spend_money_at(Region.sewer, 5000) | (self.has_building(Building.fish_pond) & self.has(Fish.void_salmon)), + Loot.void_essence: self.can_mine_in_the_mines_floor_81_120() | self.can_mine_in_the_skull_cavern(), + ArtisanGood.void_mayonnaise: self.has(Machine.mayonnaise_machine) & self.has(AnimalProduct.void_egg), + Ingredient.wheat_flour: self.can_spend_money_at(Region.pierre_store, 100) | + (self.has_building(Building.mill) & self.has(Vegetable.wheat)), + WaterItem.white_algae: self.can_fish() & self.can_reach_region(Region.mines_floor_20), + Forageable.wild_horseradish: self.can_forage(Season.spring), + Forageable.wild_plum: self.can_forage(Season.fall), + Gift.wilted_bouquet: self.has(Machine.furnace) & self.has(Gift.bouquet) & self.has(Material.coal), + ArtisanGood.wine: self.has_wine(), + Forageable.winter_root: self.can_forage(Season.winter, Region.forest, True), + Material.wood: self.has_tool(Tool.axe), + AnimalProduct.wool: self.has_animal(Animal.rabbit) | self.has_animal(Animal.sheep), + Machine.worm_bin: self.has_skill_level(Skill.fishing, 8) & self.has(Material.hardwood) & self.has(MetalBar.gold) & self.has(MetalBar.iron) & self.has(Material.fiber), }) self.item_rules.update(self.fish_rules) self.item_rules.update(self.museum_rules) + self.item_rules.update(self.sapling_rules) self.item_rules.update(self.tree_fruit_rules) self.item_rules.update(self.seed_rules) self.item_rules.update(self.crop_rules) + # For some recipes, the cooked item can be obtained directly, so we either cook it or get it + for recipe in self.cooking_rules: + cooking_rule = self.cooking_rules[recipe] + obtention_rule = self.item_rules[recipe] if recipe in self.item_rules else False_() + self.item_rules[recipe] = obtention_rule | cooking_rule + self.building_rules.update({ - "Barn": self.can_spend_money(6000) & self.has(["Wood", "Stone"]), - "Big Barn": self.can_spend_money(12000) & self.has(["Wood", "Stone"]) & self.has_building("Barn"), - "Deluxe Barn": self.can_spend_money(25000) & self.has(["Wood", "Stone"]) & self.has_building("Big Barn"), - "Coop": self.can_spend_money(4000) & self.has(["Wood", "Stone"]), - "Big Coop": self.can_spend_money(10000) & self.has(["Wood", "Stone"]) & self.has_building("Coop"), - "Deluxe Coop": self.can_spend_money(20000) & self.has(["Wood", "Stone"]) & self.has_building("Big Coop"), - "Fish Pond": self.can_spend_money(5000) & self.has(["Stone", "Seaweed", "Green Algae"]), - "Mill": self.can_spend_money(2500) & self.has(["Stone", "Wood", "Cloth"]), - "Shed": self.can_spend_money(15000) & self.has("Wood"), - "Big Shed": self.can_spend_money(20000) & self.has(["Wood", "Stone"]) & self.has_building("Shed"), - "Silo": self.can_spend_money(100) & self.has(["Stone", "Clay", "Copper Bar"]), - "Slime Hutch": self.can_spend_money(10000) & self.has(["Stone", "Refined Quartz", "Iridium Bar"]), - "Stable": self.can_spend_money(10000) & self.has(["Hardwood", "Iron Bar"]), - "Well": self.can_spend_money(1000) & self.has("Stone"), - "Shipping Bin": self.can_spend_money(250) & self.has("Wood"), - "Kitchen": self.can_spend_money(10000) & self.has("Wood") & self.has_house(0), - "Kids Room": self.can_spend_money(50000) & self.has("Hardwood") & self.has_house(1), - "Cellar": self.can_spend_money(100000) & self.has_house(2), + Building.barn: self.can_spend_money_at(Region.carpenter, 6000) & self.has([Material.wood, Material.stone]), + Building.big_barn: self.can_spend_money_at(Region.carpenter, 12000) & self.has([Material.wood, Material.stone]) & self.has_building(Building.barn), + Building.deluxe_barn: self.can_spend_money_at(Region.carpenter, 25000) & self.has([Material.wood, Material.stone]) & self.has_building(Building.big_barn), + Building.coop: self.can_spend_money_at(Region.carpenter, 4000) & self.has([Material.wood, Material.stone]), + Building.big_coop: self.can_spend_money_at(Region.carpenter, 10000) & self.has([Material.wood, Material.stone]) & self.has_building(Building.coop), + Building.deluxe_coop: self.can_spend_money_at(Region.carpenter, 20000) & self.has([Material.wood, Material.stone]) & self.has_building(Building.big_coop), + Building.fish_pond: self.can_spend_money_at(Region.carpenter, 5000) & self.has([Material.stone, WaterItem.seaweed, WaterItem.green_algae]), + Building.mill: self.can_spend_money_at(Region.carpenter, 2500) & self.has([Material.stone, Material.wood, ArtisanGood.cloth]), + Building.shed: self.can_spend_money_at(Region.carpenter, 15000) & self.has(Material.wood), + Building.big_shed: self.can_spend_money_at(Region.carpenter, 20000) & self.has([Material.wood, Material.stone]) & self.has_building(Building.shed), + Building.silo: self.can_spend_money_at(Region.carpenter, 100) & self.has([Material.stone, Material.clay, MetalBar.copper]), + Building.slime_hutch: self.can_spend_money_at(Region.carpenter, 10000) & self.has([Material.stone, MetalBar.quartz, MetalBar.iridium]), + Building.stable: self.can_spend_money_at(Region.carpenter, 10000) & self.has([Material.hardwood, MetalBar.iron]), + Building.well: self.can_spend_money_at(Region.carpenter, 1000) & self.has(Material.stone), + Building.shipping_bin: self.can_spend_money_at(Region.carpenter, 250) & self.has(Material.wood), + Building.kitchen: self.can_spend_money_at(Region.carpenter, 10000) & self.has(Material.wood) & self.has_house(0), + Building.kids_room: self.can_spend_money_at(Region.carpenter, 50000) & self.has(Material.hardwood) & self.has_house(1), + Building.cellar: self.can_spend_money_at(Region.carpenter, 100000) & self.has_house(2), }) + self.building_rules.update(get_modded_building_rules(self, self.options[options.Mods])) + self.quest_rules.update({ - "Introductions": True_(), - "How To Win Friends": self.can_complete_quest("Introductions"), - "Getting Started": self.has("Parsnip") & self.has_tool("Hoe") & self.has_tool("Watering Can"), - "To The Beach": True_(), - "Raising Animals": self.can_complete_quest("Getting Started") & self.has_building("Coop"), - "Advancement": self.can_complete_quest("Getting Started") & self.has_skill_level("Farming", 1), - "Archaeology": self.has_tool("Hoe") | self.can_mine_in_the_mines_floor_1_40() | self.can_fish(), - "Meet The Wizard": True_() & self.can_reach_region(SVRegion.community_center), - "Forging Ahead": self.has("Copper Ore") & self.has("Furnace"), - "Smelting": self.has("Copper Bar"), - "Initiation": self.can_mine_in_the_mines_floor_1_40(), - "Robin's Lost Axe": self.has_season("Spring"), - "Jodi's Request": self.has_season("Spring") & self.has("Cauliflower"), - "Mayor's \"Shorts\"": self.has_season("Summer") & self.has_relationship("Marnie", 4), - "Blackberry Basket": self.has_season("Fall"), - "Marnie's Request": self.has_relationship("Marnie", 3) & self.has("Cave Carrot"), - "Pam Is Thirsty": self.has_season("Summer") & self.has("Pale Ale"), - "A Dark Reagent": self.has_season("Winter") & self.has("Void Essence"), - "Cow's Delight": self.has_season("Fall") & self.has("Amaranth"), - "The Skull Key": self.received("Skull Key") & self.can_reach_region(SVRegion.desert), - "Crop Research": self.has_season("Summer") & self.has("Melon"), - "Knee Therapy": self.has_season("Summer") & self.has("Hot Pepper"), - "Robin's Request": self.has_season("Winter") & self.has("Hardwood"), - "Qi's Challenge": self.can_mine_in_the_skull_cavern(), - "The Mysterious Qi": self.has("Battery Pack") & self.can_reach_region(SVRegion.desert) & self.has( - "Rainbow Shell") & self.has("Beet") & self.has("Solar Essence"), - "Carving Pumpkins": self.has_season("Fall") & self.has("Pumpkin"), - "A Winter Mystery": self.has_season("Winter"), - "Strange Note": self.received("Magnifying Glass") & self.can_reach_region(SVRegion.secret_woods) & self.has( - "Maple Syrup"), - "Cryptic Note": self.received("Magnifying Glass") & self.can_reach_region(SVRegion.perfect_skull_cavern), - "Fresh Fruit": self.has("Apricot"), - "Aquatic Research": self.has("Pufferfish"), - "A Soldier's Star": self.has_relationship("Kent") & self.has("Starfruit"), - "Mayor's Need": self.has("Truffle Oil"), - "Wanted: Lobster": self.has("Lobster"), - "Pam Needs Juice": self.has("Battery Pack"), - "Fish Casserole": self.has_relationship("Jodi", 4) & self.has("Largemouth Bass"), - "Catch A Squid": self.has("Squid"), - "Fish Stew": self.has("Albacore"), - "Pierre's Notice": self.has("Sashimi"), - "Clint's Attempt": self.has("Amethyst"), - "A Favor For Clint": self.has("Iron Bar"), - "Staff Of Power": self.has("Iridium Bar"), - "Granny's Gift": self.has("Leek"), - "Exotic Spirits": self.has("Coconut"), - "Catch a Lingcod": self.has("Lingcod"), + Quest.introductions: self.can_reach_region(Region.town), + Quest.how_to_win_friends: self.can_complete_quest(Quest.introductions), + Quest.getting_started: self.has(Vegetable.parsnip) & self.has_tool(Tool.hoe) & self.can_water(0), + Quest.to_the_beach: self.can_reach_region(Region.beach), + Quest.raising_animals: self.can_complete_quest(Quest.getting_started) & self.has_building(Building.coop), + Quest.advancement: self.can_complete_quest(Quest.getting_started) & self.has(Craftable.scarecrow), + Quest.archaeology: (self.has_tool(Tool.hoe) | self.can_mine_in_the_mines_floor_1_40() | self.can_fish()) & self.can_reach_region(Region.museum), + Quest.meet_the_wizard: self.can_reach_region(Region.town) & self.can_reach_region(Region.community_center) & self.can_reach_region(Region.wizard_tower), + Quest.forging_ahead: self.has(Ore.copper) & self.has(Machine.furnace), + Quest.smelting: self.has(MetalBar.copper), + Quest.initiation: self.can_mine_in_the_mines_floor_1_40(), + Quest.robins_lost_axe: self.has_season(Season.spring) & self.can_reach_region(Region.forest) & self.can_meet(NPC.robin), + Quest.jodis_request: self.has_season(Season.spring) & self.has(Vegetable.cauliflower) & self.can_meet(NPC.jodi), + Quest.mayors_shorts: self.has_season(Season.summer) & self.can_reach_region(Region.ranch) & + (self.has_relationship(NPC.marnie, 2) | (magic.can_blink(self))) & self.can_meet(NPC.lewis), + Quest.blackberry_basket: self.has_season(Season.fall) & self.can_meet(NPC.linus), + Quest.marnies_request: self.has_relationship(NPC.marnie, 3) & self.has(Forageable.cave_carrot) & self.can_reach_region(Region.ranch), + Quest.pam_is_thirsty: self.has_season(Season.summer) & self.has(ArtisanGood.pale_ale) & self.can_meet(NPC.pam), + Quest.a_dark_reagent: self.has_season(Season.winter) & self.has(Loot.void_essence) & self.can_meet(NPC.wizard), + Quest.cows_delight: self.has_season(Season.fall) & self.has(Vegetable.amaranth) & self.can_meet(NPC.marnie), + Quest.the_skull_key: self.received(Wallet.skull_key) & self.can_reach_region(Region.skull_cavern_entrance), + Quest.crop_research: self.has_season(Season.summer) & self.has(Fruit.melon) & self.can_meet(NPC.demetrius), + Quest.knee_therapy: self.has_season(Season.summer) & self.has(Fruit.hot_pepper) & self.can_meet(NPC.george), + Quest.robins_request: self.has_season(Season.winter) & self.has(Material.hardwood) & self.can_meet(NPC.robin), + Quest.qis_challenge: self.can_mine_in_the_skull_cavern(), + Quest.the_mysterious_qi: self.can_reach_region(Region.bus_tunnel) & self.has(ArtisanGood.battery_pack) & self.can_reach_region(Region.desert) & self.has(Forageable.rainbow_shell) & self.has(Vegetable.beet) & self.has(Loot.solar_essence), + Quest.carving_pumpkins: self.has_season(Season.fall) & self.has(Vegetable.pumpkin) & self.can_meet(NPC.caroline), + Quest.a_winter_mystery: self.has_season(Season.winter) & self.can_reach_region(Region.town), + Quest.strange_note: self.has(Forageable.secret_note) & self.can_reach_region(Region.secret_woods) & self.has(ArtisanGood.maple_syrup), + Quest.cryptic_note: self.has(Forageable.secret_note) & self.can_reach_region(Region.skull_cavern_100), + Quest.fresh_fruit: self.has_season(Season.spring) & self.has(Fruit.apricot) & self.can_meet(NPC.emily), + Quest.aquatic_research: self.has_season(Season.summer) & self.has(Fish.pufferfish) & self.can_meet(NPC.demetrius), + Quest.a_soldiers_star: self.has_season(Season.summer) & self.has_year_two() & self.has(Fruit.starfruit) & self.can_meet(NPC.kent), + Quest.mayors_need: self.has_season(Season.summer) & self.has(ArtisanGood.truffle_oil) & self.can_meet(NPC.lewis), + Quest.wanted_lobster: self.has_season(Season.fall) & self.has_season(Season.fall) & self.has(Fish.lobster) & self.can_meet(NPC.gus), + Quest.pam_needs_juice: self.has_season(Season.fall) & self.has(ArtisanGood.battery_pack) & self.can_meet(NPC.pam), + Quest.fish_casserole: self.has_relationship(NPC.jodi, 4) & self.has(Fish.largemouth_bass) & self.can_reach_region(Region.sam_house), + Quest.catch_a_squid: self.has_season(Season.winter) & self.has(Fish.squid) & self.can_meet(NPC.willy), + Quest.fish_stew: self.has_season(Season.winter) & self.has(Fish.albacore) & self.can_meet(NPC.gus), + Quest.pierres_notice: self.has_season(Season.spring) & self.has("Sashimi") & self.can_meet(NPC.pierre), + Quest.clints_attempt: self.has_season(Season.winter) & self.has(Mineral.amethyst) & self.can_meet(NPC.emily), + Quest.a_favor_for_clint: self.has_season(Season.winter) & self.has(MetalBar.iron) & self.can_meet(NPC.clint), + Quest.staff_of_power: self.has_season(Season.winter) & self.has(MetalBar.iridium) & self.can_meet(NPC.wizard), + Quest.grannys_gift: self.has_season(Season.spring) & self.has(Forageable.leek) & self.can_meet(NPC.evelyn), + Quest.exotic_spirits: self.has_season(Season.winter) & self.has(Forageable.coconut) & self.can_meet(NPC.gus), + Quest.catch_a_lingcod: self.has_season(Season.winter) & self.has("Lingcod") & self.can_meet(NPC.willy), + Quest.dark_talisman: self.has_rusty_key() & self.can_reach_region(Region.railroad) & self.can_meet(NPC.krobus) & self.can_reach_region(Region.mutant_bug_lair), + Quest.goblin_problem: self.can_reach_region(Region.witch_swamp) & self.has(ArtisanGood.void_mayonnaise), + Quest.magic_ink: self.can_reach_region(Region.witch_hut) & self.can_meet(NPC.wizard), + Quest.the_pirates_wife: self.can_reach_region(Region.island_west) & self.can_meet(NPC.kent) & + self.can_meet(NPC.gus) & self.can_meet(NPC.sandy) & self.can_meet(NPC.george) & + self.can_meet(NPC.wizard) & self.can_meet(NPC.willy), }) + self.quest_rules.update(get_modded_quest_rules(self, self.options[options.Mods])) + + self.festival_rules.update({ + FestivalCheck.egg_hunt: self.has_season(Season.spring) & self.can_reach_region(Region.town) & self.can_win_egg_hunt(), + FestivalCheck.strawberry_seeds: self.has_season(Season.spring) & self.can_reach_region(Region.town) & self.can_spend_money(1000), + FestivalCheck.dance: self.has_season(Season.spring) & self.can_reach_region(Region.forest) & self.has_relationship(Generic.bachelor, 4), + FestivalCheck.rarecrow_5: self.has_season(Season.spring) & self.can_reach_region(Region.forest) & self.can_spend_money(2500), + FestivalCheck.luau_soup: self.has_season(Season.summer) & self.can_reach_region(Region.beach) & self.can_succeed_luau_soup(), + FestivalCheck.moonlight_jellies: self.has_season(Season.summer) & self.can_reach_region(Region.beach), + FestivalCheck.smashing_stone: self.has_season(Season.fall) & self.can_reach_region(Region.town), + FestivalCheck.grange_display: self.has_season(Season.fall) & self.can_reach_region(Region.town) & self.can_succeed_grange_display(), + FestivalCheck.rarecrow_1: self.has_season(Season.fall) & self.can_reach_region(Region.town), # only cost star tokens + FestivalCheck.fair_stardrop: self.has_season(Season.fall) & self.can_reach_region(Region.town), # only cost star tokens + FestivalCheck.spirit_eve_maze: self.has_season(Season.fall) & self.can_reach_region(Region.town), + FestivalCheck.rarecrow_2: self.has_season(Season.fall) & self.can_reach_region(Region.town) & self.can_spend_money(5000), + FestivalCheck.fishing_competition: self.has_season(Season.winter) & self.can_reach_region(Region.forest) & self.can_win_fishing_competition(), + FestivalCheck.rarecrow_4: self.has_season(Season.winter) & self.can_reach_region(Region.forest) & self.can_spend_money(5000), + FestivalCheck.mermaid_pearl: self.has_season(Season.winter) & self.can_reach_region(Region.beach), + FestivalCheck.cone_hat: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(2500), + FestivalCheck.iridium_fireplace: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(15000), + FestivalCheck.rarecrow_7: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(5000) & self.can_find_museum_artifacts(20), + FestivalCheck.rarecrow_8: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(5000) & self.can_find_museum_items(40), + FestivalCheck.lupini_red_eagle: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(1200), + FestivalCheck.lupini_portrait_mermaid: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(1200), + FestivalCheck.lupini_solar_kingdom: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.can_spend_money(1200), + FestivalCheck.lupini_clouds: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.has_year_two() & self.can_spend_money(1200), + FestivalCheck.lupini_1000_years: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.has_year_two() & self.can_spend_money(1200), + FestivalCheck.lupini_three_trees: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.has_year_two() & self.can_spend_money(1200), + FestivalCheck.lupini_the_serpent: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.has_year_three() & self.can_spend_money(1200), + FestivalCheck.lupini_tropical_fish: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.has_year_three() & self.can_spend_money(1200), + FestivalCheck.lupini_land_of_clay: self.has_season(Season.winter) & self.can_reach_region(Region.beach) & self.has_year_three() & self.can_spend_money(1200), + FestivalCheck.secret_santa: self.has_season(Season.winter) & self.can_reach_region(Region.town) & self.has_any_universal_love(), + FestivalCheck.legend_of_the_winter_star: self.has_season(Season.winter) & self.can_reach_region(Region.town), + FestivalCheck.all_rarecrows: self.can_reach_region(Region.farm) & self.has_all_rarecrows(), + }) + + self.special_order_rules.update({ + SpecialOrder.island_ingredients: self.has_island_transport() & self.can_farm_perfectly() & + self.has(Vegetable.taro_root) & self.has(Fruit.pineapple) & self.has(Forageable.ginger), + SpecialOrder.cave_patrol: self.can_mine_perfectly() & self.can_mine_to_floor(120), + SpecialOrder.aquatic_overpopulation: self.can_fish_perfectly(), + SpecialOrder.biome_balance: self.can_fish_perfectly(), + SpecialOrder.rock_rejuivenation: self.has(Mineral.ruby) & self.has(Mineral.topaz) & self.has(Mineral.emerald) & + self.has(Mineral.jade) & self.has(Mineral.amethyst) & self.has_relationship(NPC.emily, 4) & + self.has(ArtisanGood.cloth) & self.can_reach_region(Region.haley_house), + SpecialOrder.gifts_for_george: self.has_season(Season.spring) & self.has(Forageable.leek), + SpecialOrder.fragments_of_the_past: self.can_reach_region(Region.dig_site), + SpecialOrder.gus_famous_omelet: self.has(AnimalProduct.any_egg), + SpecialOrder.crop_order: self.can_farm_perfectly(), + SpecialOrder.community_cleanup: self.can_crab_pot(), + SpecialOrder.the_strong_stuff: self.can_keg(Vegetable.potato), + SpecialOrder.pierres_prime_produce: self.can_farm_perfectly(), + SpecialOrder.robins_project: self.can_chop_perfectly() & self.has(Material.hardwood), + SpecialOrder.robins_resource_rush: self.can_chop_perfectly() & self.has(Fertilizer.tree) & self.can_mine_perfectly(), + SpecialOrder.juicy_bugs_wanted_yum: self.has(Loot.bug_meat), + SpecialOrder.tropical_fish: self.has_island_transport() & self.has(Fish.stingray) & self.has(Fish.blue_discus) & self.has(Fish.lionfish), + SpecialOrder.a_curious_substance: self.can_mine_perfectly() & self.can_mine_to_floor(80), + SpecialOrder.prismatic_jelly: self.can_mine_perfectly() & self.can_mine_to_floor(40), + SpecialOrder.qis_crop: self.can_farm_perfectly() & self.can_reach_region(Region.greenhouse) & + self.can_reach_region(Region.island_west) & self.has_total_skill_level(50) & + self.has(Machine.seed_maker), + SpecialOrder.lets_play_a_game: self.has_junimo_kart_max_level(), + SpecialOrder.four_precious_stones: self.has_lived_months(MAX_MONTHS) & self.has("Prismatic Shard") & + self.can_mine_perfectly_in_the_skull_cavern(), + SpecialOrder.qis_hungry_challenge: self.can_mine_perfectly_in_the_skull_cavern() & self.has_max_buffs(), + SpecialOrder.qis_cuisine: self.can_cook() & (self.can_spend_money_at(Region.saloon, 205000) | self.can_spend_money_at(Region.pierre_store, 170000)), + SpecialOrder.qis_kindness: self.can_give_loved_gifts_to_everyone(), + SpecialOrder.extended_family: self.can_fish_perfectly() & self.has(Fish.angler) & self.has(Fish.glacierfish) & + self.has(Fish.crimsonfish) & self.has(Fish.mutant_carp) & self.has(Fish.legend), + SpecialOrder.danger_in_the_deep: self.can_mine_perfectly() & self.has_mine_elevator_to_floor(120), + SpecialOrder.skull_cavern_invasion: self.can_mine_perfectly_in_the_skull_cavern() & self.has_max_buffs(), + SpecialOrder.qis_prismatic_grange: self.has(Loot.bug_meat) & # 100 Bug Meat + self.can_spend_money_at(Region.saloon, 24000) & # 100 Spaghetti + self.can_spend_money_at(Region.blacksmith, 15000) & # 100 Copper Ore + self.can_spend_money_at(Region.ranch, 5000) & # 100 Hay + self.can_spend_money_at(Region.saloon, 22000) & # 100 Salads + self.can_spend_money_at(Region.saloon, 7500) & # 100 Joja Cola + self.can_spend_money(80000), # I need this extra rule because money rules aren't additive... + }) + + self.special_order_rules.update(get_modded_special_orders_rules(self, self.options[options.Mods])) + def has(self, items: Union[str, (Iterable[str], Sized)], count: Optional[int] = None) -> StardewRule: if isinstance(items, str): return Has(items, self.item_rules) @@ -534,6 +575,12 @@ class StardewLogic: def can_reach_all_regions(self, spots: Iterable[str]) -> StardewRule: return And(self.can_reach_region(spot) for spot in spots) + def can_reach_all_regions_except_one(self, spots: Iterable[str]) -> StardewRule: + num_required = len(list(spots)) - 1 + if num_required <= 0: + num_required = len(list(spots)) + return Count(num_required, [self.can_reach_region(spot) for spot in spots]) + def can_reach_location(self, spot: str) -> StardewRule: return Reach(spot, "Location", self.player) @@ -544,47 +591,85 @@ class StardewLogic: return self.has_lived_months(min(8, amount // MONEY_PER_MONTH)) def can_spend_money(self, amount: int) -> StardewRule: + if self.options[options.StartingMoney] == -1: + return True_() return self.has_lived_months(min(8, amount // (MONEY_PER_MONTH // 5))) - def has_tool(self, tool: str, material: str = "Basic") -> StardewRule: - if material == "Basic": + def can_spend_money_at(self, region: str, amount: int) -> StardewRule: + return self.can_reach_region(region) & self.can_spend_money(amount) + + def has_tool(self, tool: str, material: str = ToolMaterial.basic) -> StardewRule: + if material == ToolMaterial.basic or tool == Tool.scythe: return True_() if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: return self.received(f"Progressive {tool}", count=tool_materials[material]) - return self.has(f"{material} Bar") & self.can_spend_money(tool_prices[material]) + return self.has(f"{material} Bar") & self.can_spend_money(tool_upgrade_prices[material]) + + def can_earn_skill_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.has_lived_months(months) + previous_level_rule = self.has_skill_level(skill, level - 1) + + if skill == Skill.fishing: + xp_rule = self.can_get_fishing_xp() & self.has_tool(Tool.fishing_rod, ToolMaterial.tiers[max(tool_level, 3)]) + elif skill == Skill.farming: + xp_rule = self.can_get_farming_xp() & self.has_tool(Tool.hoe, tool_material) & self.can_water(tool_level) + elif skill == Skill.foraging: + xp_rule = self.can_get_foraging_xp() & \ + (self.has_tool(Tool.axe, tool_material) | magic.can_use_clear_debris_instead_of_tool_level(self, tool_level)) + elif skill == Skill.mining: + xp_rule = self.can_get_mining_xp() & \ + (self.has_tool(Tool.pickaxe, tool_material) | magic.can_use_clear_debris_instead_of_tool_level(self, tool_level)) + elif skill == Skill.combat: + combat_tier = Performance.tiers[tool_level] + xp_rule = self.can_get_combat_xp() & self.can_do_combat_at_level(combat_tier) + else: + xp_rule = skills.can_earn_mod_skill_level(self, skill, level) + + return previous_level_rule & months_rule & xp_rule def has_skill_level(self, skill: str, level: int) -> StardewRule: - if level == 0: + if level <= 0: return True_() if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: return self.received(f"{skill} Level", count=level) - if skill == "Fishing" and self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - return self.can_get_fishing_xp() + return self.can_earn_skill_level(skill, level) - return self.has_lived_months(month_end_per_skill_level[(skill, level)]) + def has_farming_level(self, level: int) -> StardewRule: + return self.has_skill_level(Skill.farming, level) - def has_total_skill_level(self, level: int) -> StardewRule: - if level == 0: + def has_total_skill_level(self, level: int, allow_modded_skills: bool = False) -> StardewRule: + if level <= 0: return True_() if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: skills_items = ["Farming Level", "Mining Level", "Foraging Level", "Fishing Level", "Combat Level"] + if allow_modded_skills: + skills.append_mod_skill_level(skills_items, self.options) return self.received(skills_items, count=level) - if level > 40 and self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - return self.has_lived_months(month_end_per_total_level[level]) & self.can_get_fishing_xp() - - return self.has_lived_months(month_end_per_total_level[level]) + months_with_4_skills = max(1, (level // 4) - 1) + months_with_5_skills = max(1, (level // 5) - 1) + rule_with_fishing = self.has_lived_months(months_with_5_skills) & self.can_get_fishing_xp() + if level > 40: + return rule_with_fishing + return self.has_lived_months(months_with_4_skills) | rule_with_fishing def has_building(self, building: str) -> StardewRule: + carpenter_rule = self.can_reach_region(Region.carpenter) if not self.options[options.BuildingProgression] == options.BuildingProgression.option_vanilla: count = 1 - if building in ["Coop", "Barn", "Shed"]: + if building in [Building.coop, Building.barn, Building.shed]: building = f"Progressive {building}" elif building.startswith("Big"): count = 2 @@ -592,9 +677,9 @@ class StardewLogic: elif building.startswith("Deluxe"): count = 3 building = " ".join(["Progressive", *building.split(" ")[1:]]) - return self.received(f"{building}", count) + return self.received(f"{building}", count) & carpenter_rule - return Has(building, self.building_rules) + return Has(building, self.building_rules) & carpenter_rule def has_house(self, upgrade_level: int) -> StardewRule: if upgrade_level < 1: @@ -604,128 +689,252 @@ class StardewLogic: return False_() if not self.options[options.BuildingProgression] == options.BuildingProgression.option_vanilla: - return self.received(f"Progressive House", upgrade_level) + return self.received(f"Progressive House", upgrade_level) & self.can_reach_region(Region.carpenter) if upgrade_level == 1: - return Has("Kitchen", self.building_rules) + return Has(Building.kitchen, self.building_rules) if upgrade_level == 2: - return Has("Kids Room", self.building_rules) + return Has(Building.kids_room, self.building_rules) # if upgrade_level == 3: - return Has("Cellar", self.building_rules) + return Has(Building.cellar, self.building_rules) def can_complete_quest(self, quest: str) -> StardewRule: return Has(quest, self.quest_rules) + def can_complete_special_order(self, specialorder: str) -> StardewRule: + return Has(specialorder, self.special_order_rules) + + def can_get_farming_xp(self) -> StardewRule: + crop_rules = [] + for crop in all_crops: + crop_rules.append(self.can_grow_crop(crop)) + return Or(crop_rules) + + def can_get_foraging_xp(self) -> StardewRule: + tool_rule = self.has_tool(Tool.axe) + tree_rule = self.can_reach_region(Region.forest) & self.has_any_season_not_winter() + stump_rule = self.can_reach_region(Region.secret_woods) & self.has_tool(Tool.axe, ToolMaterial.copper) + return tool_rule & (tree_rule | stump_rule) + + def can_get_mining_xp(self) -> StardewRule: + tool_rule = self.has_tool(Tool.pickaxe) + stone_rule = self.can_reach_any_region([Region.mines_floor_5, Region.quarry, Region.skull_cavern_25, Region.volcano_floor_5]) + return tool_rule & stone_rule + + def can_get_combat_xp(self) -> StardewRule: + tool_rule = self.has_any_weapon() + enemy_rule = self.can_reach_any_region([Region.mines_floor_5, Region.skull_cavern_25, Region.volcano_floor_5]) + return tool_rule & enemy_rule + def can_get_fishing_xp(self) -> StardewRule: if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: return self.can_fish() | self.can_crab_pot() return self.can_fish() - def has_max_fishing_rod(self) -> StardewRule: - if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - return self.received("Progressive Fishing Rod", 4) - return self.can_get_fishing_xp() - def can_fish(self, difficulty: int = 0) -> StardewRule: skill_required = max(0, int((difficulty / 10) - 1)) if difficulty <= 40: skill_required = 0 - skill_rule = self.has_skill_level("Fishing", skill_required) + skill_rule = self.has_skill_level(Skill.fishing, skill_required) + region_rule = self.can_reach_any_region(fishing_regions) number_fishing_rod_required = 1 if difficulty < 50 else 2 if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - return self.received("Progressive Fishing Rod", number_fishing_rod_required) & skill_rule + return self.received("Progressive Fishing Rod", number_fishing_rod_required) & skill_rule & region_rule - return skill_rule + return skill_rule & region_rule + + def can_fish_in_freshwater(self) -> StardewRule: + return self.can_fish() & self.can_reach_any_region([Region.forest, Region.town, Region.mountain]) def has_max_fishing(self) -> StardewRule: - skill_rule = self.has_skill_level("Fishing", 10) + skill_rule = self.has_skill_level(Skill.fishing, 10) return self.has_max_fishing_rod() & skill_rule def can_fish_chests(self) -> StardewRule: - skill_rule = self.has_skill_level("Fishing", 4) + skill_rule = self.has_skill_level(Skill.fishing, 4) return self.has_max_fishing_rod() & skill_rule - def can_buy_seed(self, seed: SeedItem): - if self.options[options.SeedShuffle] == options.SeedShuffle.option_disabled or seed.name == "Rare Seed": + def can_buy_seed(self, seed: SeedItem) -> StardewRule: + if self.options[options.Cropsanity] == options.Cropsanity.option_disabled: item_rule = True_() else: item_rule = self.received(seed.name) season_rule = self.has_any_season(seed.seasons) - region_rule = self.can_reach_any_region(seed.regions) - return season_rule & region_rule & item_rule + region_rule = self.can_reach_all_regions(seed.regions) + currency_rule = self.can_spend_money(1000) + if seed.name == Seed.pineapple: + currency_rule = self.has(Forageable.magma_cap) + if seed.name == Seed.taro: + currency_rule = self.has(Fossil.bone_fragment) + return season_rule & region_rule & item_rule & currency_rule - def can_grow_crop(self, crop: CropItem): + 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[options.Cropsanity] == options.Cropsanity.option_disabled: + allowed_buy_sapling = True_() + else: + allowed_buy_sapling = received_sapling + can_buy_sapling = self.can_spend_money_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_grow_crop(self, crop: CropItem) -> StardewRule: season_rule = self.has_any_season(crop.farm_growth_seasons) seed_rule = self.has(crop.seed.name) - farm_rule = self.can_reach_region(SVRegion.farm) & season_rule - region_rule = farm_rule | self.can_reach_region(SVRegion.greenhouse) - return seed_rule & region_rule + farm_rule = self.can_reach_region(Region.farm) & season_rule + tool_rule = self.has_tool(Tool.hoe) & self.has_tool(Tool.watering_can) + region_rule = farm_rule | self.can_reach_region(Region.greenhouse) | self.can_reach_region(Region.island_west) + 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.has_any_season(seasons) | self.can_reach_region(Region.greenhouse) | self.has_island_farm() + farm_rule = self.can_reach_region(Region.farm) | self.can_reach_region( + Region.greenhouse) | self.has_island_farm() + return season_rule & farm_rule + + def has_island_farm(self) -> StardewRule: + return self.can_reach_region(Region.island_south) def can_catch_fish(self, fish: FishItem) -> StardewRule: region_rule = self.can_reach_any_region(fish.locations) season_rule = self.has_any_season(fish.seasons) - difficulty_rule = self.can_fish(fish.difficulty) if fish.difficulty == -1: difficulty_rule = self.can_crab_pot() + else: + difficulty_rule = self.can_fish(fish.difficulty) return region_rule & season_rule & difficulty_rule def can_catch_every_fish(self) -> StardewRule: - rules = [self.has_skill_level("Fishing", 10), self.has_max_fishing_rod()] + rules = [self.has_skill_level(Skill.fishing, 10), self.has_max_fishing_rod()] for fish in all_fish: + if self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true and \ + fish in island_fish: + continue rules.append(self.can_catch_fish(fish)) return And(rules) def has_max_fishing_rod(self) -> StardewRule: if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - return self.received("Progressive Fishing Rod", 4) + return self.received(APTool.fishing_rod, 4) return self.can_get_fishing_xp() - def can_cook(self) -> StardewRule: - return self.has_house(1) or self.has_skill_level("Foraging", 9) + def can_cook(self, recipe: CookingRecipe = None) -> StardewRule: + cook_rule = self.has_house(1) | self.has_skill_level(Skill.foraging, 9) + if recipe is None: + return cook_rule + + learn_rule = self.can_learn_recipe(recipe.source) + ingredients_rule = And([self.has(ingredient) for ingredient in recipe.ingredients]) + number_ingredients = sum(recipe.ingredients[ingredient] for ingredient in recipe.ingredients) + time_rule = self.has_lived_months(number_ingredients) + return cook_rule & learn_rule & ingredients_rule & time_rule + + def can_learn_recipe(self, source: RecipeSource) -> StardewRule: + if isinstance(source, StarterSource): + return True_() + if isinstance(source, ShopSource): + return self.can_spend_money_at(source.region, source.price) + if isinstance(source, SkillSource): + return self.has_skill_level(source.skill, source.level) + if isinstance(source, FriendshipSource): + return self.has_relationship(source.friend, source.hearts) + if isinstance(source, QueenOfSauceSource): + year_rule = self.has_year_two() if source.year == 2 else self.has_year_three() + return self.can_watch(Channel.queen_of_sauce) & self.has_season(source.season) & year_rule + + return False_() + + def can_watch(self, channel: str = None): + tv_rule = True_() + if channel is None: + return tv_rule + return self.received(channel) & tv_rule def can_smelt(self, item: str) -> StardewRule: - return self.has("Furnace") & self.has(item) + return self.has(Machine.furnace) & self.has(item) - def can_crab_pot(self) -> StardewRule: - if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: - return self.has("Crab Pot") - - return True_() - - def can_do_panning(self) -> StardewRule: + def can_do_panning(self, item: str = Generic.any) -> StardewRule: return self.received("Glittering Boulder Removed") + def can_crab_pot(self, region: str = Generic.any) -> StardewRule: + crab_pot_rule = self.has(Craftable.bait) + if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: + crab_pot_rule = crab_pot_rule & self.has(Machine.crab_pot) + else: + crab_pot_rule = crab_pot_rule & self.can_get_fishing_xp() + + if region != Generic.any: + return crab_pot_rule & self.can_reach_region(region) + + water_region_rules = self.can_reach_any_region(fishing_regions) + return crab_pot_rule & water_region_rules + # Regions def can_mine_in_the_mines_floor_1_40(self) -> StardewRule: - return self.can_reach_region(SVRegion.mines_floor_5) + return self.can_reach_region(Region.mines_floor_5) def can_mine_in_the_mines_floor_41_80(self) -> StardewRule: - return self.can_reach_region(SVRegion.mines_floor_45) + return self.can_reach_region(Region.mines_floor_45) def can_mine_in_the_mines_floor_81_120(self) -> StardewRule: - return self.can_reach_region(SVRegion.mines_floor_85) + return self.can_reach_region(Region.mines_floor_85) def can_mine_in_the_skull_cavern(self) -> StardewRule: return (self.can_progress_in_the_mines_from_floor(120) & - self.can_reach_region(SVRegion.skull_cavern)) + self.can_reach_region(Region.skull_cavern)) + + def can_mine_perfectly(self) -> StardewRule: + return self.can_progress_in_the_mines_from_floor(160) def can_mine_perfectly_in_the_skull_cavern(self) -> StardewRule: - return (self.can_progress_in_the_mines_from_floor(160) & - self.can_reach_region(SVRegion.skull_cavern)) + return (self.can_mine_perfectly() & + self.can_reach_region(Region.skull_cavern)) + + def can_farm_perfectly(self) -> StardewRule: + tool_rule = self.has_tool(Tool.hoe, ToolMaterial.iridium) & self.can_water(4) + return tool_rule & self.has_farming_level(10) + + def can_fish_perfectly(self) -> StardewRule: + skill_rule = self.has_skill_level(Skill.fishing, 10) + return skill_rule & self.has_max_fishing_rod() + + def can_chop_trees(self) -> StardewRule: + return self.has_tool(Tool.axe) & self.can_reach_region(Region.forest) + + def can_chop_perfectly(self) -> StardewRule: + magic_rule = (magic.can_use_clear_debris_instead_of_tool_level(self, 3)) & self.has_skill_level(ModSkill.magic, 10) + tool_rule = self.has_tool(Tool.axe, ToolMaterial.iridium) + foraging_rule = self.has_skill_level(Skill.foraging, 10) + region_rule = self.can_reach_region(Region.forest) + return region_rule & ((tool_rule & foraging_rule) | magic_rule) + + def has_max_buffs(self) -> StardewRule: + number_of_movement_buffs: int = self.options[options.NumberOfMovementBuffs] + number_of_luck_buffs: int = self.options[options.NumberOfLuckBuffs] + return self.received(Buff.movement, number_of_movement_buffs) & self.received(Buff.luck, number_of_luck_buffs) def get_weapon_rule_for_floor_tier(self, tier: int): if tier >= 4: - return self.has_galaxy_weapon() + return self.can_do_combat_at_level(Performance.galaxy) if tier >= 3: - return self.has_great_weapon() + return self.can_do_combat_at_level(Performance.great) if tier >= 2: - return self.has_good_weapon() + return self.can_do_combat_at_level(Performance.good) if tier >= 1: - return self.has_decent_weapon() - return self.has_any_weapon() + return self.can_do_combat_at_level(Performance.decent) + return self.can_do_combat_at_level(Performance.basic) def can_progress_in_the_mines_from_floor(self, floor: int) -> StardewRule: tier = int(floor / 40) @@ -733,10 +942,10 @@ class StardewLogic: weapon_rule = self.get_weapon_rule_for_floor_tier(tier) rules.append(weapon_rule) if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - rules.append(self.received("Progressive Pickaxe", tier)) + rules.append(self.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier])) if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: combat_tier = min(10, max(0, tier * 2)) - rules.append(self.has_skill_level("Combat", combat_tier)) + rules.append(self.has_skill_level(Skill.combat, combat_tier)) return And(rules) def can_progress_easily_in_the_mines_from_floor(self, floor: int) -> StardewRule: @@ -745,17 +954,14 @@ class StardewLogic: weapon_rule = self.get_weapon_rule_for_floor_tier(tier) rules.append(weapon_rule) if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: - rules.append(self.received("Progressive Pickaxe", count=tier)) + rules.append(self.has_tool(Tool.pickaxe, ToolMaterial.tiers[tier])) if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: combat_tier = min(10, max(0, tier * 2)) - rules.append(self.has_skill_level("Combat", combat_tier)) + rules.append(self.has_skill_level(Skill.combat, combat_tier)) return And(rules) def has_mine_elevator_to_floor(self, floor: int) -> StardewRule: - if (self.options[options.TheMinesElevatorsProgression] == - options.TheMinesElevatorsProgression.option_progressive or - self.options[options.TheMinesElevatorsProgression] == - options.TheMinesElevatorsProgression.option_progressive_from_previous_floor): + if self.options[options.ElevatorProgression] != options.ElevatorProgression.option_vanilla: return self.received("Progressive Mine Elevator", count=int(floor / 5)) return True_() @@ -767,6 +973,32 @@ class StardewLogic: (self.has_mine_elevator_to_floor(previous_previous_elevator) & self.can_progress_easily_in_the_mines_from_floor(previous_previous_elevator))) + def can_progress_in_the_skull_cavern_from_floor(self, floor: int) -> StardewRule: + tier = floor // 50 + rules = [] + weapon_rule = self.has_great_weapon() + rules.append(weapon_rule) + rules.append(self.can_cook()) + if self.options[options.ToolProgression] == options.ToolProgression.option_progressive: + rules.append(self.received("Progressive Pickaxe", min(4, max(0, tier + 2)))) + if self.options[options.SkillProgression] == options.SkillProgression.option_progressive: + skill_tier = min(10, max(0, tier * 2 + 6)) + rules.extend({self.has_skill_level(Skill.combat, skill_tier), + self.has_skill_level(Skill.mining, skill_tier)}) + return And(rules) + + def can_progress_easily_in_the_skull_cavern_from_floor(self, floor: int) -> StardewRule: + return self.can_progress_in_the_skull_cavern_from_floor(floor + 50) + + def can_mine_to_skull_cavern_floor(self, floor: int) -> StardewRule: + previous_elevator = max(floor - 25, 0) + previous_previous_elevator = max(floor - 50, 0) + has_mine_elevator = self.has_mine_elevator_to_floor(5) # Skull Cavern Elevator menu needs a normal elevator... + return ((has_skull_cavern_elevator_to_floor(self, previous_elevator) & + self.can_progress_in_the_skull_cavern_from_floor(previous_elevator)) | + (has_skull_cavern_elevator_to_floor(self, previous_previous_elevator) & + self.can_progress_easily_in_the_skull_cavern_from_floor(previous_previous_elevator))) & has_mine_elevator + def has_jotpk_power_level(self, power_level: int) -> StardewRule: if self.options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling: return True_() @@ -779,15 +1011,29 @@ class StardewLogic: return True_() return self.received("Junimo Kart: Extra Life", power_level) + def has_junimo_kart_max_level(self) -> StardewRule: + play_rule = self.can_reach_region(Region.junimo_kart_3) + if self.options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling: + return play_rule + return self.has_junimo_kart_power_level(8) + def has_traveling_merchant(self, tier: int = 1): - traveling_merchant_days = [f"Traveling Merchant: {day}" for day in week_days] + traveling_merchant_days = [f"Traveling Merchant: {day}" for day in Weekday.all_days] return self.received(traveling_merchant_days, tier) def can_get_married(self) -> StardewRule: - return self.can_reach_region(SVRegion.tide_pools) & self.has_relationship("Bachelor", 10) & self.has_house(1) + return self.has_relationship(Generic.bachelor, 10) & self.has(Gift.mermaid_pendant) - def can_have_two_children(self) -> StardewRule: - return self.can_get_married() & self.has_house(2) & self.has_relationship("Bachelor", 12) + def has_children(self, number_children: int) -> StardewRule: + if number_children <= 0: + return True_() + possible_kids = ["Cute Baby", "Ugly Baby"] + return self.received(possible_kids, number_children) & self.has_house(2) + + def can_reproduce(self, number_children: int = 1) -> StardewRule: + if number_children <= 0: + return True_() + return self.can_get_married() & self.has_house(2) & self.has_relationship(Generic.bachelor, 12) & self.has_children(number_children - 1) def has_relationship(self, npc: str, hearts: int = 1) -> StardewRule: if hearts <= 0: @@ -795,28 +1041,36 @@ class StardewLogic: if self.options[options.Friendsanity] == options.Friendsanity.option_none: return self.can_earn_relationship(npc, hearts) if npc not in all_villagers_by_name: - if npc == "Pet": + if npc == NPC.pet: if self.options[options.Friendsanity] == options.Friendsanity.option_bachelors: return self.can_befriend_pet(hearts) - return self.received(f"Pet: 1 <3", hearts) - if npc == "Any" or npc == "Bachelor": + return self.received_hearts(NPC.pet, hearts) + if npc == Generic.any or npc == Generic.bachelor: possible_friends = [] for name in all_villagers_by_name: - if npc == "Any" or all_villagers_by_name[name].bachelor: + 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.has_relationship(name, hearts)) return Or(possible_friends) - if npc == "All": + 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.has_relationship(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.has_relationship(name, hearts)) return 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[options.Friendsanity] == options.Friendsanity.option_bachelors and not villager.bachelor: return self.can_earn_relationship(npc, hearts) @@ -824,55 +1078,120 @@ class StardewLogic: return self.can_earn_relationship(npc, hearts) if self.options[ options.Friendsanity] != options.Friendsanity.option_all_with_marriage and villager.bachelor and hearts > 8: - return self.received(f"{villager.name}: 1 <3", 8) & self.can_earn_relationship(npc, hearts) - return self.received(f"{villager.name}: 1 <3", hearts) + return self.received_hearts(villager, 8) & self.can_earn_relationship(npc, hearts) + return self.received_hearts(villager, hearts) + + def received_hearts(self, npc: Union[str, Villager], hearts: int) -> StardewRule: + if isinstance(npc, Villager): + return self.received_hearts(npc.name, hearts) + heart_size: int = self.options[options.FriendsanityHeartSize] + return self.received(self.heart(npc), math.ceil(hearts / heart_size)) def can_meet(self, npc: str) -> StardewRule: - if npc not in all_villagers_by_name: + 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.can_reach_any_region(villager.locations)] - if npc == "Kent": - rules.append(self.has_lived_months(4)) - if npc == "Dwarf": - rules.append(self.received("Dwarvish Translation Guide")) - rules.append(self.has_tool("Pickaxe", "Iron")) + if npc == NPC.kent: + rules.append(self.has_year_two()) return And(rules) - def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule: - if npc == "Pet": - return self.can_befriend_pet(hearts) - if npc in all_villagers_by_name: + 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 villager = all_villagers_by_name[npc] - option1 = self.has_season(villager.birthday) & self.has(villager.gifts) & self.has_lived_months(1) - option2 = self.has_season(villager.birthday) & self.has(villager.gifts, 1) & self.has_lived_months( - hearts // 3) - option3 = (self.has_season(villager.birthday) | self.has(villager.gifts, 1)) & self.has_lived_months( - hearts // 2) - option4 = self.has_lived_months(hearts) - return self.can_meet(npc) & (option1 | option2 | option3 | option4) + gift_rule = self.has_any_universal_love() + meet_rule = self.can_meet(npc) + rules.append(meet_rule & gift_rule) + loved_gifts_rules = And(rules) + simplified_rules = loved_gifts_rules.simplify() + return simplified_rules + + def can_earn_relationship(self, npc: str, hearts: int = 0) -> StardewRule: + if hearts <= 0: + return True_() + + heart_size: int = self.options[options.FriendsanityHeartSize] + previous_heart = hearts - heart_size + previous_heart_rule = self.has_relationship(npc, previous_heart) + + if npc == NPC.pet: + earn_rule = self.can_befriend_pet(hearts) + elif npc == NPC.wizard and ModNames.magic in self.options[options.Mods]: + earn_rule = self.can_meet(npc) & self.has_lived_months(hearts) + elif npc in all_villagers_by_name: + if not self.npc_is_in_current_slot(npc): + return previous_heart_rule + villager = all_villagers_by_name[npc] + rule_if_birthday = self.has_season(villager.birthday) & self.has_any_universal_love() & self.has_lived_months(hearts // 2) + rule_if_not_birthday = self.has_lived_months(hearts) + earn_rule = self.can_meet(npc) & (rule_if_birthday | rule_if_not_birthday) else: - return self.has_lived_months(min(hearts // 2, 8)) + earn_rule = self.has_lived_months(min(hearts // 2, 8)) + + return previous_heart_rule & earn_rule def can_befriend_pet(self, hearts: int): - if hearts == 0: + if hearts <= 0: return True_() points = hearts * 200 points_per_month = 12 * 14 points_per_water_month = 18 * 14 - return self.can_reach_region(SVRegion.farm) & \ - ((self.has_tool("Watering Can") & self.has_lived_months(points // points_per_water_month)) | - self.has_lived_months(points // points_per_month)) + return self.can_reach_region(Region.farm) & \ + ((self.can_water(0) & self.has_lived_months(points // points_per_water_month)) | + self.has_lived_months(points // points_per_month)) def can_complete_bundle(self, bundle_requirements: List[BundleItem], number_required: int) -> StardewRule: item_rules = [] + highest_quality_yet = 0 for bundle_item in bundle_requirements: if bundle_item.item.item_id == -1: return self.can_spend_money(bundle_item.amount) else: item_rules.append(bundle_item.item.name) - return self.has(item_rules, number_required) + if bundle_item.quality > highest_quality_yet: + highest_quality_yet = bundle_item.quality + return self.has(item_rules, number_required) & self.can_grow_gold_quality(highest_quality_yet) + + def can_grow_gold_quality(self, quality: int) -> StardewRule: + if quality <= 0: + return True_() + if quality == 1: + return self.has_farming_level(5) | (self.has_fertilizer(1) & self.has_farming_level(2)) | ( + self.has_fertilizer(2) & self.has_farming_level(1)) | self.has_fertilizer(3) + if quality == 2: + return self.has_farming_level(10) | (self.has_fertilizer(1) & self.has_farming_level(5)) | ( + self.has_fertilizer(2) & self.has_farming_level(3)) | ( + self.has_fertilizer(3) & self.has_farming_level(2)) + if quality >= 3: + return self.has_fertilizer(3) & self.has_farming_level(4) + + def has_fertilizer(self, tier: int) -> StardewRule: + if tier <= 0: + return True_() + if tier == 1: + return self.has(Fertilizer.basic) + if tier == 2: + return self.has(Fertilizer.quality) + if tier >= 3: + return self.has(Fertilizer.deluxe) + + def can_complete_field_office(self) -> StardewRule: + field_office = self.can_reach_region(Region.field_office) + professor_snail = self.received("Open Professor Snail Cave") + dig_site = self.can_reach_region(Region.dig_site) + tools = self.has_tool(Tool.pickaxe) & self.has_tool(Tool.hoe) & self.has_tool(Tool.scythe) + leg_and_snake_skull = dig_site + ribs_and_spine = self.can_reach_region(Region.island_south) + skull = self.can_open_geode(Geode.golden_coconut) + tail = self.can_do_panning() & dig_site + frog = self.can_reach_region(Region.island_east) + bat = self.can_reach_region(Region.volcano_floor_5) + snake_vertebrae = self.can_reach_region(Region.island_west) + return field_office & professor_snail & tools & leg_and_snake_skull & ribs_and_spine & skull & tail & frog & bat & snake_vertebrae def can_complete_community_center(self) -> StardewRule: return (self.can_reach_location("Complete Crafts Room") & @@ -899,15 +1218,18 @@ class StardewLogic: self.can_get_married() & self.has_house(2), self.has_relationship("5", 8), # 5 Friends self.has_relationship("10", 8), # 10 friends - self.has_relationship("Pet", 5), # Max Pet + self.has_relationship(NPC.pet, 5), # Max Pet self.can_complete_community_center(), # Community Center Completion self.can_complete_community_center(), # CC Ceremony first point self.can_complete_community_center(), # CC Ceremony second point - self.received("Skull Key"), # Skull Key obtained + self.received(Wallet.skull_key), # Skull Key obtained self.has_rusty_key(), # Rusty key not expected ] return Count(12, rules_worth_a_point) + def has_island_transport(self) -> StardewRule: + return self.received(Transportation.island_obelisk) | self.received(Transportation.boat_repair) + def has_any_weapon(self) -> StardewRule: return self.has_decent_weapon() | self.received(item.name for item in all_items if Group.WEAPON in item.groups) @@ -938,18 +1260,41 @@ class StardewLogic: def has_year_two(self) -> StardewRule: return self.has_lived_months(4) - def has_spring_summer_or_fall(self) -> StardewRule: - return self.has_season("Spring") | self.has_season("Summer") | self.has_season("Fall") + def has_year_three(self) -> StardewRule: + return self.has_lived_months(8) + + def can_speak_dwarf(self) -> StardewRule: + if self.options[options.Museumsanity] == options.Museumsanity.option_none: + return self.has([item.name for item in dwarf_scrolls]) + return self.received("Dwarvish Translation Guide") def can_find_museum_item(self, item: MuseumItem) -> StardewRule: - region_rule = self.can_reach_all_regions(item.locations) - geodes_rule = self.has(item.geodes) + region_rule = self.can_reach_all_regions_except_one(item.locations) + geodes_rule = And([self.can_open_geode(geode) for geode in item.geodes]) # monster_rule = self.can_farm_monster(item.monsters) # extra_rule = True_() - return region_rule & geodes_rule # & monster_rule & extra_rule + pan_rule = False_() + if item.name == "Earth Crystal" or item.name == "Fire Quartz" or item.name == "Frozen Tear": + pan_rule = self.can_do_panning() + return pan_rule | (region_rule & geodes_rule) # & monster_rule & extra_rule + + def can_find_museum_artifacts(self, number: int) -> StardewRule: + rules = [] + for donation in all_museum_items: + if donation in all_artifact_items: + rules.append(self.can_find_museum_item(donation)) + + return Count(number, rules) + + def can_find_museum_items(self, number: int) -> StardewRule: + rules = [] + for donation in all_museum_items: + rules.append(self.can_find_museum_item(donation)) + + return Count(number, rules) def can_complete_museum(self) -> StardewRule: - rules = [self.can_mine_perfectly_in_the_skull_cavern()] + rules = [self.can_mine_perfectly()] if self.options[options.Museumsanity] != options.Museumsanity.option_none: rules.append(self.received("Traveling Merchant Metal Detector", 4)) @@ -959,11 +1304,13 @@ class StardewLogic: return And(rules) def has_season(self, season: str) -> StardewRule: - seasons_order = ["Spring", "Summer", "Fall", "Winter"] + if season == Generic.any: + return True_() + seasons_order = [Season.spring, Season.summer, Season.fall, Season.winter] if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive: - return self.received("Progressive Season", seasons_order.index(season)) + return self.received(Season.progressive, seasons_order.index(season)) if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled: - if season == "Spring": + if season == Season.spring: return True_() return self.has_lived_months(1) return self.received(season) @@ -973,16 +1320,267 @@ class StardewLogic: return True_() return Or([self.has_season(season) for season in seasons]) + def has_any_season_not_winter(self): + return self.has_any_season([Season.spring, Season.summer, Season.fall]) + def has_all_seasons(self, seasons: Iterable[str]): if not seasons: return True_() return And([self.has_season(season) for season in seasons]) def has_lived_months(self, number: int) -> StardewRule: - number = max(0, min(number, 8)) + number = max(0, min(number, MAX_MONTHS)) return self.received("Month End", number) def has_rusty_key(self) -> StardewRule: if self.options[options.Museumsanity] == options.Museumsanity.option_none: + required_donations = 80 # It's 60, but without a metal detector I'd rather overshoot so players don't get screwed by RNG + return self.has([item.name for item in all_museum_items], required_donations) & self.can_reach_region(Region.museum) + return self.received(Wallet.rusty_key) + + def can_win_egg_hunt(self) -> StardewRule: + number_of_movement_buffs: int = self.options[options.NumberOfMovementBuffs] + if self.options[options.FestivalLocations] == options.FestivalLocations.option_hard or number_of_movement_buffs < 2: return True_() - return self.received("Rusty Key") + return self.received(Buff.movement, number_of_movement_buffs // 2) + + def can_succeed_luau_soup(self) -> StardewRule: + if self.options[options.FestivalLocations] != options.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(fish) for fish in 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.can_keg(kegable) for kegable in eligible_kegables] + aged_rule = [self.can_age(rule, "Iridium") for rule in keg_rules] + # There are a few other valid items but I don't feel like coding them all + return Or(fish_rule) | Or(aged_rule) + + def can_succeed_grange_display(self) -> StardewRule: + if self.options[options.FestivalLocations] != options.FestivalLocations.option_hard: + return True_() + animal_rule = self.has_animal(Generic.any) + artisan_rule = self.can_keg(Generic.any) | self.can_preserves_jar(Generic.any) + cooking_rule = True_() # Salads at the bar are good enough + fish_rule = self.can_fish(50) + forage_rule = True_() # Hazelnut always available since the grange display is in fall + mineral_rule = self.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 = Or([self.has(fruit) for fruit in 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 = Or([self.has(vegetable) for vegetable in 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.can_fish(60) + + def has_any_universal_love(self) -> StardewRule: + return self.has(Gift.golden_pumpkin) | self.has("Magic Rock Candy") | self.has(Gift.pearl) | self.has( + "Prismatic Shard") | self.has(AnimalProduct.rabbit_foot) + + def has_jelly(self) -> StardewRule: + return self.can_preserves_jar(Fruit.any) + + def has_pickle(self) -> StardewRule: + return self.can_preserves_jar(Vegetable.any) + + def can_preserves_jar(self, item: str) -> StardewRule: + machine_rule = self.has(Machine.preserves_jar) + if item == Generic.any: + return machine_rule + if item == Fruit.any: + return machine_rule & self.has(all_fruits, 1) + if item == Vegetable.any: + return machine_rule & self.has(all_vegetables, 1) + return machine_rule & self.has(item) + + def has_wine(self) -> StardewRule: + return self.can_keg(Fruit.any) + + def has_juice(self) -> StardewRule: + return self.can_keg(Vegetable.any) + + def can_keg(self, item: str) -> StardewRule: + machine_rule = self.has(Machine.keg) + if item == Generic.any: + return machine_rule + if item == Fruit.any: + return machine_rule & self.has(all_fruits, 1) + if item == Vegetable.any: + return machine_rule & self.has(all_vegetables, 1) + return machine_rule & self.has(item) + + def can_age(self, item: Union[str, StardewRule], quality: str) -> StardewRule: + months = 1 + if quality == "Gold": + months = 2 + elif quality == "Iridium": + months = 3 + if isinstance(item, str): + rule = self.has(item) + else: + rule: StardewRule = item + return self.has(Machine.cask) & self.has_lived_months(months) & rule + + def can_buy_animal(self, animal: str) -> StardewRule: + price = 0 + building = "" + if animal == Animal.chicken: + price = 800 + building = Building.coop + elif animal == Animal.cow: + price = 1500 + building = Building.barn + elif animal == Animal.goat: + price = 4000 + building = Building.big_barn + elif animal == Animal.duck: + price = 1200 + building = Building.big_coop + elif animal == Animal.sheep: + price = 8000 + building = Building.deluxe_barn + elif animal == Animal.rabbit: + price = 8000 + building = Building.deluxe_coop + elif animal == Animal.pig: + price = 16000 + building = Building.deluxe_barn + else: + return True_() + return self.can_spend_money_at(Region.ranch, price) & self.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.has(animal) + + def has_happy_animal(self, animal: str) -> StardewRule: + return self.has_animal(animal) & self.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: + coop_rule = Or([self.has_animal(coop_animal) for coop_animal in coop_animals]) + return coop_rule + + def has_any_barn_animal(self) -> StardewRule: + barn_rule = Or([self.has_animal(barn_animal) for barn_animal in barn_animals]) + return barn_rule + + def can_open_geode(self, geode: str) -> StardewRule: + blacksmith_access = self.can_reach_region("Clint's Blacksmith") + geodes = [Geode.geode, Geode.frozen, Geode.magma, Geode.omni] + if geode == Generic.any: + return blacksmith_access & Or([self.has(geode_type) for geode_type in geodes]) + return blacksmith_access & self.has(geode) + + def has_island_trader(self) -> StardewRule: + if self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + return False_() + return self.can_reach_region(Region.island_trader) + + def has_walnut(self, number: int) -> StardewRule: + if self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + return False_() + if number <= 0: + return True_() + # https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations + reach_south = self.can_reach_region(Region.island_south) + reach_north = self.can_reach_region(Region.island_north) + reach_west = self.can_reach_region(Region.island_west) + reach_hut = self.can_reach_region(Region.leo_hut) + reach_southeast = self.can_reach_region(Region.island_south_east) + reach_pirate_cove = self.can_reach_region(Region.pirate_cove) + reach_outside_areas = And(reach_south, reach_north, reach_west, reach_hut) + reach_volcano_regions = [self.can_reach_region(Region.volcano), + self.can_reach_region(Region.volcano_secret_beach), + self.can_reach_region(Region.volcano_floor_5), + self.can_reach_region(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_caves = And(self.can_reach_region(Region.qi_walnut_room), self.can_reach_region(Region.dig_site), + self.can_reach_region(Region.gourmand_frog_cave), + self.can_reach_region(Region.colored_crystals_cave), + self.can_reach_region(Region.shipwreck), self.has(Weapon.any_slingshot)) + reach_entire_island = And(reach_outside_areas, reach_all_volcano, + reach_caves, reach_southeast, reach_pirate_cove) + if number <= 5: + return Or(reach_south, reach_north, reach_west, reach_volcano) + if number <= 10: + return Count(2, reach_walnut_regions) + if number <= 15: + return 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(gems) & self.can_mine_perfectly() & \ + self.can_fish_perfectly() & self.has(Craftable.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) + + def has_everything(self, all_progression_items: Set[str]) -> StardewRule: + all_regions = [region.name for region in vanilla_regions] + rules = self.received(all_progression_items, len(all_progression_items)) & \ + self.can_reach_all_regions(all_regions) + return rules + + def heart(self, npc: Union[str, Villager]) -> str: + if isinstance(npc, str): + return f"{npc} <3" + return self.heart(npc.name) + + def can_forage(self, season: str, region: str = Region.forest, need_hoe: bool = False) -> StardewRule: + season_rule = self.has_season(season) + region_rule = self.can_reach_region(region) + if need_hoe: + return season_rule & region_rule & self.has_tool(Tool.hoe) + return season_rule & region_rule + + def npc_is_in_current_slot(self, name: str) -> bool: + npc = all_villagers_by_name[name] + mod = npc.mod_name + return mod is None or mod in self.options[options.Mods] + + def can_do_combat_at_level(self, level: str) -> StardewRule: + if level == Performance.basic: + return self.has_any_weapon() | magic.has_any_spell(self) + if level == Performance.decent: + return self.has_decent_weapon() | magic.has_decent_spells(self) + if level == Performance.good: + return self.has_good_weapon() | magic.has_good_spells(self) + if level == Performance.great: + return self.has_great_weapon() | magic.has_great_spells(self) + if level == Performance.galaxy: + return self.has_galaxy_weapon() | magic.has_amazing_spells(self) + + def can_water(self, level: int) -> StardewRule: + tool_rule = self.has_tool(Tool.watering_can, ToolMaterial.tiers[level]) + spell_rule = (self.received(MagicSpell.water) & magic.can_use_altar(self) & self.has_skill_level(ModSkill.magic, level)) + return tool_rule | spell_rule + + def has_prismatic_jelly_reward_access(self) -> StardewRule: + if self.options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled: + return self.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) + diff --git a/worlds/stardew_valley/mods/__init__.py b/worlds/stardew_valley/mods/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/stardew_valley/mods/logic/__init__.py b/worlds/stardew_valley/mods/logic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/stardew_valley/mods/logic/buildings.py b/worlds/stardew_valley/mods/logic/buildings.py new file mode 100644 index 00000000..5ca4bf32 --- /dev/null +++ b/worlds/stardew_valley/mods/logic/buildings.py @@ -0,0 +1,16 @@ +from typing import Union + +from ...strings.artisan_good_names import ArtisanGood +from ...strings.building_names import ModBuilding +from ..mod_data import ModNames +from ...strings.metal_names import MetalBar +from ...strings.region_names import Region + + +def get_modded_building_rules(vanilla_logic, active_mods): + buildings = {} + if ModNames.tractor in active_mods: + buildings.update({ + ModBuilding.tractor_garage: vanilla_logic.can_spend_money_at(Region.carpenter, 150000) & vanilla_logic.has(MetalBar.iron) & + vanilla_logic.has(MetalBar.iridium) & vanilla_logic.has(ArtisanGood.battery_pack)}) + return buildings diff --git a/worlds/stardew_valley/mods/logic/deepwoods.py b/worlds/stardew_valley/mods/logic/deepwoods.py new file mode 100644 index 00000000..55881ee3 --- /dev/null +++ b/worlds/stardew_valley/mods/logic/deepwoods.py @@ -0,0 +1,35 @@ +from ...strings.craftable_names import Craftable +from ...strings.performance_names import Performance +from ...strings.skill_names import Skill +from ...strings.tool_names import Tool, ToolMaterial +from ...strings.ap_names.transport_names import ModTransportation +from ...stardew_rule import StardewRule, True_, And +from ... import options + + +def can_reach_woods_depth(vanilla_logic, depth: int) -> StardewRule: + tier = int(depth / 25) + 1 + rules = [] + if depth > 10: + rules.append(vanilla_logic.has(Craftable.bomb) | vanilla_logic.has_tool(Tool.axe, ToolMaterial.iridium)) + if depth > 30: + rules.append(vanilla_logic.received(ModTransportation.woods_obelisk)) + if depth > 50: + rules.append(vanilla_logic.can_do_combat_at_level(Performance.great) & vanilla_logic.can_cook() & + vanilla_logic.received(ModTransportation.woods_obelisk)) + if vanilla_logic.options[options.SkillProgression] == options.SkillProgression.option_progressive: + combat_tier = min(10, max(0, tier + 5)) + rules.append(vanilla_logic.has_skill_level(Skill.combat, combat_tier)) + return And(rules) + + +def has_woods_rune_to_depth(vanilla_logic, floor: int) -> StardewRule: + if vanilla_logic.options[options.ElevatorProgression] == options.ElevatorProgression.option_vanilla: + return True_() + return vanilla_logic.received("Progressive Wood Obelisk Sigils", count=int(floor / 10)) + + +def can_chop_to_depth(vanilla_logic, floor: int) -> StardewRule: + previous_elevator = max(floor - 10, 0) + return (has_woods_rune_to_depth(vanilla_logic, previous_elevator) & + can_reach_woods_depth(vanilla_logic, previous_elevator)) diff --git a/worlds/stardew_valley/mods/logic/magic.py b/worlds/stardew_valley/mods/logic/magic.py new file mode 100644 index 00000000..a084c6aa --- /dev/null +++ b/worlds/stardew_valley/mods/logic/magic.py @@ -0,0 +1,80 @@ +from ...strings.region_names import MagicRegion +from ...mods.mod_data import ModNames +from ...strings.spells import MagicSpell +from ...strings.ap_names.skill_level_names import ModSkillLevel +from ...stardew_rule import Count, StardewRule, False_ +from ... import options + + +def can_use_clear_debris_instead_of_tool_level(vanilla_logic, level: int) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + return vanilla_logic.received(MagicSpell.clear_debris) & can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, level) + + +def can_use_altar(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + return vanilla_logic.can_reach_region(MagicRegion.altar) + + +def has_any_spell(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + return can_use_altar(vanilla_logic) + + +def has_attack_spell_count(vanilla_logic, count: int) -> StardewRule: + attack_spell_rule = [vanilla_logic.received(MagicSpell.fireball), vanilla_logic.received( + MagicSpell.frostbite), vanilla_logic.received(MagicSpell.shockwave), vanilla_logic.received(MagicSpell.spirit), + vanilla_logic.received(MagicSpell.meteor) + ] + return Count(count, attack_spell_rule) + + +def has_support_spell_count(vanilla_logic, count: int) -> StardewRule: + support_spell_rule = [can_use_altar(vanilla_logic), vanilla_logic.received(ModSkillLevel.magic_level, 2), + vanilla_logic.received(MagicSpell.descend), vanilla_logic.received(MagicSpell.heal), + vanilla_logic.received(MagicSpell.tendrils)] + return Count(count, support_spell_rule) + + +def has_decent_spells(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 2) + magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 1) + return magic_resource_rule & magic_attack_options_rule + + +def has_good_spells(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 4) + magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 2) + magic_support_options_rule = has_support_spell_count(vanilla_logic, 1) + return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule + + +def has_great_spells(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 6) + magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 3) + magic_support_options_rule = has_support_spell_count(vanilla_logic, 1) + return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule + + +def has_amazing_spells(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + magic_resource_rule = can_use_altar(vanilla_logic) & vanilla_logic.received(ModSkillLevel.magic_level, 8) + magic_attack_options_rule = has_attack_spell_count(vanilla_logic, 4) + magic_support_options_rule = has_support_spell_count(vanilla_logic, 2) + return magic_resource_rule & magic_attack_options_rule & magic_support_options_rule + + +def can_blink(vanilla_logic) -> StardewRule: + if ModNames.magic not in vanilla_logic.options[options.Mods]: + return False_() + return vanilla_logic.received(MagicSpell.blink) & can_use_altar(vanilla_logic) diff --git a/worlds/stardew_valley/mods/logic/quests.py b/worlds/stardew_valley/mods/logic/quests.py new file mode 100644 index 00000000..bf185754 --- /dev/null +++ b/worlds/stardew_valley/mods/logic/quests.py @@ -0,0 +1,31 @@ +from typing import Union +from ...strings.quest_names import ModQuest +from ..mod_data import ModNames +from ...strings.food_names import Meal, Beverage +from ...strings.monster_drop_names import Loot +from ...strings.villager_names import ModNPC +from ...strings.season_names import Season +from ...strings.region_names import Region + + +def get_modded_quest_rules(vanilla_logic, active_mods): + quests = {} + if ModNames.juna in active_mods: + quests.update({ + ModQuest.JunaCola: vanilla_logic.has_relationship(ModNPC.juna, 3) & vanilla_logic.has(Beverage.joja_cola), + ModQuest.JunaSpaghetti: vanilla_logic.has_relationship(ModNPC.juna, 6) & vanilla_logic.has(Meal.spaghetti) + }) + + if ModNames.ginger in active_mods: + quests.update({ + ModQuest.MrGinger: vanilla_logic.has_relationship(ModNPC.mr_ginger, 6) & vanilla_logic.has(Loot.void_essence) + }) + + if ModNames.ayeisha in active_mods: + quests.update({ + ModQuest.AyeishaEnvelope: (vanilla_logic.has_season(Season.spring) | vanilla_logic.has_season(Season.fall)) & + vanilla_logic.can_reach_region(Region.mountain), + ModQuest.AyeishaRing: vanilla_logic.has_season(Season.winter) & vanilla_logic.can_reach_region(Region.forest) + }) + + return quests diff --git a/worlds/stardew_valley/mods/logic/skills.py b/worlds/stardew_valley/mods/logic/skills.py new file mode 100644 index 00000000..05b4c623 --- /dev/null +++ b/worlds/stardew_valley/mods/logic/skills.py @@ -0,0 +1,94 @@ +from typing import List, Union +from . import magic +from ...strings.building_names import Building +from ...strings.geode_names import Geode +from ...strings.region_names import Region +from ...strings.skill_names import ModSkill +from ...strings.spells import MagicSpell +from ...strings.machine_names import Machine +from ...strings.tool_names import Tool, ToolMaterial +from ...mods.mod_data import ModNames +from ...data.villagers_data import all_villagers +from ...stardew_rule import Count, StardewRule, False_ +from ... import options + + +def append_mod_skill_level(skills_items: List[str], active_mods): + if ModNames.luck_skill in active_mods: + skills_items.append("Luck Level") + if ModNames.socializing_skill in active_mods: + skills_items.append("Socializing Level") + if ModNames.magic in active_mods: + skills_items.append("Magic Level") + if ModNames.archaeology in active_mods: + skills_items.append("Archaeology Level") + if ModNames.binning_skill in active_mods: + skills_items.append("Binning Level") + if ModNames.cooking_skill in active_mods: + skills_items.append("Cooking Level") + + +def can_earn_mod_skill_level(logic, skill: str, level: int) -> StardewRule: + if ModNames.luck_skill in logic.options[options.Mods] and skill == ModSkill.luck: + return can_earn_luck_skill_level(logic, level) + if ModNames.magic in logic.options[options.Mods] and skill == ModSkill.magic: + return can_earn_magic_skill_level(logic, level) + if ModNames.socializing_skill in logic.options[options.Mods] and skill == ModSkill.socializing: + return can_earn_socializing_skill_level(logic, level) + if ModNames.archaeology in logic.options[options.Mods] and skill == ModSkill.archaeology: + return can_earn_archaeology_skill_level(logic, level) + if ModNames.cooking_skill in logic.options[options.Mods] and skill == ModSkill.cooking: + return can_earn_cooking_skill_level(logic, level) + if ModNames.binning_skill in logic.options[options.Mods] and skill == ModSkill.binning: + return can_earn_binning_skill_level(logic, level) + return False_() + + +def can_earn_luck_skill_level(vanilla_logic, level: int) -> StardewRule: + if level >= 6: + return vanilla_logic.can_fish_chests() | vanilla_logic.can_open_geode(Geode.magma) + else: + return vanilla_logic.can_fish_chests() | vanilla_logic.can_open_geode(Geode.geode) + + +def can_earn_magic_skill_level(vanilla_logic, level: int) -> StardewRule: + spell_count = [vanilla_logic.received(MagicSpell.clear_debris), vanilla_logic.received(MagicSpell.water), + vanilla_logic.received(MagicSpell.blink), vanilla_logic.received(MagicSpell.fireball), + vanilla_logic.received(MagicSpell.frostbite), + vanilla_logic.received(MagicSpell.descend), vanilla_logic.received(MagicSpell.tendrils), + vanilla_logic.received(MagicSpell.shockwave), + vanilla_logic.received(MagicSpell.meteor), + vanilla_logic.received(MagicSpell.spirit)] + return magic.can_use_altar(vanilla_logic) & Count(level, spell_count) + + +def can_earn_socializing_skill_level(vanilla_logic, level: int) -> StardewRule: + villager_count = [] + for villager in all_villagers: + if villager.mod_name in vanilla_logic.options[options.Mods] or villager.mod_name is None: + villager_count.append(vanilla_logic.can_earn_relationship(villager.name, level)) + return Count(level * 2, villager_count) + + +def can_earn_archaeology_skill_level(vanilla_logic, level: int) -> StardewRule: + if level >= 6: + return vanilla_logic.can_do_panning() | vanilla_logic.has_tool(Tool.hoe, ToolMaterial.gold) + else: + return vanilla_logic.can_do_panning() | vanilla_logic.has_tool(Tool.hoe, ToolMaterial.basic) + + +def can_earn_cooking_skill_level(vanilla_logic, level: int) -> StardewRule: + if level >= 6: + return vanilla_logic.can_cook() & vanilla_logic.can_fish() & vanilla_logic.can_reach_region(Region.saloon) & \ + vanilla_logic.has_building(Building.coop) & vanilla_logic.has_building(Building.barn) + else: + return vanilla_logic.can_cook() + + +def can_earn_binning_skill_level(vanilla_logic, level: int) -> StardewRule: + if level >= 6: + return vanilla_logic.can_reach_region(Region.town) & vanilla_logic.has(Machine.recycling_machine) & \ + (vanilla_logic.can_fish() | vanilla_logic.can_crab_pot()) + else: + return vanilla_logic.can_reach_region(Region.town) | (vanilla_logic.has(Machine.recycling_machine) & + (vanilla_logic.can_fish() | vanilla_logic.can_crab_pot())) diff --git a/worlds/stardew_valley/mods/logic/skullcavernelevator.py b/worlds/stardew_valley/mods/logic/skullcavernelevator.py new file mode 100644 index 00000000..74db86d8 --- /dev/null +++ b/worlds/stardew_valley/mods/logic/skullcavernelevator.py @@ -0,0 +1,10 @@ +from ...stardew_rule import Count, StardewRule, True_ +from ...mods.mod_data import ModNames +from ... import options + + +def has_skull_cavern_elevator_to_floor(self, floor: int) -> StardewRule: + if self.options[options.ElevatorProgression] != options.ElevatorProgression.option_vanilla and \ + ModNames.skull_cavern_elevator in self.options[options.Mods]: + return self.received("Progressive Skull Cavern Elevator", floor // 25) + return True_() diff --git a/worlds/stardew_valley/mods/logic/special_orders.py b/worlds/stardew_valley/mods/logic/special_orders.py new file mode 100644 index 00000000..45d5d572 --- /dev/null +++ b/worlds/stardew_valley/mods/logic/special_orders.py @@ -0,0 +1,24 @@ +from typing import Union +from ...strings.craftable_names import Craftable +from ...strings.food_names import Meal +from ...strings.material_names import Material +from ...strings.monster_drop_names import Loot +from ...strings.region_names import Region +from ...strings.special_order_names import SpecialOrder, ModSpecialOrder +from ...strings.villager_names import ModNPC +from ..mod_data import ModNames + + +def get_modded_special_orders_rules(vanilla_logic, active_mods): + special_orders = {} + if ModNames.juna in active_mods: + special_orders.update({ + ModSpecialOrder.junas_monster_mash: vanilla_logic.has_relationship(ModNPC.juna, 4) & + vanilla_logic.can_complete_special_order(SpecialOrder.a_curious_substance) & + vanilla_logic.has_rusty_key() & + vanilla_logic.can_reach_region(Region.forest) & vanilla_logic.has(Craftable.monster_musk) & + vanilla_logic.has("Energy Tonic") & vanilla_logic.has(Material.sap) & vanilla_logic.has(Loot.bug_meat) & + vanilla_logic.has(Craftable.oil_of_garlic) & vanilla_logic.has(Meal.strange_bun) + }) + + return special_orders diff --git a/worlds/stardew_valley/mods/mod_data.py b/worlds/stardew_valley/mods/mod_data.py new file mode 100644 index 00000000..190f8088 --- /dev/null +++ b/worlds/stardew_valley/mods/mod_data.py @@ -0,0 +1,48 @@ +class ModNames: + vanilla = None + deepwoods = "DeepWoods" + tractor = "Tractor Mod" + big_backpack = "Bigger Backpack" + luck_skill = "Luck Skill" + magic = "Magic" + socializing_skill = "Socializing Skill" + archaeology = "Archaeology" + cooking_skill = "Cooking Skill" + binning_skill = "Binning Skill" + juna = "Juna - Roommate NPC" + jasper = "Professor Jasper Thomas" + alec = "Alec Revisited" + yoba = "Custom NPC - Yoba" + eugene = "Custom NPC Eugene" + wellwick = "'Prophet' Wellwick" + ginger = "Mister Ginger (cat npc)" + shiko = "Shiko - New Custom NPC" + delores = "Delores - Custom NPC" + ayeisha = "Ayeisha - The Postal Worker (Custom NPC)" + riley = "Custom NPC - Riley" + skull_cavern_elevator = "Skull Cavern Elevator" + + +mod_versions = { + ModNames.deepwoods: "3.0.0-beta", + ModNames.tractor: "4.16.4", + ModNames.big_backpack: "6.0.0", + ModNames.luck_skill: "1.2.4", + ModNames.magic: "0.8.2", + ModNames.socializing_skill: "1.1.5", + ModNames.archaeology: "1.5.0", + ModNames.cooking_skill: "1.4.5", + ModNames.binning_skill: "1.2.7", + ModNames.juna: "2.1.3", + ModNames.jasper: "1.7.6", + ModNames.alec: "2.1.0", + ModNames.yoba: "1.0.0", + ModNames.eugene: "1.3.1", + ModNames.wellwick: "1.0.0", + ModNames.ginger: "1.5.9", + ModNames.shiko: "1.1.0", + ModNames.delores: "1.1.2", + ModNames.ayeisha: "0.5.0-alpha", + ModNames.riley: "1.2.2", + ModNames.skull_cavern_elevator: "1.5.0", +} diff --git a/worlds/stardew_valley/mods/mod_regions.py b/worlds/stardew_valley/mods/mod_regions.py new file mode 100644 index 00000000..b05bc953 --- /dev/null +++ b/worlds/stardew_valley/mods/mod_regions.py @@ -0,0 +1,144 @@ +from ..strings.entrance_names import DeepWoodsEntrance, EugeneEntrance, \ + JasperEntrance, AlecEntrance, YobaEntrance, JunaEntrance, MagicEntrance, AyeishaEntrance, RileyEntrance +from ..strings.region_names import Region, DeepWoodsRegion, EugeneRegion, JasperRegion, \ + AlecRegion, YobaRegion, JunaRegion, MagicRegion, AyeishaRegion, RileyRegion +from ..region_classes import RegionData, ConnectionData, RandomizationFlag, ModRegionData +from .mod_data import ModNames + +deep_woods_regions = [ + RegionData(Region.farm, [DeepWoodsEntrance.use_woods_obelisk]), + RegionData(DeepWoodsRegion.woods_obelisk_menu, [DeepWoodsEntrance.deep_woods_depth_1, + DeepWoodsEntrance.deep_woods_depth_10, + DeepWoodsEntrance.deep_woods_depth_20, + DeepWoodsEntrance.deep_woods_depth_30, + DeepWoodsEntrance.deep_woods_depth_40, + DeepWoodsEntrance.deep_woods_depth_50, + DeepWoodsEntrance.deep_woods_depth_60, + DeepWoodsEntrance.deep_woods_depth_70, + DeepWoodsEntrance.deep_woods_depth_80, + DeepWoodsEntrance.deep_woods_depth_90, + DeepWoodsEntrance.deep_woods_depth_100]), + RegionData(Region.secret_woods, [DeepWoodsEntrance.secret_woods_to_deep_woods]), + RegionData(DeepWoodsRegion.main_lichtung, [DeepWoodsEntrance.deep_woods_house]), + RegionData(DeepWoodsRegion.abandoned_home), + RegionData(DeepWoodsRegion.floor_10), + RegionData(DeepWoodsRegion.floor_20), + RegionData(DeepWoodsRegion.floor_30), + RegionData(DeepWoodsRegion.floor_40), + RegionData(DeepWoodsRegion.floor_50), + RegionData(DeepWoodsRegion.floor_60), + RegionData(DeepWoodsRegion.floor_70), + RegionData(DeepWoodsRegion.floor_80), + RegionData(DeepWoodsRegion.floor_90), + RegionData(DeepWoodsRegion.floor_100) +] + +deep_woods_entrances = [ + ConnectionData(DeepWoodsEntrance.use_woods_obelisk, DeepWoodsRegion.woods_obelisk_menu), + ConnectionData(DeepWoodsEntrance.secret_woods_to_deep_woods, DeepWoodsRegion.main_lichtung), + ConnectionData(DeepWoodsEntrance.deep_woods_house, DeepWoodsRegion.abandoned_home, + flag=RandomizationFlag.NON_PROGRESSION), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_1, DeepWoodsRegion.main_lichtung), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_10, DeepWoodsRegion.floor_10), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_20, DeepWoodsRegion.floor_20), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_30, DeepWoodsRegion.floor_30), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_40, DeepWoodsRegion.floor_40), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_50, DeepWoodsRegion.floor_50), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_60, DeepWoodsRegion.floor_60), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_70, DeepWoodsRegion.floor_70), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_80, DeepWoodsRegion.floor_80), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_90, DeepWoodsRegion.floor_90), + ConnectionData(DeepWoodsEntrance.deep_woods_depth_100, DeepWoodsRegion.floor_100) +] + +eugene_regions = [ + RegionData(Region.forest, [EugeneEntrance.forest_to_garden]), + RegionData(EugeneRegion.eugene_garden, [EugeneEntrance.garden_to_bedroom]), + RegionData(EugeneRegion.eugene_bedroom) +] + +eugene_entrances = [ + ConnectionData(EugeneEntrance.forest_to_garden, EugeneRegion.eugene_garden, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(EugeneEntrance.garden_to_bedroom, EugeneRegion.eugene_bedroom, flag=RandomizationFlag.BUILDINGS) +] + +magic_regions = [ + RegionData(Region.pierre_store, [MagicEntrance.store_to_altar]), + RegionData(MagicRegion.altar) +] + +magic_entrances = [ + ConnectionData(MagicEntrance.store_to_altar, MagicRegion.altar, flag=RandomizationFlag.NOT_RANDOMIZED) +] + +jasper_regions = [ + RegionData(Region.museum, [JasperEntrance.museum_to_bedroom]), + RegionData(JasperRegion.jasper_bedroom) +] + +jasper_entrances = [ + ConnectionData(JasperEntrance.museum_to_bedroom, JasperRegion.jasper_bedroom, flag=RandomizationFlag.BUILDINGS) +] +alec_regions = [ + RegionData(Region.forest, [AlecEntrance.forest_to_petshop]), + RegionData(AlecRegion.pet_store, [AlecEntrance.petshop_to_bedroom]), + RegionData(AlecRegion.alec_bedroom) +] + +alec_entrances = [ + ConnectionData(AlecEntrance.forest_to_petshop, AlecRegion.pet_store, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(AlecEntrance.petshop_to_bedroom, AlecRegion.alec_bedroom, flag=RandomizationFlag.BUILDINGS) +] + +yoba_regions = [ + RegionData(Region.secret_woods, [YobaEntrance.secret_woods_to_clearing]), + RegionData(YobaRegion.yoba_clearing) +] + +yoba_entrances = [ + ConnectionData(YobaEntrance.secret_woods_to_clearing, YobaRegion.yoba_clearing, flag=RandomizationFlag.BUILDINGS) +] + +juna_regions = [ + RegionData(Region.forest, [JunaEntrance.forest_to_juna_cave]), + RegionData(JunaRegion.juna_cave) +] + +juna_entrances = [ + ConnectionData(JunaEntrance.forest_to_juna_cave, JunaRegion.juna_cave, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA) +] + +ayeisha_regions = [ + RegionData(Region.bus_stop, [AyeishaEntrance.bus_stop_to_mail_van]), + RegionData(AyeishaRegion.mail_van) +] + +ayeisha_entrances = [ + ConnectionData(AyeishaEntrance.bus_stop_to_mail_van, AyeishaRegion.mail_van, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA) +] + +riley_regions = [ + RegionData(Region.town, [RileyEntrance.town_to_riley]), + RegionData(RileyRegion.riley_house) +] + +riley_entrances = [ + ConnectionData(RileyEntrance.town_to_riley, RileyRegion.riley_house, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA) +] + +ModDataList = { + ModNames.deepwoods: ModRegionData(ModNames.deepwoods, deep_woods_regions, deep_woods_entrances), + ModNames.eugene: ModRegionData(ModNames.eugene, eugene_regions, eugene_entrances), + ModNames.jasper: ModRegionData(ModNames.jasper, jasper_regions, jasper_entrances), + ModNames.alec: ModRegionData(ModNames.alec, alec_regions, alec_entrances), + ModNames.yoba: ModRegionData(ModNames.yoba, yoba_regions, yoba_entrances), + ModNames.juna: ModRegionData(ModNames.juna, juna_regions, juna_entrances), + ModNames.magic: ModRegionData(ModNames.magic, magic_regions, magic_entrances), + ModNames.ayeisha: ModRegionData(ModNames.ayeisha, ayeisha_regions, ayeisha_entrances), + ModNames.riley: ModRegionData(ModNames.riley, riley_regions, riley_entrances), +} diff --git a/worlds/stardew_valley/options.py b/worlds/stardew_valley/options.py index e365dd67..78de9e8d 100644 --- a/worlds/stardew_valley/options.py +++ b/worlds/stardew_valley/options.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from typing import Dict, Union, Protocol, runtime_checkable, ClassVar -from Options import Option, Range, DeathLink, SpecialRange, Toggle, Choice - +from Options import Option, Range, DeathLink, SpecialRange, Toggle, Choice, OptionSet +from .mods.mod_data import ModNames @runtime_checkable class StardewOption(Protocol): @@ -11,14 +11,19 @@ class StardewOption(Protocol): @dataclass class StardewOptions: - options: Dict[str, Union[bool, int]] + options: Dict[str, Union[bool, int, str]] - def __getitem__(self, item: Union[str, StardewOption]) -> Union[bool, int]: + def __getitem__(self, item: Union[str, StardewOption]) -> Union[bool, int, str]: if isinstance(item, StardewOption): item = item.internal_name return self.options.get(item, None) + def __setitem__(self, key: Union[str, StardewOption], value: Union[bool, int, str]): + if isinstance(key, StardewOption): + key = key.internal_name + self.options[key] = value + class Goal(Choice): """What's your goal with this play-through? @@ -29,6 +34,8 @@ class Goal(Choice): Master Angler: The world will be completed once you have caught every fish in the game. Pairs well with Fishsanity. Complete Collection: The world will be completed once you have completed the museum by donating every possible item. Pairs well with Museumsanity. Full House: The world will be completed once you get married and have two kids. Pairs well with Friendsanity. + Greatest Walnut Hunter: The world will be completed once you find all 130 Golden Walnuts + Perfection: The world will be completed once you attain Perfection, based on the vanilla definition. """ internal_name = "goal" display_name = "Goal" @@ -40,6 +47,18 @@ class Goal(Choice): option_master_angler = 4 option_complete_collection = 5 option_full_house = 6 + option_greatest_walnut_hunter = 7 + # option_junimo_kart = + # option_prairie_king = + # option_fector_challenge = + # option_craft_master = + # option_mystery_of_the_stardrops = + # option_protector_of_the_valley = + # option_full_shipment = + # option_legend = + # option_beloved_farmer = + # option_master_of_the_five_ways = + option_perfection = 25 @classmethod def get_option_name(cls, value) -> str: @@ -68,21 +87,22 @@ class StartingMoney(SpecialRange): } -class ResourcePackMultiplier(SpecialRange): - """How many items will be in the resource packs. A lower setting mean fewer resources in each pack. - A higher setting means more resources in each pack. Easy (200) doubles the default quantity""" - internal_name = "resource_pack_multiplier" - default = 100 - range_start = 0 - range_end = 200 +class ProfitMargin(SpecialRange): + """Multiplier over all gold earned in-game by the player.""" + internal_name = "profit_margin" + display_name = "Profit Margin" + range_start = 25 + range_end = 400 # step = 25 - display_name = "Resource Pack Multiplier" + default = 100 special_range_names = { - "resource packs disabled": 0, - "half packs": 50, - "normal packs": 100, - "double packs": 200, + "quarter": 25, + "half": 50, + "normal": 100, + "double": 200, + "triple": 300, + "quadruple": 400, } @@ -119,8 +139,9 @@ class EntranceRandomization(Choice): Disabled: No entrance randomization is done Pelican Town: Only buildings in the main town area are randomized among each other Non Progression: Only buildings that are always available are randomized with each other + Buildings: All Entrances that Allow you to enter a building using a door are randomized with each other + Chaos: Same as above, but the entrances get reshuffled every single day! """ - # Buildings: All buildings in the world are randomized with each other # Everything: All buildings and areas are randomized with each other # Chaos, same as everything: but the buildings are shuffled again every in-game day. You can't learn it! # Buildings One-way: Entrance pairs are disconnected, they aren't two-way! @@ -133,12 +154,12 @@ class EntranceRandomization(Choice): option_disabled = 0 option_pelican_town = 1 option_non_progression = 2 - # option_buildings = 3 + option_buildings = 3 # option_everything = 4 - # option_chaos = 4 - # option_buildings_one_way = 5 - # option_everything_one_way = 6 - # option_chaos_one_way = 7 + option_chaos = 5 + # option_buildings_one_way = 6 + # option_everything_one_way = 7 + # option_chaos_one_way = 8 class SeasonRandomization(Choice): @@ -158,14 +179,14 @@ class SeasonRandomization(Choice): option_progressive = 3 -class SeedShuffle(Choice): - """Should seeds be randomized? +class Cropsanity(Choice): + """Formerly named "Seed Shuffle" Pierre now sells a random amount of seasonal seeds and Joja sells them without season requirements, but only in huge packs. - Disabled: All the seeds will be unlocked from the start. - Shuffled: The seeds will be unlocked as Archipelago items + Disabled: All the seeds are unlocked from the start, there are no location checks for growing and harvesting crops + Shuffled: Seeds are unlocked as archipelago item, for each seed there is a location check for growing and harvesting that crop """ - internal_name = "seed_shuffle" - display_name = "Seed Shuffle" + internal_name = "cropsanity" + display_name = "Cropsanity" default = 1 option_disabled = 0 option_shuffled = 1 @@ -196,13 +217,13 @@ class ToolProgression(Choice): option_progressive = 1 -class TheMinesElevatorsProgression(Choice): - """How is The Mines' Elevator progression handled? - Vanilla: You will unlock a new elevator floor every 5 floor in the mine. - Progressive: You will randomly find Progressive Mine Elevator to go deeper. Location are sent for reaching - every level multiple of 5. - Progressive from previous floor: Locations are sent for taking the ladder or stairs to every 5 - levels, taking the elevator does not count.""" +class ElevatorProgression(Choice): + """How is Elevator progression handled? + Vanilla: You will unlock new elevator floors for yourself. + Progressive: You will randomly find Progressive Mine Elevators to go deeper. Locations are sent for reaching + every elevator level. + Progressive from previous floor: Same as progressive, but you must reach elevator floors on your own, + you cannot use the elevator to check elevator locations""" internal_name = "elevator_progression" display_name = "Elevator Progression" default = 2 @@ -238,9 +259,23 @@ class BuildingProgression(Choice): option_progressive_early_shipping_bin = 2 +class FestivalLocations(Choice): + """Locations for attending and participating in festivals + With Disabled, you do not need to attend festivals + With Easy, there are checks for participating in festivals + With Hard, the festival checks are only granted when the player performs well in the festival + """ + internal_name = "festival_locations" + display_name = "Festival Locations" + default = 1 + option_disabled = 0 + option_easy = 1 + option_hard = 2 + + class ArcadeMachineLocations(Choice): """How are the Arcade Machines handled? - Vanilla: The arcade machines are not included in the Archipelago shuffling. + Disabled: The arcade machines are not included in the Archipelago shuffling. Victories: Each Arcade Machine will contain one check on victory Victories Easy: The arcade machines are both made considerably easier to be more accessible for the average player. @@ -257,6 +292,20 @@ class ArcadeMachineLocations(Choice): option_full_shuffling = 3 +class SpecialOrderLocations(Choice): + """How are the Special Orders handled? + Disabled: The special orders are not included in the Archipelago shuffling. + Board Only: The Special Orders on the board in town are location checks + Board and Qi: The Special Orders from Qi's walnut room are checks, as well as the board in town + """ + internal_name = "special_order_locations" + display_name = "Special Order Locations" + default = 1 + option_disabled = 0 + option_board_only = 1 + option_board_qi = 2 + + class HelpWantedLocations(SpecialRange): """How many "Help Wanted" quests need to be completed as Archipelago Locations Out of every 7 quests, 4 will be item deliveries, and then 1 of each for: Fishing, Gathering and Slaying Monsters. @@ -284,6 +333,9 @@ class Fishsanity(Choice): Special: A curated selection of strong fish are checks Randomized: A random selection of fish are checks All: Every single fish in the game is a location that contains an item. Pairs well with the Master Angler Goal + Exclude Legendaries: Every fish except legendaries + Exclude Hard Fish: Every fish under difficulty 80 + Only Easy Fish: Every fish under difficulty 50 """ internal_name = "fishsanity" display_name = "Fishsanity" @@ -294,6 +346,9 @@ class Fishsanity(Choice): option_randomized = 3 alias_random_selection = option_randomized option_all = 4 + option_exclude_legendaries = 5 + option_exclude_hard_fish = 6 + option_only_easy_fish = 7 class Museumsanity(Choice): @@ -331,18 +386,64 @@ class Friendsanity(Choice): option_all_with_marriage = 5 -class NumberOfPlayerBuffs(Range): - """Number of buffs to the player of each type that exist as items in the pool. - Buffs include movement speed (+25% multiplier, stacks additively) - and daily luck bonus (0.025 flat value per buff)""" - internal_name = "player_buff_number" - display_name = "Number of Player Buffs" +# Conditional Setting - Friendsanity not None +class FriendsanityHeartSize(Range): + """If using friendsanity, how many hearts are received per item, and how many hearts must be earned to send a check + A higher value will lead to fewer heart items in the item pool, reducing bloat""" + internal_name = "friendsanity_heart_size" + display_name = "Friendsanity Heart Size" + range_start = 1 + range_end = 8 + default = 4 + # step = 1 + + +class NumberOfMovementBuffs(Range): + """Number of movement speed buffs to the player that exist as items in the pool. + Each movement speed buff is a +25% multiplier that stacks additively""" + internal_name = "movement_buff_number" + display_name = "Number of Movement Buffs" range_start = 0 range_end = 12 default = 4 # step = 1 +class NumberOfLuckBuffs(Range): + """Number of luck buffs to the player that exist as items in the pool. + Each luck buff is a bonus to daily luck of 0.025""" + internal_name = "luck_buff_number" + display_name = "Number of Luck Buffs" + range_start = 0 + range_end = 12 + default = 4 + # step = 1 + + +class ExcludeGingerIsland(Toggle): + """Exclude Ginger Island? + This option will forcefully exclude everything related to Ginger Island from the slot. + If you pick a goal that requires Ginger Island, you cannot exclude it and it will get included anyway""" + internal_name = "exclude_ginger_island" + display_name = "Exclude Ginger Island" + default = 0 + + +class TrapItems(Choice): + """When rolling filler items, including resource packs, the game can also roll trap items. + This setting is for choosing if traps will be in the item pool, and if so, how punishing they will be. + """ + internal_name = "trap_items" + display_name = "Trap Items" + default = 2 + option_no_traps = 0 + option_easy = 1 + option_medium = 2 + option_hard = 3 + option_hell = 4 + option_nightmare = 5 + + class MultipleDaySleepEnabled(Toggle): """Enable the ability to sleep automatically for multiple days straight?""" internal_name = "multiple_day_sleep_enabled" @@ -372,7 +473,7 @@ class ExperienceMultiplier(SpecialRange): internal_name = "experience_multiplier" display_name = "Experience Multiplier" range_start = 25 - range_end = 400 + range_end = 800 # step = 25 default = 200 @@ -392,7 +493,7 @@ class FriendshipMultiplier(SpecialRange): internal_name = "friendship_multiplier" display_name = "Friendship Multiplier" range_start = 25 - range_end = 400 + range_end = 800 # step = 25 default = 200 @@ -438,46 +539,46 @@ class Gifting(Toggle): default = 1 -class GiftTax(SpecialRange): - """Joja Prime will deliver gifts within one business day, for a price! - Sending a gift will cost a percentage of the item's monetary value as a tax on the sender""" - internal_name = "gift_tax" - display_name = "Gift Tax" - range_start = 0 - range_end = 400 - # step = 20 - default = 20 - - special_range_names = { - "no tax": 0, - "soft tax": 20, - "rough tax": 40, - "full tax": 100, - "oppressive tax": 200, - "nightmare tax": 400, +class Mods(OptionSet): + """List of mods that will be considered for shuffling.""" + internal_name = "mods" + display_name = "Mods" + valid_keys = { + ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack, + ModNames.luck_skill, ModNames.magic, ModNames.socializing_skill, ModNames.archaeology, + ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna, + ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene, + ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores, + ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator } stardew_valley_option_classes = [ Goal, StartingMoney, - ResourcePackMultiplier, + ProfitMargin, BundleRandomization, BundlePrice, EntranceRandomization, SeasonRandomization, - SeedShuffle, + Cropsanity, BackpackProgression, ToolProgression, SkillProgression, BuildingProgression, - TheMinesElevatorsProgression, + FestivalLocations, + ElevatorProgression, ArcadeMachineLocations, + SpecialOrderLocations, HelpWantedLocations, Fishsanity, Museumsanity, Friendsanity, - NumberOfPlayerBuffs, + FriendsanityHeartSize, + NumberOfMovementBuffs, + NumberOfLuckBuffs, + ExcludeGingerIsland, + TrapItems, MultipleDaySleepEnabled, MultipleDaySleepCost, ExperienceMultiplier, @@ -485,7 +586,7 @@ stardew_valley_option_classes = [ DebrisMultiplier, QuickStart, Gifting, - GiftTax, + Mods, ] stardew_valley_options: Dict[str, type(Option)] = {option.internal_name: option for option in stardew_valley_option_classes} diff --git a/worlds/stardew_valley/region_classes.py b/worlds/stardew_valley/region_classes.py new file mode 100644 index 00000000..9db32241 --- /dev/null +++ b/worlds/stardew_valley/region_classes.py @@ -0,0 +1,58 @@ +from enum import IntFlag +from typing import Optional, List +from dataclasses import dataclass, field + +connector_keyword = " to " + + +class RandomizationFlag(IntFlag): + NOT_RANDOMIZED = 0b0 + PELICAN_TOWN = 0b11111 + NON_PROGRESSION = 0b11110 + BUILDINGS = 0b11100 + EVERYTHING = 0b11000 + CHAOS = 0b10000 + GINGER_ISLAND = 0b0100000 + LEAD_TO_OPEN_AREA = 0b1000000 + + +@dataclass(frozen=True) +class RegionData: + name: str + exits: List[str] = field(default_factory=list) + + def get_merged_with(self, exits: List[str]): + merged_exits = [] + merged_exits.extend(self.exits) + if exits is not None: + merged_exits.extend(exits) + merged_exits = list(set(merged_exits)) + return RegionData(self.name, merged_exits) + + def get_clone(self): + return self.get_merged_with(None) + + +@dataclass(frozen=True) +class ConnectionData: + name: str + destination: str + origin: Optional[str] = None + reverse: Optional[str] = None + flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED + + def __post_init__(self): + if connector_keyword in self.name: + origin, destination = self.name.split(connector_keyword) + if self.reverse is None: + super().__setattr__("reverse", f"{destination}{connector_keyword}{origin}") + + +@dataclass(frozen=True) +class ModRegionData: + mod_name: str + regions: List[RegionData] + connections: List[ConnectionData] + + + diff --git a/worlds/stardew_valley/regions.py b/worlds/stardew_valley/regions.py index 5c6dcfd9..d115e3ed 100644 --- a/worlds/stardew_valley/regions.py +++ b/worlds/stardew_valley/regions.py @@ -1,12 +1,13 @@ -from dataclasses import dataclass, field -from enum import IntFlag from random import Random -from typing import Iterable, Dict, Protocol, Optional, List, Tuple +from typing import Iterable, Dict, Protocol, List, Tuple, Set from BaseClasses import Region, Entrance from . import options -from .data.region_data import SVRegion +from .strings.entrance_names import Entrance +from .strings.region_names import Region +from .region_classes import RegionData, ConnectionData, RandomizationFlag from .options import StardewOptions +from .mods.mod_regions import ModDataList class RegionFactory(Protocol): @@ -14,299 +15,597 @@ class RegionFactory(Protocol): raise NotImplementedError -class RandomizationFlag(IntFlag): - NOT_RANDOMIZED = 0b0 - PELICAN_TOWN = 0b11111 - NON_PROGRESSION = 0b11110 - BUILDINGS = 0b11100 - EVERYTHING = 0b11000 - CHAOS = 0b10000 - - -@dataclass(frozen=True) -class RegionData: - name: str - exits: List[str] = field(default_factory=list) - - -@dataclass(frozen=True) -class ConnectionData: - name: str - destination: str - reverse: Optional[str] = None - flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED - - def __post_init__(self): - if self.reverse is None and " to " in self.name: - origin, destination = self.name.split(" to ") - super().__setattr__("reverse", f"{destination} to {origin}") - - -stardew_valley_regions = [ - RegionData(SVRegion.menu, ["To Stardew Valley"]), - RegionData(SVRegion.stardew_valley, ["To Farmhouse"]), - RegionData(SVRegion.farm_house, ["Outside to Farm", "Downstairs to Cellar"]), - RegionData(SVRegion.cellar), - RegionData(SVRegion.farm, - ["Farm to Backwoods", "Farm to Bus Stop", "Farm to Forest", "Farm to Farmcave", "Enter Greenhouse", - "Use Desert Obelisk", "Use Island Obelisk"]), - RegionData(SVRegion.backwoods, ["Backwoods to Mountain"]), - RegionData(SVRegion.bus_stop, ["Bus Stop to Town", "Take Bus to Desert", "Bus Stop to Tunnel Entrance"]), - RegionData(SVRegion.forest, ["Forest to Town", "Enter Secret Woods", "Forest to Wizard Tower", "Forest to Marnie's Ranch", - "Forest to Leah's Cottage", "Forest to Sewers", "Talk to Traveling Merchant"]), - RegionData(SVRegion.traveling_cart), - RegionData(SVRegion.farm_cave), - RegionData(SVRegion.greenhouse), - RegionData(SVRegion.mountain, - ["Mountain to Railroad", "Mountain to Tent", "Mountain to Carpenter Shop", "Mountain to The Mines", - "Enter Quarry", "Mountain to Adventurer's Guild", "Mountain to Town"]), - RegionData(SVRegion.tunnel_entrance, ["Enter Tunnel"]), - RegionData(SVRegion.tunnel), - RegionData(SVRegion.town, ["Town to Community Center", "Town to Beach", "Town to Hospital", - "Town to Pierre's General Store", "Town to Saloon", "Town to Alex's House", "Town to Trailer", - "Town to Mayor's Manor", - "Town to Sam's House", "Town to Haley's House", "Town to Sewers", "Town to Clint's Blacksmith", - "Town to Museum", - "Town to JojaMart"]), - RegionData(SVRegion.beach, ["Beach to Willy's Fish Shop", "Enter Elliott's House", "Enter Tide Pools"]), - RegionData(SVRegion.railroad, ["Enter Bathhouse Entrance", "Enter Witch Warp Cave"]), # "Enter Perfection Cutscene Area" - RegionData(SVRegion.ranch), - RegionData(SVRegion.leah_house), - RegionData(SVRegion.sewers, ["Enter Mutant Bug Lair"]), - RegionData(SVRegion.mutant_bug_lair), - RegionData(SVRegion.wizard_tower, ["Enter Wizard Basement"]), - RegionData(SVRegion.wizard_basement), - RegionData(SVRegion.tent), - RegionData(SVRegion.carpenter, ["Enter Sebastian's Room"]), - RegionData(SVRegion.sebastian_room), - RegionData(SVRegion.adventurer_guild), - RegionData(SVRegion.community_center, - ["Access Crafts Room", "Access Pantry", "Access Fish Tank", "Access Boiler Room", - "Access Bulletin Board", - "Access Vault"]), - RegionData(SVRegion.crafts_room), - RegionData(SVRegion.pantry), - RegionData(SVRegion.fish_tank), - RegionData(SVRegion.boiler_room), - RegionData(SVRegion.bulletin_board), - RegionData(SVRegion.vault), - RegionData(SVRegion.hospital, ["Enter Harvey's Room"]), - RegionData(SVRegion.harvey_room), - RegionData(SVRegion.pierre_store, ["Enter Sunroom"]), - RegionData(SVRegion.sunroom), - RegionData(SVRegion.saloon, ["Play Journey of the Prairie King", "Play Junimo Kart"]), - RegionData(SVRegion.alex_house), - RegionData(SVRegion.trailer), - RegionData(SVRegion.mayor_house), - RegionData(SVRegion.sam_house), - RegionData(SVRegion.haley_house), - RegionData(SVRegion.blacksmith), - RegionData(SVRegion.museum), - RegionData(SVRegion.jojamart), - RegionData(SVRegion.fish_shop), - RegionData(SVRegion.elliott_house), - RegionData(SVRegion.tide_pools), - RegionData(SVRegion.bathhouse_entrance, ["Enter Locker Room"]), - RegionData(SVRegion.locker_room, ["Enter Public Bath"]), - RegionData(SVRegion.public_bath), - RegionData(SVRegion.witch_warp_cave, ["Enter Witch's Swamp"]), - RegionData(SVRegion.witch_swamp), - RegionData(SVRegion.quarry, ["Enter Quarry Mine Entrance"]), - RegionData(SVRegion.quarry_mine_entrance, ["Enter Quarry Mine"]), - RegionData(SVRegion.quarry_mine), - RegionData(SVRegion.secret_woods), - RegionData(SVRegion.desert, ["Enter Skull Cavern Entrance"]), - RegionData(SVRegion.skull_cavern_entrance, ["Enter Skull Cavern"]), - RegionData(SVRegion.skull_cavern, ["Mine to Skull Cavern Floor 100"]), - RegionData(SVRegion.perfect_skull_cavern), - RegionData(SVRegion.ginger_island), - RegionData(SVRegion.jotpk_world_1, ["Reach JotPK World 2"]), - RegionData(SVRegion.jotpk_world_2, ["Reach JotPK World 3"]), - RegionData(SVRegion.jotpk_world_3), - RegionData(SVRegion.junimo_kart_1, ["Reach Junimo Kart 2"]), - RegionData(SVRegion.junimo_kart_2, ["Reach Junimo Kart 3"]), - RegionData(SVRegion.junimo_kart_3), - RegionData(SVRegion.mines, ["Dig to The Mines - Floor 5", "Dig to The Mines - Floor 10", "Dig to The Mines - Floor 15", - "Dig to The Mines - Floor 20", "Dig to The Mines - Floor 25", - "Dig to The Mines - Floor 30", - "Dig to The Mines - Floor 35", "Dig to The Mines - Floor 40", - "Dig to The Mines - Floor 45", - "Dig to The Mines - Floor 50", "Dig to The Mines - Floor 55", - "Dig to The Mines - Floor 60", - "Dig to The Mines - Floor 65", "Dig to The Mines - Floor 70", - "Dig to The Mines - Floor 75", - "Dig to The Mines - Floor 80", "Dig to The Mines - Floor 85", - "Dig to The Mines - Floor 90", - "Dig to The Mines - Floor 95", "Dig to The Mines - Floor 100", - "Dig to The Mines - Floor 105", - "Dig to The Mines - Floor 110", "Dig to The Mines - Floor 115", - "Dig to The Mines - Floor 120"]), - RegionData(SVRegion.mines_floor_5), - RegionData(SVRegion.mines_floor_10), - RegionData(SVRegion.mines_floor_15), - RegionData(SVRegion.mines_floor_20), - RegionData(SVRegion.mines_floor_25), - RegionData(SVRegion.mines_floor_30), - RegionData(SVRegion.mines_floor_35), - RegionData(SVRegion.mines_floor_40), - RegionData(SVRegion.mines_floor_45), - RegionData(SVRegion.mines_floor_50), - RegionData(SVRegion.mines_floor_55), - RegionData(SVRegion.mines_floor_60), - RegionData(SVRegion.mines_floor_65), - RegionData(SVRegion.mines_floor_70), - RegionData(SVRegion.mines_floor_75), - RegionData(SVRegion.mines_floor_80), - RegionData(SVRegion.mines_floor_85), - RegionData(SVRegion.mines_floor_90), - RegionData(SVRegion.mines_floor_95), - RegionData(SVRegion.mines_floor_100), - RegionData(SVRegion.mines_floor_105), - RegionData(SVRegion.mines_floor_110), - RegionData(SVRegion.mines_floor_115), - RegionData(SVRegion.mines_floor_120), +vanilla_regions = [ + RegionData(Region.menu, [Entrance.to_stardew_valley]), + RegionData(Region.stardew_valley, [Entrance.to_farmhouse]), + RegionData(Region.farm_house, [Entrance.farmhouse_to_farm, Entrance.downstairs_to_cellar]), + RegionData(Region.cellar), + RegionData(Region.farm, + [Entrance.farm_to_backwoods, Entrance.farm_to_bus_stop, Entrance.farm_to_forest, + Entrance.farm_to_farmcave, Entrance.enter_greenhouse, + Entrance.use_desert_obelisk, Entrance.use_island_obelisk]), + RegionData(Region.backwoods, [Entrance.backwoods_to_mountain]), + RegionData(Region.bus_stop, + [Entrance.bus_stop_to_town, Entrance.take_bus_to_desert, Entrance.bus_stop_to_tunnel_entrance]), + RegionData(Region.forest, + [Entrance.forest_to_town, Entrance.enter_secret_woods, Entrance.forest_to_wizard_tower, + Entrance.forest_to_marnie_ranch, + Entrance.forest_to_leah_cottage, Entrance.forest_to_sewer, + Entrance.buy_from_traveling_merchant]), + RegionData(Region.traveling_cart), + RegionData(Region.farm_cave), + RegionData(Region.greenhouse), + RegionData(Region.mountain, + [Entrance.mountain_to_railroad, Entrance.mountain_to_tent, Entrance.mountain_to_carpenter_shop, + Entrance.mountain_to_the_mines, Entrance.enter_quarry, Entrance.mountain_to_adventurer_guild, + Entrance.mountain_to_town, Entrance.mountain_to_maru_room, + Entrance.mountain_to_leo_treehouse]), + RegionData(Region.leo_treehouse), + RegionData(Region.maru_room), + RegionData(Region.tunnel_entrance, [Entrance.tunnel_entrance_to_bus_tunnel]), + RegionData(Region.bus_tunnel), + RegionData(Region.town, + [Entrance.town_to_community_center, Entrance.town_to_beach, Entrance.town_to_hospital, + Entrance.town_to_pierre_general_store, Entrance.town_to_saloon, Entrance.town_to_alex_house, + Entrance.town_to_trailer, + Entrance.town_to_mayor_manor, + Entrance.town_to_sam_house, Entrance.town_to_haley_house, Entrance.town_to_sewer, + Entrance.town_to_clint_blacksmith, + Entrance.town_to_museum, + Entrance.town_to_jojamart]), + RegionData(Region.beach, + [Entrance.beach_to_willy_fish_shop, Entrance.enter_elliott_house, Entrance.enter_tide_pools]), + RegionData(Region.railroad, [Entrance.enter_bathhouse_entrance, Entrance.enter_witch_warp_cave]), + RegionData(Region.ranch), + RegionData(Region.leah_house), + RegionData(Region.sewer, [Entrance.enter_mutant_bug_lair]), + RegionData(Region.mutant_bug_lair), + RegionData(Region.wizard_tower, [Entrance.enter_wizard_basement]), + RegionData(Region.wizard_basement), + RegionData(Region.tent), + RegionData(Region.carpenter, [Entrance.enter_sebastian_room]), + RegionData(Region.sebastian_room), + RegionData(Region.adventurer_guild), + RegionData(Region.community_center, + [Entrance.access_crafts_room, Entrance.access_pantry, Entrance.access_fish_tank, + Entrance.access_boiler_room, Entrance.access_bulletin_board, Entrance.access_vault]), + RegionData(Region.crafts_room), + RegionData(Region.pantry), + RegionData(Region.fish_tank), + RegionData(Region.boiler_room), + RegionData(Region.bulletin_board), + RegionData(Region.vault), + RegionData(Region.hospital, [Entrance.enter_harvey_room]), + RegionData(Region.harvey_room), + RegionData(Region.pierre_store, [Entrance.enter_sunroom]), + RegionData(Region.sunroom), + RegionData(Region.saloon, [Entrance.play_journey_of_the_prairie_king, Entrance.play_junimo_kart]), + RegionData(Region.alex_house), + RegionData(Region.trailer), + RegionData(Region.mayor_house), + RegionData(Region.sam_house), + RegionData(Region.haley_house), + RegionData(Region.blacksmith), + RegionData(Region.museum), + RegionData(Region.jojamart), + RegionData(Region.fish_shop, [Entrance.fish_shop_to_boat_tunnel]), + RegionData(Region.boat_tunnel, [Entrance.boat_to_ginger_island]), + RegionData(Region.elliott_house), + RegionData(Region.tide_pools), + RegionData(Region.bathhouse_entrance, [Entrance.enter_locker_room]), + RegionData(Region.locker_room, [Entrance.enter_public_bath]), + RegionData(Region.public_bath), + RegionData(Region.witch_warp_cave, [Entrance.enter_witch_swamp]), + RegionData(Region.witch_swamp, [Entrance.enter_witch_hut]), + RegionData(Region.witch_hut, [Entrance.witch_warp_to_wizard_basement]), + RegionData(Region.quarry, [Entrance.enter_quarry_mine_entrance]), + RegionData(Region.quarry_mine_entrance, [Entrance.enter_quarry_mine]), + RegionData(Region.quarry_mine), + RegionData(Region.secret_woods), + RegionData(Region.desert, [Entrance.enter_skull_cavern_entrance, Entrance.enter_oasis]), + RegionData(Region.oasis, [Entrance.enter_casino]), + RegionData(Region.casino), + RegionData(Region.skull_cavern_entrance, [Entrance.enter_skull_cavern]), + RegionData(Region.skull_cavern, [Entrance.mine_to_skull_cavern_floor_25, Entrance.mine_to_skull_cavern_floor_50, + Entrance.mine_to_skull_cavern_floor_75, Entrance.mine_to_skull_cavern_floor_100, + Entrance.mine_to_skull_cavern_floor_125, Entrance.mine_to_skull_cavern_floor_150, + Entrance.mine_to_skull_cavern_floor_175, Entrance.mine_to_skull_cavern_floor_200]), + RegionData(Region.skull_cavern_25), + RegionData(Region.skull_cavern_50), + RegionData(Region.skull_cavern_75), + RegionData(Region.skull_cavern_100), + RegionData(Region.skull_cavern_125), + RegionData(Region.skull_cavern_150), + RegionData(Region.skull_cavern_175), + RegionData(Region.skull_cavern_200), + RegionData(Region.island_south, [Entrance.island_south_to_west, Entrance.island_south_to_north, + Entrance.island_south_to_east, Entrance.island_south_to_southeast, + Entrance.use_island_resort, + Entrance.parrot_express_docks_to_volcano, + Entrance.parrot_express_docks_to_dig_site, + Entrance.parrot_express_docks_to_jungle]), + RegionData(Region.island_resort), + RegionData(Region.island_west, + [Entrance.island_west_to_islandfarmhouse, Entrance.island_west_to_gourmand_cave, + Entrance.island_west_to_crystals_cave, Entrance.island_west_to_shipwreck, + Entrance.island_west_to_qi_walnut_room, Entrance.use_farm_obelisk, + Entrance.parrot_express_jungle_to_docks, Entrance.parrot_express_jungle_to_dig_site, + Entrance.parrot_express_jungle_to_volcano]), + RegionData(Region.island_east, [Entrance.island_east_to_leo_hut, Entrance.island_east_to_island_shrine]), + RegionData(Region.island_shrine), + RegionData(Region.island_south_east, [Entrance.island_southeast_to_pirate_cove]), + RegionData(Region.island_north, [Entrance.talk_to_island_trader, Entrance.island_north_to_field_office, + Entrance.island_north_to_dig_site, Entrance.island_north_to_volcano, + Entrance.parrot_express_volcano_to_dig_site, + Entrance.parrot_express_volcano_to_jungle, + Entrance.parrot_express_volcano_to_docks]), + RegionData(Region.volcano, [Entrance.climb_to_volcano_5, Entrance.volcano_to_secret_beach]), + RegionData(Region.volcano_secret_beach), + RegionData(Region.volcano_floor_5, [Entrance.talk_to_volcano_dwarf, Entrance.climb_to_volcano_10]), + RegionData(Region.volcano_dwarf_shop), + RegionData(Region.volcano_floor_10), + RegionData(Region.island_trader), + RegionData(Region.island_farmhouse), + RegionData(Region.gourmand_frog_cave), + RegionData(Region.colored_crystals_cave), + RegionData(Region.shipwreck), + RegionData(Region.qi_walnut_room), + RegionData(Region.leo_hut), + RegionData(Region.pirate_cove), + RegionData(Region.field_office), + RegionData(Region.dig_site, + [Entrance.dig_site_to_professor_snail_cave, Entrance.parrot_express_dig_site_to_volcano, + Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_dig_site_to_jungle]), + RegionData(Region.professor_snail_cave), + RegionData(Region.jotpk_world_1, [Entrance.reach_jotpk_world_2]), + RegionData(Region.jotpk_world_2, [Entrance.reach_jotpk_world_3]), + RegionData(Region.jotpk_world_3), + RegionData(Region.junimo_kart_1, [Entrance.reach_junimo_kart_2]), + RegionData(Region.junimo_kart_2, [Entrance.reach_junimo_kart_3]), + RegionData(Region.junimo_kart_3), + RegionData(Region.mines, [Entrance.talk_to_mines_dwarf, + Entrance.dig_to_mines_floor_5, Entrance.dig_to_mines_floor_10, + Entrance.dig_to_mines_floor_15, Entrance.dig_to_mines_floor_20, + Entrance.dig_to_mines_floor_25, Entrance.dig_to_mines_floor_30, + Entrance.dig_to_mines_floor_35, Entrance.dig_to_mines_floor_40, + Entrance.dig_to_mines_floor_45, Entrance.dig_to_mines_floor_50, + Entrance.dig_to_mines_floor_55, Entrance.dig_to_mines_floor_60, + Entrance.dig_to_mines_floor_65, Entrance.dig_to_mines_floor_70, + Entrance.dig_to_mines_floor_75, Entrance.dig_to_mines_floor_80, + Entrance.dig_to_mines_floor_85, Entrance.dig_to_mines_floor_90, + Entrance.dig_to_mines_floor_95, Entrance.dig_to_mines_floor_100, + Entrance.dig_to_mines_floor_105, Entrance.dig_to_mines_floor_110, + Entrance.dig_to_mines_floor_115, Entrance.dig_to_mines_floor_120]), + RegionData(Region.mines_dwarf_shop), + RegionData(Region.mines_floor_5), + RegionData(Region.mines_floor_10), + RegionData(Region.mines_floor_15), + RegionData(Region.mines_floor_20), + RegionData(Region.mines_floor_25), + RegionData(Region.mines_floor_30), + RegionData(Region.mines_floor_35), + RegionData(Region.mines_floor_40), + RegionData(Region.mines_floor_45), + RegionData(Region.mines_floor_50), + RegionData(Region.mines_floor_55), + RegionData(Region.mines_floor_60), + RegionData(Region.mines_floor_65), + RegionData(Region.mines_floor_70), + RegionData(Region.mines_floor_75), + RegionData(Region.mines_floor_80), + RegionData(Region.mines_floor_85), + RegionData(Region.mines_floor_90), + RegionData(Region.mines_floor_95), + RegionData(Region.mines_floor_100), + RegionData(Region.mines_floor_105), + RegionData(Region.mines_floor_110), + RegionData(Region.mines_floor_115), + RegionData(Region.mines_floor_120), ] # Exists and where they lead -mandatory_connections = [ - ConnectionData("To Stardew Valley", SVRegion.stardew_valley), - ConnectionData("To Farmhouse", SVRegion.farm_house), - ConnectionData("Outside to Farm", SVRegion.farm), - ConnectionData("Downstairs to Cellar", SVRegion.cellar), - ConnectionData("Farm to Backwoods", SVRegion.backwoods), - ConnectionData("Farm to Bus Stop", SVRegion.bus_stop), - ConnectionData("Farm to Forest", SVRegion.forest), - ConnectionData("Farm to Farmcave", SVRegion.farm_cave, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Enter Greenhouse", SVRegion.greenhouse), - ConnectionData("Use Desert Obelisk", SVRegion.desert), - ConnectionData("Use Island Obelisk", SVRegion.ginger_island), - ConnectionData("Backwoods to Mountain", SVRegion.mountain), - ConnectionData("Bus Stop to Town", SVRegion.town), - ConnectionData("Bus Stop to Tunnel Entrance", SVRegion.tunnel_entrance), - ConnectionData("Take Bus to Desert", SVRegion.desert), - ConnectionData("Enter Tunnel", SVRegion.tunnel), - ConnectionData("Forest to Town", SVRegion.town), - ConnectionData("Forest to Wizard Tower", SVRegion.wizard_tower, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Enter Wizard Basement", SVRegion.wizard_basement), - ConnectionData("Forest to Marnie's Ranch", SVRegion.ranch, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Forest to Leah's Cottage", SVRegion.leah_house), - ConnectionData("Enter Secret Woods", SVRegion.secret_woods), - ConnectionData("Forest to Sewers", SVRegion.sewers), - ConnectionData("Talk to Traveling Merchant", SVRegion.traveling_cart), - ConnectionData("Town to Sewers", SVRegion.sewers), - ConnectionData("Enter Mutant Bug Lair", SVRegion.mutant_bug_lair), - ConnectionData("Mountain to Railroad", SVRegion.railroad), - ConnectionData("Mountain to Tent", SVRegion.tent, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Mountain to Carpenter Shop", SVRegion.carpenter, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Enter Sebastian's Room", SVRegion.sebastian_room), - ConnectionData("Mountain to Adventurer's Guild", SVRegion.adventurer_guild), - ConnectionData("Enter Quarry", SVRegion.quarry), - ConnectionData("Enter Quarry Mine Entrance", SVRegion.quarry_mine_entrance), - ConnectionData("Enter Quarry Mine", SVRegion.quarry_mine), - ConnectionData("Mountain to Town", SVRegion.town), - ConnectionData("Town to Community Center", SVRegion.community_center, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Access Crafts Room", SVRegion.crafts_room), - ConnectionData("Access Pantry", SVRegion.pantry), - ConnectionData("Access Fish Tank", SVRegion.fish_tank), - ConnectionData("Access Boiler Room", SVRegion.boiler_room), - ConnectionData("Access Bulletin Board", SVRegion.bulletin_board), - ConnectionData("Access Vault", SVRegion.vault), - ConnectionData("Town to Hospital", SVRegion.hospital, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Enter Harvey's Room", SVRegion.harvey_room), - ConnectionData("Town to Pierre's General Store", SVRegion.pierre_store, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Enter Sunroom", SVRegion.sunroom), - ConnectionData("Town to Clint's Blacksmith", SVRegion.blacksmith, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Saloon", SVRegion.saloon, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Play Journey of the Prairie King", SVRegion.jotpk_world_1), - ConnectionData("Reach JotPK World 2", SVRegion.jotpk_world_2), - ConnectionData("Reach JotPK World 3", SVRegion.jotpk_world_3), - ConnectionData("Play Junimo Kart", SVRegion.junimo_kart_1), - ConnectionData("Reach Junimo Kart 2", SVRegion.junimo_kart_2), - ConnectionData("Reach Junimo Kart 3", SVRegion.junimo_kart_3), - ConnectionData("Town to Sam's House", SVRegion.sam_house, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Haley's House", SVRegion.haley_house, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Mayor's Manor", SVRegion.mayor_house, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Alex's House", SVRegion.alex_house, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Trailer", SVRegion.trailer, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Museum", SVRegion.museum, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to JojaMart", SVRegion.jojamart, flag=RandomizationFlag.PELICAN_TOWN), - ConnectionData("Town to Beach", SVRegion.beach), - ConnectionData("Enter Elliott's House", SVRegion.elliott_house), - ConnectionData("Beach to Willy's Fish Shop", SVRegion.fish_shop, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Enter Tide Pools", SVRegion.tide_pools), - ConnectionData("Mountain to The Mines", SVRegion.mines, flag=RandomizationFlag.NON_PROGRESSION), - ConnectionData("Dig to The Mines - Floor 5", SVRegion.mines_floor_5), - ConnectionData("Dig to The Mines - Floor 10", SVRegion.mines_floor_10), - ConnectionData("Dig to The Mines - Floor 15", SVRegion.mines_floor_15), - ConnectionData("Dig to The Mines - Floor 20", SVRegion.mines_floor_20), - ConnectionData("Dig to The Mines - Floor 25", SVRegion.mines_floor_25), - ConnectionData("Dig to The Mines - Floor 30", SVRegion.mines_floor_30), - ConnectionData("Dig to The Mines - Floor 35", SVRegion.mines_floor_35), - ConnectionData("Dig to The Mines - Floor 40", SVRegion.mines_floor_40), - ConnectionData("Dig to The Mines - Floor 45", SVRegion.mines_floor_45), - ConnectionData("Dig to The Mines - Floor 50", SVRegion.mines_floor_50), - ConnectionData("Dig to The Mines - Floor 55", SVRegion.mines_floor_55), - ConnectionData("Dig to The Mines - Floor 60", SVRegion.mines_floor_60), - ConnectionData("Dig to The Mines - Floor 65", SVRegion.mines_floor_65), - ConnectionData("Dig to The Mines - Floor 70", SVRegion.mines_floor_70), - ConnectionData("Dig to The Mines - Floor 75", SVRegion.mines_floor_75), - ConnectionData("Dig to The Mines - Floor 80", SVRegion.mines_floor_80), - ConnectionData("Dig to The Mines - Floor 85", SVRegion.mines_floor_85), - ConnectionData("Dig to The Mines - Floor 90", SVRegion.mines_floor_90), - ConnectionData("Dig to The Mines - Floor 95", SVRegion.mines_floor_95), - ConnectionData("Dig to The Mines - Floor 100", SVRegion.mines_floor_100), - ConnectionData("Dig to The Mines - Floor 105", SVRegion.mines_floor_105), - ConnectionData("Dig to The Mines - Floor 110", SVRegion.mines_floor_110), - ConnectionData("Dig to The Mines - Floor 115", SVRegion.mines_floor_115), - ConnectionData("Dig to The Mines - Floor 120", SVRegion.mines_floor_120), - ConnectionData("Enter Skull Cavern Entrance", SVRegion.skull_cavern_entrance), - ConnectionData("Enter Skull Cavern", SVRegion.skull_cavern), - ConnectionData("Mine to Skull Cavern Floor 100", SVRegion.perfect_skull_cavern), - ConnectionData("Enter Witch Warp Cave", SVRegion.witch_warp_cave), - ConnectionData("Enter Witch's Swamp", SVRegion.witch_swamp), - ConnectionData("Enter Bathhouse Entrance", SVRegion.bathhouse_entrance), - ConnectionData("Enter Locker Room", SVRegion.locker_room), - ConnectionData("Enter Public Bath", SVRegion.public_bath), +vanilla_connections = [ + ConnectionData(Entrance.to_stardew_valley, Region.stardew_valley), + ConnectionData(Entrance.to_farmhouse, Region.farm_house), + ConnectionData(Entrance.farmhouse_to_farm, Region.farm), + ConnectionData(Entrance.downstairs_to_cellar, Region.cellar), + ConnectionData(Entrance.farm_to_backwoods, Region.backwoods), + ConnectionData(Entrance.farm_to_bus_stop, Region.bus_stop), + ConnectionData(Entrance.farm_to_forest, Region.forest), + ConnectionData(Entrance.farm_to_farmcave, Region.farm_cave, flag=RandomizationFlag.NON_PROGRESSION), + ConnectionData(Entrance.enter_greenhouse, Region.greenhouse), + ConnectionData(Entrance.use_desert_obelisk, Region.desert), + ConnectionData(Entrance.use_island_obelisk, Region.island_south), + ConnectionData(Entrance.use_farm_obelisk, Region.farm), + ConnectionData(Entrance.backwoods_to_mountain, Region.mountain), + ConnectionData(Entrance.bus_stop_to_town, Region.town), + ConnectionData(Entrance.bus_stop_to_tunnel_entrance, Region.tunnel_entrance), + ConnectionData(Entrance.tunnel_entrance_to_bus_tunnel, Region.bus_tunnel, flag=RandomizationFlag.NON_PROGRESSION), + ConnectionData(Entrance.take_bus_to_desert, Region.desert), + ConnectionData(Entrance.forest_to_town, Region.town), + ConnectionData(Entrance.forest_to_wizard_tower, Region.wizard_tower, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_wizard_basement, Region.wizard_basement, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.forest_to_marnie_ranch, Region.ranch, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.forest_to_leah_cottage, Region.leah_house, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_secret_woods, Region.secret_woods), + ConnectionData(Entrance.forest_to_sewer, Region.sewer, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.buy_from_traveling_merchant, Region.traveling_cart), + ConnectionData(Entrance.town_to_sewer, Region.sewer, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_mutant_bug_lair, Region.mutant_bug_lair, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.mountain_to_railroad, Region.railroad), + ConnectionData(Entrance.mountain_to_tent, Region.tent, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.mountain_to_leo_treehouse, Region.leo_treehouse, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.mountain_to_carpenter_shop, Region.carpenter, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.mountain_to_maru_room, Region.maru_room, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_sebastian_room, Region.sebastian_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.mountain_to_adventurer_guild, Region.adventurer_guild, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_quarry, Region.quarry), + ConnectionData(Entrance.enter_quarry_mine_entrance, Region.quarry_mine_entrance, + flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_quarry_mine, Region.quarry_mine), + ConnectionData(Entrance.mountain_to_town, Region.town), + ConnectionData(Entrance.town_to_community_center, Region.community_center, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.access_crafts_room, Region.crafts_room), + ConnectionData(Entrance.access_pantry, Region.pantry), + ConnectionData(Entrance.access_fish_tank, Region.fish_tank), + ConnectionData(Entrance.access_boiler_room, Region.boiler_room), + ConnectionData(Entrance.access_bulletin_board, Region.bulletin_board), + ConnectionData(Entrance.access_vault, Region.vault), + ConnectionData(Entrance.town_to_hospital, Region.hospital, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_harvey_room, Region.harvey_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.town_to_pierre_general_store, Region.pierre_store, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_sunroom, Region.sunroom, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.town_to_clint_blacksmith, Region.blacksmith, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_saloon, Region.saloon, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.play_journey_of_the_prairie_king, Region.jotpk_world_1), + ConnectionData(Entrance.reach_jotpk_world_2, Region.jotpk_world_2), + ConnectionData(Entrance.reach_jotpk_world_3, Region.jotpk_world_3), + ConnectionData(Entrance.play_junimo_kart, Region.junimo_kart_1), + ConnectionData(Entrance.reach_junimo_kart_2, Region.junimo_kart_2), + ConnectionData(Entrance.reach_junimo_kart_3, Region.junimo_kart_3), + ConnectionData(Entrance.town_to_sam_house, Region.sam_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_haley_house, Region.haley_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_mayor_manor, Region.mayor_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_alex_house, Region.alex_house, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_trailer, Region.trailer, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_museum, Region.museum, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_jojamart, Region.jojamart, + flag=RandomizationFlag.PELICAN_TOWN | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.town_to_beach, Region.beach), + ConnectionData(Entrance.enter_elliott_house, Region.elliott_house, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.beach_to_willy_fish_shop, Region.fish_shop, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.fish_shop_to_boat_tunnel, Region.boat_tunnel, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.boat_to_ginger_island, Region.island_south), + ConnectionData(Entrance.enter_tide_pools, Region.tide_pools), + ConnectionData(Entrance.mountain_to_the_mines, Region.mines, + flag=RandomizationFlag.NON_PROGRESSION | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.talk_to_mines_dwarf, Region.mines_dwarf_shop), + ConnectionData(Entrance.dig_to_mines_floor_5, Region.mines_floor_5), + ConnectionData(Entrance.dig_to_mines_floor_10, Region.mines_floor_10), + ConnectionData(Entrance.dig_to_mines_floor_15, Region.mines_floor_15), + ConnectionData(Entrance.dig_to_mines_floor_20, Region.mines_floor_20), + ConnectionData(Entrance.dig_to_mines_floor_25, Region.mines_floor_25), + ConnectionData(Entrance.dig_to_mines_floor_30, Region.mines_floor_30), + ConnectionData(Entrance.dig_to_mines_floor_35, Region.mines_floor_35), + ConnectionData(Entrance.dig_to_mines_floor_40, Region.mines_floor_40), + ConnectionData(Entrance.dig_to_mines_floor_45, Region.mines_floor_45), + ConnectionData(Entrance.dig_to_mines_floor_50, Region.mines_floor_50), + ConnectionData(Entrance.dig_to_mines_floor_55, Region.mines_floor_55), + ConnectionData(Entrance.dig_to_mines_floor_60, Region.mines_floor_60), + ConnectionData(Entrance.dig_to_mines_floor_65, Region.mines_floor_65), + ConnectionData(Entrance.dig_to_mines_floor_70, Region.mines_floor_70), + ConnectionData(Entrance.dig_to_mines_floor_75, Region.mines_floor_75), + ConnectionData(Entrance.dig_to_mines_floor_80, Region.mines_floor_80), + ConnectionData(Entrance.dig_to_mines_floor_85, Region.mines_floor_85), + ConnectionData(Entrance.dig_to_mines_floor_90, Region.mines_floor_90), + ConnectionData(Entrance.dig_to_mines_floor_95, Region.mines_floor_95), + ConnectionData(Entrance.dig_to_mines_floor_100, Region.mines_floor_100), + ConnectionData(Entrance.dig_to_mines_floor_105, Region.mines_floor_105), + ConnectionData(Entrance.dig_to_mines_floor_110, Region.mines_floor_110), + ConnectionData(Entrance.dig_to_mines_floor_115, Region.mines_floor_115), + ConnectionData(Entrance.dig_to_mines_floor_120, Region.mines_floor_120), + ConnectionData(Entrance.enter_skull_cavern_entrance, Region.skull_cavern_entrance, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_oasis, Region.oasis, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_casino, Region.casino, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_skull_cavern, Region.skull_cavern), + ConnectionData(Entrance.mine_to_skull_cavern_floor_25, Region.skull_cavern_25), + ConnectionData(Entrance.mine_to_skull_cavern_floor_50, Region.skull_cavern_50), + ConnectionData(Entrance.mine_to_skull_cavern_floor_75, Region.skull_cavern_75), + ConnectionData(Entrance.mine_to_skull_cavern_floor_100, Region.skull_cavern_100), + ConnectionData(Entrance.mine_to_skull_cavern_floor_125, Region.skull_cavern_125), + ConnectionData(Entrance.mine_to_skull_cavern_floor_150, Region.skull_cavern_150), + ConnectionData(Entrance.mine_to_skull_cavern_floor_175, Region.skull_cavern_175), + ConnectionData(Entrance.mine_to_skull_cavern_floor_200, Region.skull_cavern_200), + ConnectionData(Entrance.enter_witch_warp_cave, Region.witch_warp_cave, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_witch_swamp, Region.witch_swamp, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_witch_hut, Region.witch_hut, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.witch_warp_to_wizard_basement, Region.wizard_basement, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_bathhouse_entrance, Region.bathhouse_entrance, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.LEAD_TO_OPEN_AREA), + ConnectionData(Entrance.enter_locker_room, Region.locker_room, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.enter_public_bath, Region.public_bath, flag=RandomizationFlag.BUILDINGS), + ConnectionData(Entrance.island_south_to_west, Region.island_west, flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_south_to_north, Region.island_north, flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_south_to_east, Region.island_east, flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_south_to_southeast, Region.island_south_east, + flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.use_island_resort, Region.island_resort), + ConnectionData(Entrance.island_west_to_islandfarmhouse, Region.island_farmhouse, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_west_to_gourmand_cave, Region.gourmand_frog_cave, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_west_to_crystals_cave, Region.colored_crystals_cave, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_west_to_shipwreck, Region.shipwreck, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_west_to_qi_walnut_room, Region.qi_walnut_room, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_east_to_leo_hut, Region.leo_hut, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_east_to_island_shrine, Region.island_shrine, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_southeast_to_pirate_cove, Region.pirate_cove, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_north_to_field_office, Region.field_office, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_north_to_dig_site, Region.dig_site, flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.dig_site_to_professor_snail_cave, Region.professor_snail_cave, flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.island_north_to_volcano, Region.volcano, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.volcano_to_secret_beach, Region.volcano_secret_beach, + flag=RandomizationFlag.BUILDINGS | RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.talk_to_island_trader, Region.island_trader, flag=RandomizationFlag.GINGER_ISLAND), + ConnectionData(Entrance.climb_to_volcano_5, Region.volcano_floor_5), + ConnectionData(Entrance.talk_to_volcano_dwarf, Region.volcano_dwarf_shop), + ConnectionData(Entrance.climb_to_volcano_10, Region.volcano_floor_10), + ConnectionData(Entrance.parrot_express_jungle_to_docks, Region.island_south), + ConnectionData(Entrance.parrot_express_dig_site_to_docks, Region.island_south), + ConnectionData(Entrance.parrot_express_volcano_to_docks, Region.island_south), + ConnectionData(Entrance.parrot_express_volcano_to_jungle, Region.island_west), + ConnectionData(Entrance.parrot_express_docks_to_jungle, Region.island_west), + ConnectionData(Entrance.parrot_express_dig_site_to_jungle, Region.island_west), + ConnectionData(Entrance.parrot_express_docks_to_dig_site, Region.dig_site), + ConnectionData(Entrance.parrot_express_volcano_to_dig_site, Region.dig_site), + ConnectionData(Entrance.parrot_express_jungle_to_dig_site, Region.dig_site), + ConnectionData(Entrance.parrot_express_dig_site_to_volcano, Region.island_north), + ConnectionData(Entrance.parrot_express_docks_to_volcano, Region.island_north), + ConnectionData(Entrance.parrot_express_jungle_to_volcano, Region.island_north), ] +def create_final_regions(world_options: StardewOptions) -> List[RegionData]: + final_regions = [] + final_regions.extend(vanilla_regions) + if world_options[options.Mods] is None: + return final_regions + for mod in world_options[options.Mods]: + if mod not in ModDataList: + continue + for mod_region in ModDataList[mod].regions: + existing_region = next( + (region for region in final_regions if region.name == mod_region.name), None) + if existing_region: + final_regions.remove(existing_region) + final_regions.append(existing_region.get_merged_with(mod_region.exits)) + continue + + final_regions.append(mod_region.get_clone()) + return final_regions + + +def create_final_connections(world_options: StardewOptions) -> List[ConnectionData]: + final_connections = [] + final_connections.extend(vanilla_connections) + if world_options[options.Mods] is None: + return final_connections + for mod in world_options[options.Mods]: + if mod not in ModDataList: + continue + final_connections.extend(ModDataList[mod].connections) + return final_connections + + def create_regions(region_factory: RegionFactory, random: Random, world_options: StardewOptions) -> Tuple[ Iterable[Region], Dict[str, str]]: + final_regions = create_final_regions(world_options) regions: Dict[str: Region] = {region.name: region_factory(region.name, region.exits) for region in - stardew_valley_regions} + final_regions} entrances: Dict[str: Entrance] = {entrance.name: entrance for region in regions.values() for entrance in region.exits} - connections, randomized_data = randomize_connections(random, world_options) + regions_by_name: Dict[str, RegionData] = {region.name: region for region in final_regions} + connections, randomized_data = randomize_connections(random, world_options, regions_by_name) for connection in connections: - if connection.name not in entrances: - continue - entrances[connection.name].connect(regions[connection.destination]) + if connection.name in entrances: + entrances[connection.name].connect(regions[connection.destination]) return regions.values(), randomized_data -def randomize_connections(random: Random, world_options: StardewOptions) -> Tuple[List[ConnectionData], Dict[str, str]]: +def randomize_connections(random: Random, world_options: StardewOptions, regions_by_name) -> Tuple[ + List[ConnectionData], Dict[str, str]]: connections_to_randomize = [] + final_connections = create_final_connections(world_options) + connections_by_name: Dict[str, ConnectionData] = {connection.name: connection for connection in final_connections} if world_options[options.EntranceRandomization] == options.EntranceRandomization.option_pelican_town: - connections_to_randomize = [connection for connection in mandatory_connections if + connections_to_randomize = [connection for connection in final_connections if RandomizationFlag.PELICAN_TOWN in connection.flag] elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_non_progression: - connections_to_randomize = [connection for connection in mandatory_connections if + connections_to_randomize = [connection for connection in final_connections if RandomizationFlag.NON_PROGRESSION in connection.flag] - random.shuffle(connections_to_randomize) + elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_buildings: + connections_to_randomize = [connection for connection in final_connections if + RandomizationFlag.BUILDINGS in connection.flag] + elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_chaos: + connections_to_randomize = [connection for connection in final_connections if + RandomizationFlag.BUILDINGS in connection.flag] + connections_to_randomize = exclude_island_if_necessary(connections_to_randomize, world_options) + # On Chaos, we just add the connections to randomize, unshuffled, and the client does it every day + randomized_data_for_mod = {} + for connection in connections_to_randomize: + randomized_data_for_mod[connection.name] = connection.name + randomized_data_for_mod[connection.reverse] = connection.reverse + return final_connections, randomized_data_for_mod + + connections_to_randomize = remove_excluded_entrances(connections_to_randomize, world_options) + + random.shuffle(connections_to_randomize) destination_pool = list(connections_to_randomize) random.shuffle(destination_pool) - randomized_connections = [] - randomized_data = {} + randomized_connections = randomize_chosen_connections(connections_to_randomize, destination_pool) + add_non_randomized_connections(final_connections, connections_to_randomize, randomized_connections) + + swap_connections_until_valid(regions_by_name, connections_by_name, randomized_connections, connections_to_randomize, random) + randomized_connections_for_generation = create_connections_for_generation(randomized_connections) + randomized_data_for_mod = create_data_for_mod(randomized_connections, connections_to_randomize) + + return randomized_connections_for_generation, randomized_data_for_mod + + +def remove_excluded_entrances(connections_to_randomize, world_options): + exclude_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true + exclude_sewers = world_options[options.Museumsanity] == options.Museumsanity.option_none + if exclude_island: + connections_to_randomize = [connection for connection in connections_to_randomize if RandomizationFlag.GINGER_ISLAND not in connection.flag] + if exclude_sewers: + connections_to_randomize = [connection for connection in connections_to_randomize if Region.sewer not in connection.name or Region.sewer not in connection.reverse] + + return connections_to_randomize + + +def exclude_island_if_necessary(connections_to_randomize: List[ConnectionData], world_options) -> List[ConnectionData]: + exclude_island = world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true + if exclude_island: + connections_to_randomize = [connection for connection in connections_to_randomize if + RandomizationFlag.GINGER_ISLAND not in connection.flag] + return connections_to_randomize + + +def randomize_chosen_connections(connections_to_randomize: List[ConnectionData], + destination_pool: List[ConnectionData]) -> Dict[ConnectionData, ConnectionData]: + randomized_connections = {} for connection in connections_to_randomize: destination = destination_pool.pop() - randomized_connections.append(ConnectionData(connection.name, destination.destination, destination.reverse)) - randomized_data[connection.name] = destination.name - randomized_data[destination.reverse] = connection.reverse + randomized_connections[connection] = destination + return randomized_connections - return mandatory_connections, randomized_data + +def create_connections_for_generation(randomized_connections: Dict[ConnectionData, ConnectionData]) -> List[ + ConnectionData]: + connections = [] + for connection in randomized_connections: + destination = randomized_connections[connection] + connections.append(ConnectionData(connection.name, destination.destination, destination.reverse)) + return connections + + +def create_data_for_mod(randomized_connections: Dict[ConnectionData, ConnectionData], + connections_to_randomize: List[ConnectionData]) -> Dict[str, str]: + randomized_data_for_mod = {} + for connection in randomized_connections: + if connection not in connections_to_randomize: + continue + destination = randomized_connections[connection] + add_to_mod_data(connection, destination, randomized_data_for_mod) + return randomized_data_for_mod + +def add_to_mod_data(connection: ConnectionData, destination: ConnectionData, randomized_data_for_mod: Dict[str, str]): + randomized_data_for_mod[connection.name] = destination.name + randomized_data_for_mod[destination.reverse] = connection.reverse + + +def add_non_randomized_connections(connections, connections_to_randomize: List[ConnectionData], + randomized_connections: Dict[ConnectionData, ConnectionData]): + for connection in connections: + if connection in connections_to_randomize: + continue + randomized_connections[connection] = connection + + +def swap_connections_until_valid(regions_by_name, connections_by_name, randomized_connections: Dict[ConnectionData, ConnectionData], + connections_to_randomize: List[ConnectionData], random: Random): + while True: + reachable_regions, unreachable_regions = find_reachable_regions(regions_by_name, connections_by_name, randomized_connections) + if not unreachable_regions: + return randomized_connections + swap_one_connection(regions_by_name, connections_by_name, randomized_connections, reachable_regions, + unreachable_regions, connections_to_randomize, random) + + +def find_reachable_regions(regions_by_name, connections_by_name, + randomized_connections: Dict[ConnectionData, ConnectionData]): + reachable_regions = {Region.menu} + unreachable_regions = {region for region in regions_by_name.keys()} + unreachable_regions.remove(Region.menu) + exits_to_explore = list(regions_by_name[Region.menu].exits) + while exits_to_explore: + exit_name = exits_to_explore.pop() + exit_connection = connections_by_name[exit_name] + replaced_connection = randomized_connections[exit_connection] + target_region_name = replaced_connection.destination + if target_region_name in reachable_regions: + continue + + target_region = regions_by_name[target_region_name] + reachable_regions.add(target_region_name) + unreachable_regions.remove(target_region_name) + exits_to_explore.extend(target_region.exits) + return reachable_regions, unreachable_regions + + +def swap_one_connection(regions_by_name, connections_by_name,randomized_connections: Dict[ConnectionData, ConnectionData], + reachable_regions: Set[str], unreachable_regions: Set[str], + connections_to_randomize: List[ConnectionData], random: Random): + randomized_connections_already_shuffled = {connection: randomized_connections[connection] + for connection in randomized_connections + if connection != randomized_connections[connection]} + unreachable_regions_names_leading_somewhere = tuple([region for region in unreachable_regions + if len(regions_by_name[region].exits) > 0]) + unreachable_regions_leading_somewhere = [regions_by_name[region_name] for region_name in unreachable_regions_names_leading_somewhere] + unreachable_regions_exits_names = [exit_name for region in unreachable_regions_leading_somewhere for exit_name in region.exits] + unreachable_connections = [connections_by_name[exit_name] for exit_name in unreachable_regions_exits_names] + unreachable_connections_that_can_be_randomized = [connection for connection in unreachable_connections if connection in connections_to_randomize] + + chosen_unreachable_entrance = random.choice(unreachable_connections_that_can_be_randomized) + + chosen_reachable_entrance = None + while chosen_reachable_entrance is None or chosen_reachable_entrance not in randomized_connections_already_shuffled: + chosen_reachable_region_name = random.choice(sorted(reachable_regions)) + chosen_reachable_region = regions_by_name[chosen_reachable_region_name] + if not any(chosen_reachable_region.exits): + continue + chosen_reachable_entrance_name = random.choice(chosen_reachable_region.exits) + chosen_reachable_entrance = connections_by_name[chosen_reachable_entrance_name] + + reachable_destination = randomized_connections[chosen_reachable_entrance] + unreachable_destination = randomized_connections[chosen_unreachable_entrance] + randomized_connections[chosen_reachable_entrance] = unreachable_destination + randomized_connections[chosen_unreachable_entrance] = reachable_destination diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index ad48dc07..93d45f9b 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -5,85 +5,59 @@ from BaseClasses import MultiWorld from worlds.generic import Rules as MultiWorldRules from . import options, locations from .bundles import Bundle +from .data.crops_data import crops_by_name +from .strings.entrance_names import dig_to_mines_floor, dig_to_skull_floor, Entrance, move_to_woods_depth, \ + DeepWoodsEntrance, AlecEntrance, MagicEntrance from .data.museum_data import all_museum_items, all_mineral_items, all_artifact_items, \ dwarf_scrolls, skeleton_front, \ skeleton_middle, skeleton_back, all_museum_items_by_name +from .strings.region_names import Region +from .mods.mod_data import ModNames +from .mods.logic import magic, skills, deepwoods from .locations import LocationTags -from .logic import StardewLogic, And, month_end_per_skill_level, tool_prices, week_days +from .logic import StardewLogic, And, tool_upgrade_prices from .options import StardewOptions +from .strings.ap_names.transport_names import Transportation +from .strings.artisan_good_names import ArtisanGood +from .strings.calendar_names import Weekday +from .strings.craftable_names import Craftable +from .strings.material_names import Material +from .strings.metal_names import MetalBar +from .strings.spells import MagicSpell +from .strings.skill_names import ModSkill, Skill +from .strings.tool_names import Tool, ToolMaterial +from .strings.villager_names import NPC, ModNPC +from .strings.wallet_item_names import Wallet def set_rules(multi_world: MultiWorld, player: int, world_options: StardewOptions, logic: StardewLogic, current_bundles: Dict[str, Bundle]): all_location_names = list(location.name for location in multi_world.get_locations(player)) - for floor in range(5, 120 + 5, 5): - MultiWorldRules.set_rule(multi_world.get_entrance(f"Dig to The Mines - Floor {floor}", player), - logic.can_mine_to_floor(floor).simplify()) + set_entrance_rules(logic, multi_world, player, world_options) - MultiWorldRules.set_rule(multi_world.get_entrance("Enter Tide Pools", player), - logic.received("Beach Bridge").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Enter Quarry", player), - logic.received("Bridge Repair").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Enter Secret Woods", player), - logic.has_tool("Axe", "Iron").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Forest to Sewers", player), - logic.has_rusty_key().simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Town to Sewers", player), - logic.has_rusty_key().simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Take Bus to Desert", player), - logic.received("Bus Repair").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Enter Skull Cavern", player), - logic.received("Skull Key").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Mine to Skull Cavern Floor 100", player), - logic.can_mine_perfectly_in_the_skull_cavern().simplify()) - - MultiWorldRules.set_rule(multi_world.get_entrance("Use Desert Obelisk", player), - logic.received("Desert Obelisk").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Use Island Obelisk", player), - logic.received("Island Obelisk").simplify()) - MultiWorldRules.set_rule(multi_world.get_entrance("Talk to Traveling Merchant", player), - logic.has_traveling_merchant()) - MultiWorldRules.set_rule(multi_world.get_entrance("Enter Greenhouse", player), - logic.received("Greenhouse")) + set_ginger_island_rules(logic, multi_world, player, world_options) # Those checks do not exist if ToolProgression is vanilla if world_options[options.ToolProgression] != options.ToolProgression.option_vanilla: MultiWorldRules.add_rule(multi_world.get_location("Purchase Fiberglass Rod", player), - (logic.has_skill_level("Fishing", 2) & logic.can_spend_money(1800)).simplify()) + (logic.has_skill_level(Skill.fishing, 2) & logic.can_spend_money(1800)).simplify()) MultiWorldRules.add_rule(multi_world.get_location("Purchase Iridium Rod", player), - (logic.has_skill_level("Fishing", 6) & logic.can_spend_money(7500)).simplify()) + (logic.has_skill_level(Skill.fishing, 6) & logic.can_spend_money(7500)).simplify()) materials = [None, "Copper", "Iron", "Gold", "Iridium"] - tool = ["Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can"] + tool = [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.watering_can, Tool.trash_can] for (previous, material), tool in itertools.product(zip(materials[:4], materials[1:]), tool): if previous is None: MultiWorldRules.add_rule(multi_world.get_location(f"{material} {tool} Upgrade", player), (logic.has(f"{material} Ore") & - logic.can_spend_money(tool_prices[material])).simplify()) + logic.can_spend_money(tool_upgrade_prices[material])).simplify()) else: MultiWorldRules.add_rule(multi_world.get_location(f"{material} {tool} Upgrade", player), (logic.has(f"{material} Ore") & logic.has_tool(tool, previous) & - logic.can_spend_money(tool_prices[material])).simplify()) + logic.can_spend_money(tool_upgrade_prices[material])).simplify()) - # Skills - if world_options[options.SkillProgression] != options.SkillProgression.option_vanilla: - for i in range(1, 11): - MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Farming", player), - (logic.received("Month End", month_end_per_skill_level["Farming", i])).simplify()) - MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Fishing", player), - (logic.can_get_fishing_xp() & - logic.received("Month End", month_end_per_skill_level["Fishing", i])).simplify()) - MultiWorldRules.add_rule(multi_world.get_location(f"Level {i} Foraging", player), - logic.received("Month End", month_end_per_skill_level["Foraging", i]).simplify()) - if i >= 6: - MultiWorldRules.add_rule(multi_world.get_location(f"Level {i} Foraging", player), - logic.has_tool("Axe", "Iron").simplify()) - MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Mining", player), - logic.received("Month End", month_end_per_skill_level["Mining", i]).simplify()) - MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Combat", player), - (logic.received("Month End", month_end_per_skill_level["Combat", i]) & - logic.has_any_weapon()).simplify()) + set_skills_rules(logic, multi_world, player, world_options) # Bundles for bundle in current_bundles.values(): @@ -114,46 +88,294 @@ def set_rules(multi_world: MultiWorld, player: int, world_options: StardewOption # Buildings if world_options[options.BuildingProgression] != options.BuildingProgression.option_vanilla: for building in locations.locations_by_tag[LocationTags.BUILDING_BLUEPRINT]: + if building.mod_name is not None and building.mod_name not in world_options[options.Mods]: + continue MultiWorldRules.set_rule(multi_world.get_location(building.name, player), logic.building_rules[building.name.replace(" Blueprint", "")].simplify()) - # Story Quests - for quest in locations.locations_by_tag[LocationTags.QUEST]: - MultiWorldRules.set_rule(multi_world.get_location(quest.name, player), - logic.quest_rules[quest.name].simplify()) - - # Help Wanted Quests - desired_number_help_wanted: int = world_options[options.HelpWantedLocations] // 7 - for i in range(0, desired_number_help_wanted): - prefix = "Help Wanted:" - delivery = "Item Delivery" - rule = logic.received("Month End", i) - fishing_rule = rule & logic.can_fish() - slay_rule = rule & logic.has_any_weapon() - item_delivery_index = (i * 4) + 1 - for j in range(item_delivery_index, item_delivery_index + 4): - location_name = f"{prefix} {delivery} {j}" - MultiWorldRules.set_rule(multi_world.get_location(location_name, player), rule.simplify()) - - MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Gathering {i+1}", player), - rule.simplify()) - MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Fishing {i+1}", player), - fishing_rule.simplify()) - MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Slay Monsters {i+1}", player), - slay_rule.simplify()) - + set_cropsanity_rules(all_location_names, logic, multi_world, player, world_options) + set_story_quests_rules(all_location_names, logic, multi_world, player, world_options) + set_special_order_rules(all_location_names, logic, multi_world, player, world_options) + set_help_wanted_quests_rules(logic, multi_world, player, world_options) set_fishsanity_rules(all_location_names, logic, multi_world, player) set_museumsanity_rules(all_location_names, logic, multi_world, player, world_options) set_friendsanity_rules(all_location_names, logic, multi_world, player) set_backpack_rules(logic, multi_world, player, world_options) + set_festival_rules(all_location_names, logic, multi_world, player) MultiWorldRules.add_rule(multi_world.get_location("Old Master Cannoli", player), logic.has("Sweet Gem Berry").simplify()) MultiWorldRules.add_rule(multi_world.get_location("Galaxy Sword Shrine", player), logic.has("Prismatic Shard").simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Have a Baby", player), + logic.can_reproduce(1).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Have Another Baby", player), + logic.can_reproduce(2).simplify()) set_traveling_merchant_rules(logic, multi_world, player) set_arcade_machine_rules(logic, multi_world, player, world_options) + set_deepwoods_rules(logic, multi_world, player, world_options) + set_magic_spell_rules(logic, multi_world, player, world_options) + + +def set_skills_rules(logic, multi_world, player, world_options): + # Skills + if world_options[options.SkillProgression] != options.SkillProgression.option_vanilla: + for i in range(1, 11): + set_skill_rule(logic, multi_world, player, Skill.farming, i) + set_skill_rule(logic, multi_world, player, Skill.fishing, i) + set_skill_rule(logic, multi_world, player, Skill.foraging, i) + set_skill_rule(logic, multi_world, player, Skill.mining, i) + set_skill_rule(logic, multi_world, player, Skill.combat, i) + + # Modded Skills + if ModNames.luck_skill in world_options[options.Mods]: + set_skill_rule(logic, multi_world, player, ModSkill.luck, i) + if ModNames.magic in world_options[options.Mods]: + set_skill_rule(logic, multi_world, player, ModSkill.magic, i) + if ModNames.binning_skill in world_options[options.Mods]: + set_skill_rule(logic, multi_world, player, ModSkill.binning, i) + if ModNames.cooking_skill in world_options[options.Mods]: + set_skill_rule(logic, multi_world, player, ModSkill.cooking, i) + if ModNames.socializing_skill in world_options[options.Mods]: + set_skill_rule(logic, multi_world, player, ModSkill.socializing, i) + if ModNames.archaeology in world_options[options.Mods]: + set_skill_rule(logic, multi_world, player, ModSkill.archaeology, i) + + +def set_skill_rule(logic, multi_world, player, skill: str, level: int): + location_name = f"Level {level} {skill}" + location = multi_world.get_location(location_name, player) + rule = logic.can_earn_skill_level(skill, level).simplify() + MultiWorldRules.set_rule(location, rule) + + +def set_entrance_rules(logic, multi_world, player, world_options: StardewOptions): + for floor in range(5, 120 + 5, 5): + MultiWorldRules.set_rule(multi_world.get_entrance(dig_to_mines_floor(floor), player), + logic.can_mine_to_floor(floor).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_tide_pools, player), + logic.received("Beach Bridge") | (magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_quarry, player), + logic.received("Bridge Repair") | (magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_secret_woods, player), + logic.has_tool(Tool.axe, "Iron") | (magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.forest_to_sewer, player), + logic.has_rusty_key().simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.town_to_sewer, player), + logic.has_rusty_key().simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.take_bus_to_desert, player), + logic.received("Bus Repair").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_skull_cavern, player), + logic.received(Wallet.skull_key).simplify()) + for floor in range(25, 200 + 25, 25): + MultiWorldRules.set_rule(multi_world.get_entrance(dig_to_skull_floor(floor), player), + logic.can_mine_to_skull_cavern_floor(floor).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.talk_to_mines_dwarf, player), + logic.can_speak_dwarf() & logic.has_tool(Tool.pickaxe, ToolMaterial.iron)) + + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_desert_obelisk, player), + logic.received(Transportation.desert_obelisk).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_island_obelisk, player), + logic.received(Transportation.island_obelisk).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_farm_obelisk, player), + logic.received(Transportation.farm_obelisk).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.buy_from_traveling_merchant, player), + logic.has_traveling_merchant()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_greenhouse, player), + logic.received("Greenhouse")) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_adventurer_guild, player), + logic.received("Adventurer's Guild")) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_railroad, player), + logic.has_lived_months(2)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_witch_warp_cave, player), + logic.received(Wallet.dark_talisman) | (magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_witch_hut, player), + (logic.has(ArtisanGood.void_mayonnaise) | magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_mutant_bug_lair, player), + ((logic.has_rusty_key() & logic.can_reach_region(Region.railroad) & + logic.can_meet(NPC.krobus) | magic.can_blink(logic)).simplify())) + + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_harvey_room, player), + logic.has_relationship(NPC.harvey, 2)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_maru_room, player), + logic.has_relationship(NPC.maru, 2)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_sebastian_room, player), + (logic.has_relationship(NPC.sebastian, 2) | magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.forest_to_leah_cottage, player), + logic.has_relationship(NPC.leah, 2)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_elliott_house, player), + logic.has_relationship(NPC.elliott, 2)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_sunroom, player), + logic.has_relationship(NPC.caroline, 2)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.enter_wizard_basement, player), + logic.has_relationship(NPC.wizard, 4)) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.mountain_to_leo_treehouse, player), + logic.has_relationship(NPC.leo, 6) & logic.can_reach_region(Region.island_south)) + if ModNames.alec in world_options[options.Mods]: + MultiWorldRules.set_rule(multi_world.get_entrance(AlecEntrance.petshop_to_bedroom, player), + (logic.has_relationship(ModNPC.alec, 2) | magic.can_blink(logic)).simplify()) + + +def set_ginger_island_rules(logic: StardewLogic, multi_world, player, world_options: StardewOptions): + set_island_entrances_rules(logic, multi_world, player) + if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + return + + set_boat_repair_rules(logic, multi_world, player) + set_island_parrot_rules(logic, multi_world, player) + MultiWorldRules.add_rule(multi_world.get_location("Open Professor Snail Cave", player), + logic.has(Craftable.cherry_bomb).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Complete Island Field Office", player), + logic.can_complete_field_office().simplify()) + + +def set_boat_repair_rules(logic: StardewLogic, multi_world, player): + MultiWorldRules.add_rule(multi_world.get_location("Repair Boat Hull", player), + logic.has(Material.hardwood).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Repair Boat Anchor", player), + logic.has(MetalBar.iridium).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Repair Ticket Machine", player), + logic.has(ArtisanGood.battery_pack).simplify()) + + +def set_island_entrances_rules(logic: StardewLogic, multi_world, player): + boat_repaired = logic.received(Transportation.boat_repair).simplify() + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.fish_shop_to_boat_tunnel, player), + boat_repaired) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.boat_to_ginger_island, player), + boat_repaired) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_south_to_west, player), + logic.received("Island West Turtle").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_south_to_north, player), + logic.received("Island North Turtle").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_west_to_islandfarmhouse, player), + logic.received("Island Farmhouse").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_west_to_gourmand_cave, player), + logic.received("Island Farmhouse").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_north_to_dig_site, player), + logic.received("Dig Site Bridge").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.dig_site_to_professor_snail_cave, player), + logic.received("Open Professor Snail Cave").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.talk_to_island_trader, player), + logic.received("Island Trader").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_south_to_southeast, player), + logic.received("Island Resort").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.use_island_resort, player), + logic.received("Island Resort").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_west_to_qi_walnut_room, player), + logic.received("Qi Walnut Room").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.island_north_to_volcano, player), + (logic.can_water(0) | logic.received("Volcano Bridge") | + magic.can_blink(logic)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.volcano_to_secret_beach, player), + logic.can_water(2).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.climb_to_volcano_5, player), + (logic.can_mine_perfectly() & logic.can_water(1)).simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.talk_to_volcano_dwarf, player), + logic.can_speak_dwarf()) + MultiWorldRules.set_rule(multi_world.get_entrance(Entrance.climb_to_volcano_10, player), + (logic.can_mine_perfectly() & logic.can_water(1) & logic.received("Volcano Exit Shortcut")).simplify()) + parrots = [Entrance.parrot_express_docks_to_volcano, Entrance.parrot_express_jungle_to_volcano, + Entrance.parrot_express_dig_site_to_volcano, Entrance.parrot_express_docks_to_dig_site, + Entrance.parrot_express_jungle_to_dig_site, Entrance.parrot_express_volcano_to_dig_site, + Entrance.parrot_express_docks_to_jungle, Entrance.parrot_express_dig_site_to_jungle, + Entrance.parrot_express_volcano_to_jungle, Entrance.parrot_express_jungle_to_docks, + Entrance.parrot_express_dig_site_to_docks, Entrance.parrot_express_volcano_to_docks] + for parrot in parrots: + MultiWorldRules.set_rule(multi_world.get_entrance(parrot, player), logic.received(Transportation.parrot_express).simplify()) + + +def set_island_parrot_rules(logic: StardewLogic, multi_world, player): + has_walnut = logic.has_walnut(1).simplify() + has_5_walnut = logic.has_walnut(5).simplify() + has_10_walnut = logic.has_walnut(10).simplify() + has_20_walnut = logic.has_walnut(20).simplify() + MultiWorldRules.add_rule(multi_world.get_location("Leo's Parrot", player), + has_walnut) + MultiWorldRules.add_rule(multi_world.get_location("Island West Turtle", player), + has_10_walnut & logic.received("Island North Turtle")) + MultiWorldRules.add_rule(multi_world.get_location("Island Farmhouse", player), + has_20_walnut) + MultiWorldRules.add_rule(multi_world.get_location("Island Mailbox", player), + has_5_walnut & logic.received("Island Farmhouse")) + MultiWorldRules.add_rule(multi_world.get_location(Transportation.farm_obelisk, player), + has_20_walnut & logic.received("Island Mailbox")) + MultiWorldRules.add_rule(multi_world.get_location("Dig Site Bridge", player), + has_10_walnut & logic.received("Island West Turtle")) + MultiWorldRules.add_rule(multi_world.get_location("Island Trader", player), + has_10_walnut & logic.received("Island Farmhouse")) + MultiWorldRules.add_rule(multi_world.get_location("Volcano Bridge", player), + has_5_walnut & logic.received("Island West Turtle") & + logic.can_reach_region(Region.volcano_floor_10)) + MultiWorldRules.add_rule(multi_world.get_location("Volcano Exit Shortcut", player), + has_5_walnut & logic.received("Island West Turtle")) + MultiWorldRules.add_rule(multi_world.get_location("Island Resort", player), + has_20_walnut & logic.received("Island Farmhouse")) + MultiWorldRules.add_rule(multi_world.get_location(Transportation.parrot_express, player), + has_10_walnut) + + +def set_cropsanity_rules(all_location_names: List[str], logic, multi_world, player, world_options: StardewOptions): + if world_options[options.Cropsanity] == options.Cropsanity.option_disabled: + return + + harvest_prefix = "Harvest " + harvest_prefix_length = len(harvest_prefix) + for harvest_location in locations.locations_by_tag[LocationTags.CROPSANITY]: + if harvest_location.name in all_location_names and (harvest_location.mod_name is None or harvest_location.mod_name in world_options[options.Mods]): + crop_name = harvest_location.name[harvest_prefix_length:] + MultiWorldRules.set_rule(multi_world.get_location(harvest_location.name, player), + logic.has(crop_name).simplify()) + + +def set_story_quests_rules(all_location_names: List[str], logic, multi_world, player, world_options: StardewOptions): + for quest in locations.locations_by_tag[LocationTags.QUEST]: + if quest.name in all_location_names and (quest.mod_name is None or quest.mod_name in world_options[options.Mods]): + MultiWorldRules.set_rule(multi_world.get_location(quest.name, player), + logic.quest_rules[quest.name].simplify()) + + +def set_special_order_rules(all_location_names: List[str], logic: StardewLogic, multi_world, player, + world_options: StardewOptions): + if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_disabled: + return + board_rule = logic.received("Special Order Board") & logic.has_lived_months(4) + for board_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: + if board_order.name in all_location_names: + order_rule = board_rule & logic.special_order_rules[board_order.name] + MultiWorldRules.set_rule(multi_world.get_location(board_order.name, player), order_rule.simplify()) + + if world_options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true: + return + if world_options[options.SpecialOrderLocations] == options.SpecialOrderLocations.option_board_only: + return + qi_rule = logic.can_reach_region(Region.qi_walnut_room) & logic.has_lived_months(8) + for qi_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_QI]: + if qi_order.name in all_location_names: + order_rule = qi_rule & logic.special_order_rules[qi_order.name] + MultiWorldRules.set_rule(multi_world.get_location(qi_order.name, player), order_rule.simplify()) + + +def set_help_wanted_quests_rules(logic: StardewLogic, multi_world, player, world_options): + desired_number_help_wanted: int = world_options[options.HelpWantedLocations] // 7 + for i in range(0, desired_number_help_wanted): + prefix = "Help Wanted:" + delivery = "Item Delivery" + rule = logic.has_lived_months(i).simplify() + fishing_rule = rule & logic.can_fish() + slay_rule = rule & logic.can_do_combat_at_level("Basic") + item_delivery_index = (i * 4) + 1 + for j in range(item_delivery_index, item_delivery_index + 4): + location_name = f"{prefix} {delivery} {j}" + MultiWorldRules.set_rule(multi_world.get_location(location_name, player), rule) + + MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Gathering {i + 1}", player), + rule) + MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Fishing {i + 1}", player), + fishing_rule.simplify()) + MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Slay Monsters {i + 1}", player), + slay_rule.simplify()) def set_fishsanity_rules(all_location_names: List[str], logic: StardewLogic, multi_world: MultiWorld, player: int): @@ -175,7 +397,7 @@ def set_museumsanity_rules(all_location_names: List[str], logic: StardewLogic, m set_museum_individual_donations_rules(all_location_names, logic, multi_world, museum_prefix, player) -def set_museum_individual_donations_rules(all_location_names, logic, multi_world, museum_prefix, player): +def set_museum_individual_donations_rules(all_location_names, logic: StardewLogic, multi_world, museum_prefix, player): all_donations = sorted(locations.locations_by_tag[LocationTags.MUSEUM_DONATIONS], key=lambda x: all_museum_items_by_name[x.name[len(museum_prefix):]].difficulty, reverse=True) counter = 0 @@ -219,7 +441,7 @@ def set_museum_milestone_rule(logic: StardewLogic, multi_world: MultiWorld, muse MultiWorldRules.set_rule(multi_world.get_location(museum_milestone.name, player), rule.simplify()) -def get_museum_item_count_rule(logic, suffix, milestone_name, accepted_items): +def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items): metal_detector = "Traveling Merchant Metal Detector" num = int(milestone_name[:milestone_name.index(suffix)]) required_detectors = (num - 1) * 5 // len(accepted_items) @@ -233,10 +455,24 @@ def set_backpack_rules(logic: StardewLogic, multi_world: MultiWorld, player: int logic.can_spend_money(2000).simplify()) MultiWorldRules.set_rule(multi_world.get_location("Deluxe Pack", player), (logic.can_spend_money(10000) & logic.received("Progressive Backpack")).simplify()) + if ModNames.big_backpack in world_options[options.Mods]: + MultiWorldRules.set_rule(multi_world.get_location("Premium Pack", player), + (logic.can_spend_money(150000) & + logic.received("Progressive Backpack", 2)).simplify()) + + +def set_festival_rules(all_location_names: List[str], logic: StardewLogic, multi_world, player): + festival_locations = [] + festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL]) + festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL_HARD]) + for festival in festival_locations: + if festival.name in all_location_names: + MultiWorldRules.set_rule(multi_world.get_location(festival.name, player), + logic.festival_rules[festival.name].simplify()) def set_traveling_merchant_rules(logic: StardewLogic, multi_world: MultiWorld, player: int): - for day in week_days: + for day in Weekday.all_days: item_for_day = f"Traveling Merchant: {day}" for i in range(1, 4): location_name = f"Traveling Merchant {day} Item {i}" @@ -245,23 +481,27 @@ def set_traveling_merchant_rules(logic: StardewLogic, multi_world: MultiWorld, p def set_arcade_machine_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options): - if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling: - MultiWorldRules.add_rule(multi_world.get_entrance("Play Junimo Kart", player), - (logic.received("Skull Key") & logic.has("Junimo Kart Small Buff")).simplify()) - MultiWorldRules.add_rule(multi_world.get_entrance("Reach Junimo Kart 2", player), - logic.has("Junimo Kart Medium Buff").simplify()) - MultiWorldRules.add_rule(multi_world.get_entrance("Reach Junimo Kart 3", player), - logic.has("Junimo Kart Big Buff").simplify()) - MultiWorldRules.add_rule(multi_world.get_location("Junimo Kart: Sunset Speedway (Victory)", player), - logic.has("Junimo Kart Max Buff").simplify()) - MultiWorldRules.add_rule(multi_world.get_entrance("Play Journey of the Prairie King", player), - logic.has("JotPK Small Buff").simplify()) - MultiWorldRules.add_rule(multi_world.get_entrance("Reach JotPK World 2", player), - logic.has("JotPK Medium Buff").simplify()) - MultiWorldRules.add_rule(multi_world.get_entrance("Reach JotPK World 3", player), - logic.has("JotPK Big Buff").simplify()) - MultiWorldRules.add_rule(multi_world.get_location("Journey of the Prairie King Victory", player), - logic.has("JotPK Max Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.play_junimo_kart, player), + logic.received(Wallet.skull_key).simplify()) + if world_options[options.ArcadeMachineLocations] != options.ArcadeMachineLocations.option_full_shuffling: + return + + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.play_junimo_kart, player), + logic.has("Junimo Kart Small Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_junimo_kart_2, player), + logic.has("Junimo Kart Medium Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_junimo_kart_3, player), + logic.has("Junimo Kart Big Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Junimo Kart: Sunset Speedway (Victory)", player), + logic.has("Junimo Kart Max Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.play_journey_of_the_prairie_king, player), + logic.has("JotPK Small Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_jotpk_world_2, player), + logic.has("JotPK Medium Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_entrance(Entrance.reach_jotpk_world_3, player), + logic.has("JotPK Big Buff").simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Journey of the Prairie King Victory", player), + logic.has("JotPK Max Buff").simplify()) def set_friendsanity_rules(all_location_names: List[str], logic: StardewLogic, multi_world: MultiWorld, player: int): @@ -272,8 +512,93 @@ def set_friendsanity_rules(all_location_names: List[str], logic: StardewLogic, m continue friend_location_without_prefix = friend_location.name[len(friend_prefix):] friend_location_trimmed = friend_location_without_prefix[:friend_location_without_prefix.index(friend_suffix)] - parts = friend_location_trimmed.split(" ") - friend_name = parts[0] - num_hearts = int(parts[1]) + split_index = friend_location_trimmed.rindex(" ") + friend_name = friend_location_trimmed[:split_index] + num_hearts = int(friend_location_trimmed[split_index + 1:]) MultiWorldRules.set_rule(multi_world.get_location(friend_location.name, player), logic.can_earn_relationship(friend_name, num_hearts).simplify()) + + +def set_deepwoods_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options: StardewOptions): + if ModNames.deepwoods in world_options[options.Mods]: + MultiWorldRules.add_rule(multi_world.get_location("Breaking Up Deep Woods Gingerbread House", player), + logic.has_tool(Tool.axe, "Gold") & deepwoods.can_reach_woods_depth(logic, 50).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Chop Down a Deep Woods Iridium Tree", player), + logic.has_tool(Tool.axe, "Iridium").simplify()) + MultiWorldRules.set_rule(multi_world.get_entrance(DeepWoodsEntrance.use_woods_obelisk, player), + logic.received("Woods Obelisk").simplify()) + for depth in range(10, 100 + 10, 10): + MultiWorldRules.set_rule(multi_world.get_entrance(move_to_woods_depth(depth), player), + deepwoods.can_chop_to_depth(logic, depth).simplify()) + + +def set_magic_spell_rules(logic: StardewLogic, multi_world: MultiWorld, player: int, world_options: StardewOptions): + if ModNames.magic not in world_options[options.Mods]: + return + + MultiWorldRules.set_rule(multi_world.get_entrance(MagicEntrance.store_to_altar, player), + (logic.has_relationship(NPC.wizard, 3) & + logic.can_reach_region(Region.wizard_tower)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Clear Debris", player), + ((logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic")) + & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Till", player), + (logic.has_tool("Hoe", "Basic") & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Water", player), + (logic.has_tool("Watering Can", "Basic") & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze All Toil School Locations", player), + (logic.has_tool("Watering Can", "Basic") & logic.has_tool("Hoe", "Basic") + & (logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic")) + & magic.can_use_altar(logic)).simplify()) + # Do I *want* to add boots into logic when you get them even in vanilla without effort? idk + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Evac", player), + (logic.can_mine_perfectly() & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Haste", player), + (logic.has("Coffee") & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Heal", player), + (logic.has("Life Elixir") & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze All Life School Locations", player), + (logic.has("Coffee") & logic.has("Life Elixir") + & logic.can_mine_perfectly() & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Descend", player), + (logic.can_reach_region(Region.mines) & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Fireball", player), + (logic.has("Fire Quartz") & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Frostbite", player), + (logic.can_mine_to_floor(70) & logic.can_fish(85) & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze All Elemental School Locations", player), + (logic.can_reach_region(Region.mines) & logic.has("Fire Quartz") + & logic.can_reach_region(Region.mines_floor_70) & logic.can_fish(85) & + magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Lantern", player), + magic.can_use_altar(logic).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Tendrils", player), + (logic.can_reach_region(Region.farm) & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Shockwave", player), + (logic.has("Earth Crystal") & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze All Nature School Locations", player), + (logic.has("Earth Crystal") & logic.can_reach_region("Farm") & + magic.can_use_altar(logic)).simplify()), + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Meteor", player), + (logic.can_reach_region(Region.farm) & logic.has_lived_months(12) + & magic.can_use_altar(logic)).simplify()), + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Lucksteal", player), + (logic.can_reach_region(Region.witch_hut) & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze: Bloodmana", player), + (logic.can_reach_region(Region.mines_floor_100) & magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze All Eldritch School Locations", player), + (logic.can_reach_region(Region.witch_hut) & + logic.can_reach_region(Region.mines_floor_100) & + logic.can_reach_region(Region.farm) & logic.has_lived_months(12) & + magic.can_use_altar(logic)).simplify()) + MultiWorldRules.add_rule(multi_world.get_location("Analyze Every Magic School Location", player), + (logic.has_tool("Watering Can", "Basic") & logic.has_tool("Hoe", "Basic") + & (logic.has_tool("Axe", "Basic") | logic.has_tool("Pickaxe", "Basic")) & + logic.has("Coffee") & logic.has("Life Elixir") + & logic.can_mine_perfectly() & logic.has("Earth Crystal") & + logic.can_reach_region(Region.mines) & + logic.has("Fire Quartz") & logic.can_fish(85) & + logic.can_reach_region(Region.witch_hut) & + logic.can_reach_region(Region.mines_floor_100) & + logic.can_reach_region(Region.farm) & logic.has_lived_months(12) & + magic.can_use_altar(logic)).simplify()) diff --git a/worlds/stardew_valley/scripts/update_data.py b/worlds/stardew_valley/scripts/update_data.py index 4b7b6be2..7b31a370 100644 --- a/worlds/stardew_valley/scripts/update_data.py +++ b/worlds/stardew_valley/scripts/update_data.py @@ -11,7 +11,7 @@ from pathlib import Path from typing import List from worlds.stardew_valley import LocationData -from worlds.stardew_valley.items import load_item_csv, Group, ItemData, load_resource_pack_csv, friendship_pack +from worlds.stardew_valley.items import load_item_csv, Group, ItemData from worlds.stardew_valley.locations import load_location_csv RESOURCE_PACK_CODE_OFFSET = 5000 @@ -53,22 +53,23 @@ if __name__ == "__main__": for item in loaded_items if Group.RESOURCE_PACK not in item.groups and item.code_without_offset is not None) + 1) + + resource_pack_counter = itertools.count(max(item.code_without_offset + for item in loaded_items + if Group.RESOURCE_PACK in item.groups + and item.code_without_offset is not None) + 1) items_to_write = [] for item in loaded_items: - if item.has_any_group(Group.RESOURCE_PACK, Group.FRIENDSHIP_PACK): - continue - if item.code_without_offset is None: - items_to_write.append(ItemData(next(item_counter), item.name, item.classification, item.groups)) + if Group.RESOURCE_PACK in item.groups: + new_code = next(resource_pack_counter) + else: + new_code = next(item_counter) + items_to_write.append(ItemData(new_code, item.name, item.classification, item.groups)) continue items_to_write.append(item) - all_resource_packs = load_resource_pack_csv() + [friendship_pack] - resource_pack_counter = itertools.count(RESOURCE_PACK_CODE_OFFSET) - items_to_write.extend( - item for resource_pack in all_resource_packs for item in resource_pack.as_item_data(resource_pack_counter)) - write_item_csv(items_to_write) loaded_locations = load_location_csv() diff --git a/worlds/stardew_valley/stardew_rule.py b/worlds/stardew_valley/stardew_rule.py index 7f568f9f..d0fa9858 100644 --- a/worlds/stardew_valley/stardew_rule.py +++ b/worlds/stardew_valley/stardew_rule.py @@ -159,7 +159,8 @@ class And(StardewRule): if rules is not None: rules_list.update(rules) - assert rules_list, "Can't create a And conditions without rules" + if len(rules_list) < 1: + rules_list.add(True_()) new_rules = set() for rule in rules_list: @@ -297,7 +298,7 @@ class Received(StardewRule): def __post_init__(self): assert item_table[self.item].classification & ItemClassification.progression, \ - "Item has to be progression to be used in logic" + f"Item [{item_table[self.item].name}] has to be progression to be used in logic" def __call__(self, state: CollectionState) -> bool: return state.has(self.item, self.player, self.count) diff --git a/worlds/stardew_valley/strings/__init__.py b/worlds/stardew_valley/strings/__init__.py new file mode 100644 index 00000000..10b89497 --- /dev/null +++ b/worlds/stardew_valley/strings/__init__.py @@ -0,0 +1,25 @@ +# Attempt at making this better. Not sure yet +module_names = [ + "animal_names", + "animal_product_names", + "building_names", + "crop_names", + "forageable_names", + "fruit_tree_names", + "generic_names", + "geode_names", + "ingredient_names", + "item_names", + "machine_names", + "meal_names", + "metal_names", + "performance_names", + "quest_names", + "region_names", + "Material", + "season_names", + "skill_names", + "tool_names", + "tv_channel_names", + "villager_names", +] \ No newline at end of file diff --git a/worlds/stardew_valley/strings/animal_names.py b/worlds/stardew_valley/strings/animal_names.py new file mode 100644 index 00000000..ecae0d76 --- /dev/null +++ b/worlds/stardew_valley/strings/animal_names.py @@ -0,0 +1,13 @@ +class Animal: + chicken = "Chicken" + cow = "Cow" + pig = "Pig" + duck = "Duck" + sheep = "Sheep" + dinosaur = "Dinosaur" + rabbit = "Rabbit" + goat = "Goat" + ostrich = "Ostrich" + +coop_animals = [Animal.chicken, "Rabbit", "Duck", "Dinosaur"] +barn_animals = [Animal.cow, "Sheep", "Pig", "Ostrich"] \ No newline at end of file diff --git a/worlds/stardew_valley/strings/animal_product_names.py b/worlds/stardew_valley/strings/animal_product_names.py new file mode 100644 index 00000000..6656e70e --- /dev/null +++ b/worlds/stardew_valley/strings/animal_product_names.py @@ -0,0 +1,24 @@ +class AnimalProduct: + any_egg = "Any Egg" + chicken_egg = "Chicken Egg" + egg = "Egg" + brown_egg = "Egg (Brown)" + large_egg = "Large Egg" + large_brown_egg = "Large Egg (Brown)" + milk = "Milk" + large_milk = "Large Milk" + cow_milk = "Cow Milk" + wool = "Wool" + goat_milk = "Goat Milk" + large_goat_milk = "Large Goat Milk" + duck_egg = "Duck Egg" + duck_feather = "Duck Feather" + void_egg = "Void Egg" + truffle = "Truffle" + rabbit_foot = "Rabbit's Foot" + roe = "Roe" + sturgeon_roe = "Sturgeon Roe" + ostrich_egg = "Ostrich Egg" + dinosaur_egg = "Dinosaur Egg" + squid_ink = "Squid Ink" + diff --git a/worlds/stardew_valley/strings/ap_names/__init__.py b/worlds/stardew_valley/strings/ap_names/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/stardew_valley/strings/ap_names/buff_names.py b/worlds/stardew_valley/strings/ap_names/buff_names.py new file mode 100644 index 00000000..4ddd6fb5 --- /dev/null +++ b/worlds/stardew_valley/strings/ap_names/buff_names.py @@ -0,0 +1,3 @@ +class Buff: + movement = "Movement Speed Bonus" + luck = "Luck Bonus" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/ap_names/skill_level_names.py b/worlds/stardew_valley/strings/ap_names/skill_level_names.py new file mode 100644 index 00000000..d957262d --- /dev/null +++ b/worlds/stardew_valley/strings/ap_names/skill_level_names.py @@ -0,0 +1,2 @@ +class ModSkillLevel: + magic_level = "Magic Level" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/ap_names/transport_names.py b/worlds/stardew_valley/strings/ap_names/transport_names.py new file mode 100644 index 00000000..7617a238 --- /dev/null +++ b/worlds/stardew_valley/strings/ap_names/transport_names.py @@ -0,0 +1,10 @@ +class Transportation: + boat_repair = "Boat Repair" + island_obelisk = "Island Obelisk" + desert_obelisk = "Desert Obelisk" + farm_obelisk = "Farm Obelisk" + parrot_express = "Parrot Express" + + +class ModTransportation: + woods_obelisk = "Woods Obelisk" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/artisan_good_names.py b/worlds/stardew_valley/strings/artisan_good_names.py new file mode 100644 index 00000000..469644d9 --- /dev/null +++ b/worlds/stardew_valley/strings/artisan_good_names.py @@ -0,0 +1,23 @@ +class ArtisanGood: + honey = "Honey" + oak_resin = "Oak Resin" + pine_tar = "Pine Tar" + maple_syrup = "Maple Syrup" + truffle_oil = "Truffle Oil" + cheese = "Cheese" + goat_cheese = "Goat Cheese" + jelly = "Jelly" + pickles = "Pickles" + wine = "Wine" + juice = "Juice" + cloth = "Cloth" + pale_ale = "Pale Ale" + aged_roe = "Aged Roe" + battery_pack = "Battery Pack" + mayonnaise = "Mayonnaise" + duck_mayonnaise = "Duck Mayonnaise" + dinosaur_mayonnaise = "Dinosaur Mayonnaise" + void_mayonnaise = "Void Mayonnaise" + caviar = "Caviar" + green_tea = "Green Tea" + mead = "Mead" diff --git a/worlds/stardew_valley/strings/building_names.py b/worlds/stardew_valley/strings/building_names.py new file mode 100644 index 00000000..b67808fe --- /dev/null +++ b/worlds/stardew_valley/strings/building_names.py @@ -0,0 +1,23 @@ +class Building: + barn = "Barn" + big_barn = "Big Barn" + deluxe_barn = "Deluxe Barn" + coop = "Coop" + big_coop = "Big Coop" + deluxe_coop = "Deluxe Coop" + fish_pond = "Fish Pond" + mill = "Mill" + shed = "Shed" + big_shed = "Big Shed" + silo = "Silo" + slime_hutch = "Slime Hutch" + stable = "Stable" + well = "Well" + shipping_bin = "Shipping Bin" + kitchen = "Kitchen" + kids_room = "Kids Room" + cellar = "Cellar" + + +class ModBuilding: + tractor_garage = "Tractor Garage" diff --git a/worlds/stardew_valley/strings/calendar_names.py b/worlds/stardew_valley/strings/calendar_names.py new file mode 100644 index 00000000..da04ea92 --- /dev/null +++ b/worlds/stardew_valley/strings/calendar_names.py @@ -0,0 +1,11 @@ +class Weekday: + sunday = "Sunday" + monday = "Monday" + tuesday = "Tuesday" + wednesday = "Wednesday" + thursday = "Thursday" + friday = "Friday" + saturday = "Saturday" + all_days = [sunday, monday, tuesday, wednesday, thursday, friday, saturday] + + diff --git a/worlds/stardew_valley/strings/craftable_names.py b/worlds/stardew_valley/strings/craftable_names.py new file mode 100644 index 00000000..a1ee15b1 --- /dev/null +++ b/worlds/stardew_valley/strings/craftable_names.py @@ -0,0 +1,16 @@ +class Craftable: + bait = "Bait" + cherry_bomb = "Cherry Bomb" + bomb = "Bomb" + mega_bomb = "Mega Bomb" + staircase = "Staircase" + scarecrow = "Scarecrow" + rain_totem = "Rain Totem" + flute_block = "Flute Block" + life_elixir = "Life Elixir" + monster_musk = "Monster Musk" + oil_of_garlic = "Oil of Garlic" + + + + diff --git a/worlds/stardew_valley/strings/crop_names.py b/worlds/stardew_valley/strings/crop_names.py new file mode 100644 index 00000000..2b5ea4d3 --- /dev/null +++ b/worlds/stardew_valley/strings/crop_names.py @@ -0,0 +1,59 @@ +all_fruits = [] +all_vegetables = [] + + +def veggie(name: str) -> str: + all_vegetables.append(name) + return name + + +def fruity(name: str) -> str: + all_fruits.append(name) + return name + + +class Fruit: + any = "Any Fruit" + blueberry = fruity("Blueberry") + melon = fruity("Melon") + apple = fruity("Apple") + apricot = fruity("Apricot") + cherry = fruity("Cherry") + orange = fruity("Orange") + peach = fruity("Peach") + pomegranate = fruity("Pomegranate") + banana = fruity("Banana") + mango = fruity("Mango") + pineapple = fruity("Pineapple") + ancient_fruit = fruity("Ancient Fruit") + strawberry = fruity("Strawberry") + starfruit = fruity("Starfruit") + rhubarb = fruity("Rhubarb") + grape = fruity("Grape") + cranberries = fruity("Cranberries") + hot_pepper = fruity("Hot Pepper") + + +class Vegetable: + any = "Any Vegetable" + parsnip = veggie("Parsnip") + garlic = veggie("Garlic") + wheat = "Wheat" + potato = veggie("Potato") + corn = veggie("Corn") + tomato = veggie("Tomato") + pumpkin = veggie("Pumpkin") + unmilled_rice = veggie("Unmilled Rice") + beet = veggie("Beet") + hops = "Hops" + cauliflower = veggie("Cauliflower") + amaranth = veggie("Amaranth") + kale = veggie("Kale") + artichoke = veggie("Artichoke") + tea_leaves = "Tea Leaves" + eggplant = veggie("Eggplant") + green_bean = veggie("Green Bean") + red_cabbage = veggie("Red Cabbage") + yam = veggie("Yam") + radish = veggie("Radish") + taro_root = veggie("Taro Root") diff --git a/worlds/stardew_valley/strings/entrance_names.py b/worlds/stardew_valley/strings/entrance_names.py new file mode 100644 index 00000000..e744400c --- /dev/null +++ b/worlds/stardew_valley/strings/entrance_names.py @@ -0,0 +1,217 @@ +def dig_to_mines_floor(floor: int) -> str: + return f"Dig to The Mines - Floor {floor}" + + +def dig_to_skull_floor(floor: int) -> str: + return f"Mine to Skull Cavern Floor {floor}" + + +def move_to_woods_depth(depth: int) -> str: + return f"Enter Deep Woods Depth {depth}" + + +class Entrance: + to_stardew_valley = "To Stardew Valley" + to_farmhouse = "To Farmhouse" + farmhouse_to_farm = "Farmhouse to Farm" + downstairs_to_cellar = "Farmhouse to Cellar" + farm_to_backwoods = "Farm to Backwoods" + farm_to_bus_stop = "Farm to Bus Stop" + bus_stop_to_tunnel_entrance = "Bus Stop to Tunnel Entrance" + tunnel_entrance_to_bus_tunnel = "Tunnel Entrance to Bus Tunnel" + farm_to_forest = "Farm to Forest" + farm_to_farmcave = "Farm to Farmcave" + enter_greenhouse = "Farm to Greenhouse" + use_desert_obelisk = "Use Desert Obelisk" + use_island_obelisk = "Use Island Obelisk" + use_farm_obelisk = "Use Farm Obelisk" + backwoods_to_mountain = "Backwoods to Mountain" + bus_stop_to_town = "Bus Stop to Town" + take_bus_to_desert = "Bus Stop to Desert" + forest_to_town = "Forest to Town" + enter_secret_woods = "Forest to Secret Woods" + forest_to_wizard_tower = "Forest to Wizard Tower" + forest_to_marnie_ranch = "Forest to Marnie's Ranch" + forest_to_leah_cottage = "Forest to Leah's Cottage" + forest_to_sewer = "Forest to Sewer" + buy_from_traveling_merchant = "Buy from Traveling Merchant" + mountain_to_railroad = "Mountain to Railroad" + mountain_to_tent = "Mountain to Tent" + mountain_to_carpenter_shop = "Mountain to Carpenter Shop" + mountain_to_maru_room = "Mountain to Maru's Room" + mountain_to_the_mines = "Mountain to The Mines" + enter_quarry = "Mountain to Quarry" + mountain_to_adventurer_guild = "Mountain to Adventurer's Guild" + mountain_to_town = "Mountain to Town" + town_to_community_center = "Town to Community Center" + access_crafts_room = "Access Crafts Room" + access_pantry = "Access Pantry" + access_fish_tank = "Access Fish Tank" + access_boiler_room = "Access Boiler Room" + access_bulletin_board = "Access Bulletin Board" + access_vault = "Access Vault" + town_to_beach = "Town to Beach" + town_to_hospital = "Town to Hospital" + town_to_pierre_general_store = "Town to Pierre's General Store" + town_to_saloon = "Town to Saloon" + town_to_alex_house = "Town to Alex's House" + town_to_trailer = "Town to Trailer" + town_to_mayor_manor = "Town to Mayor's Manor" + town_to_sam_house = "Town to Sam's House" + town_to_haley_house = "Town to Haley's House" + town_to_sewer = "Town to Sewer" + town_to_clint_blacksmith = "Town to Clint's Blacksmith" + town_to_museum = "Town to Museum" + town_to_jojamart = "Town to JojaMart" + beach_to_willy_fish_shop = "Beach to Willy's Fish Shop" + fish_shop_to_boat_tunnel = "Fish Shop to Boat Tunnel" + boat_to_ginger_island = "Take the Boat to Ginger Island" + enter_elliott_house = "Beach to Elliott's House" + enter_tide_pools = "Beach to Tide Pools" + enter_bathhouse_entrance = "Railroad to Bathhouse Entrance" + enter_witch_warp_cave = "Railroad to Witch Warp Cave" + enter_perfection_cutscene_area = "Railroad to Perfection Cutscene Area" + enter_sebastian_room = "Carpenter Shop to Sebastian's Room" + enter_harvey_room = "Hospital to Harvey's Room" + enter_sunroom = "Pierre's General Store to Sunroom" + enter_mutant_bug_lair = "Sewer to Mutant Bug Lair" + enter_wizard_basement = "Wizard Tower to Wizard Basement" + play_journey_of_the_prairie_king = "Play Journey of the Prairie King" + reach_jotpk_world_2 = "Reach JotPK World 2" + reach_jotpk_world_3 = "Reach JotPK World 3" + play_junimo_kart = "Play Junimo Kart" + reach_junimo_kart_2 = "Reach Junimo Kart 2" + reach_junimo_kart_3 = "Reach Junimo Kart 3" + enter_locker_room = "Bathhouse Entrance to Locker Room" + enter_public_bath = "Locker Room to Public Bath" + enter_witch_swamp = "Witch Warp Cave to Witch's Swamp" + enter_witch_hut = "Witch's Swamp to Witch's Hut" + witch_warp_to_wizard_basement = "Witch's Hut to Wizard Basement" + enter_quarry_mine_entrance = "Quarry to Quarry Mine Entrance" + enter_quarry_mine = "Quarry Mine Entrance to Quarry Mine" + enter_oasis = "Desert to Oasis" + enter_casino = "Oasis to Casino" + enter_skull_cavern_entrance = "Desert to Skull Cavern Entrance" + enter_skull_cavern = "Skull Cavern Entrance to Skull Cavern" + mine_to_skull_cavern_floor_25 = dig_to_skull_floor(25) + mine_to_skull_cavern_floor_50 = dig_to_skull_floor(50) + mine_to_skull_cavern_floor_75 = dig_to_skull_floor(75) + mine_to_skull_cavern_floor_100 = dig_to_skull_floor(100) + mine_to_skull_cavern_floor_125 = dig_to_skull_floor(125) + mine_to_skull_cavern_floor_150 = dig_to_skull_floor(150) + mine_to_skull_cavern_floor_175 = dig_to_skull_floor(175) + mine_to_skull_cavern_floor_200 = dig_to_skull_floor(200) + talk_to_mines_dwarf = "Talk to Mines Dwarf" + dig_to_mines_floor_5 = dig_to_mines_floor(5) + dig_to_mines_floor_10 = dig_to_mines_floor(10) + dig_to_mines_floor_15 = dig_to_mines_floor(15) + dig_to_mines_floor_20 = dig_to_mines_floor(20) + dig_to_mines_floor_25 = dig_to_mines_floor(25) + dig_to_mines_floor_30 = dig_to_mines_floor(30) + dig_to_mines_floor_35 = dig_to_mines_floor(35) + dig_to_mines_floor_40 = dig_to_mines_floor(40) + dig_to_mines_floor_45 = dig_to_mines_floor(45) + dig_to_mines_floor_50 = dig_to_mines_floor(50) + dig_to_mines_floor_55 = dig_to_mines_floor(55) + dig_to_mines_floor_60 = dig_to_mines_floor(60) + dig_to_mines_floor_65 = dig_to_mines_floor(65) + dig_to_mines_floor_70 = dig_to_mines_floor(70) + dig_to_mines_floor_75 = dig_to_mines_floor(75) + dig_to_mines_floor_80 = dig_to_mines_floor(80) + dig_to_mines_floor_85 = dig_to_mines_floor(85) + dig_to_mines_floor_90 = dig_to_mines_floor(90) + dig_to_mines_floor_95 = dig_to_mines_floor(95) + dig_to_mines_floor_100 = dig_to_mines_floor(100) + dig_to_mines_floor_105 = dig_to_mines_floor(105) + dig_to_mines_floor_110 = dig_to_mines_floor(110) + dig_to_mines_floor_115 = dig_to_mines_floor(115) + dig_to_mines_floor_120 = dig_to_mines_floor(120) + island_south_to_west = "Island South to West" + island_south_to_north = "Island South to North" + island_south_to_east = "Island South to East" + island_south_to_southeast = "Island South to Southeast" + use_island_resort = "Use Island Resort" + island_west_to_islandfarmhouse = "Island West to Island Farmhouse" + island_west_to_gourmand_cave = "Island West to Gourmand Cave" + island_west_to_crystals_cave = "Island West to Crystal Cave" + island_west_to_shipwreck = "Island West to Shipwreck" + island_west_to_qi_walnut_room = "Island West to Qi Walnut Room" + island_east_to_leo_hut = "Island East to Leo Hut" + mountain_to_leo_treehouse = "Mountain to Leo TreeHouse" + island_east_to_island_shrine = "Island East to Island Shrine" + island_southeast_to_pirate_cove = "Island Southeast to Pirate Cove" + island_north_to_field_office = "Island North to Field Office" + island_north_to_dig_site = "Island North to Dig Site" + dig_site_to_professor_snail_cave = "Dig Site to Professor Snail Cave" + island_north_to_volcano = "Island North to Volcano Entrance" + volcano_to_secret_beach = "Volcano River to Secret Beach" + talk_to_island_trader = "Talk to Island Trader" + climb_to_volcano_5 = "Climb to Volcano Floor 5" + talk_to_volcano_dwarf = "Talk to Volcano Dwarf" + climb_to_volcano_10 = "Climb to Volcano Floor 10" + parrot_express_docks_to_volcano = "Parrot Express Docks to Volcano" + parrot_express_jungle_to_volcano = "Parrot Express Jungle to Volcano" + parrot_express_dig_site_to_volcano = "Parrot Express Dig Site to Volcano" + parrot_express_docks_to_dig_site = "Parrot Express Docks to Dig Site" + parrot_express_jungle_to_dig_site = "Parrot Express Jungle to Dig Site" + parrot_express_volcano_to_dig_site = "Parrot Express Volcano to Dig Site" + parrot_express_docks_to_jungle = "Parrot Express Docks to Jungle" + parrot_express_dig_site_to_jungle = "Parrot Express Dig Site to Jungle" + parrot_express_volcano_to_jungle = "Parrot Express Volcano to Jungle" + parrot_express_jungle_to_docks = "Parrot Express Jungle to Docks" + parrot_express_dig_site_to_docks = "Parrot Express Dig Site to Docks" + parrot_express_volcano_to_docks = "Parrot Express Volcano to Docks" + +# Skull Cavern Elevator + + +class DeepWoodsEntrance: + secret_woods_to_deep_woods = "Woods to Deep Woods" + use_woods_obelisk = "Use Woods Obelisk" + deep_woods_house = "Deep Woods to Deep Woods House" + deep_woods_depth_1 = move_to_woods_depth(1) + deep_woods_depth_10 = move_to_woods_depth(10) + deep_woods_depth_20 = move_to_woods_depth(20) + deep_woods_depth_30 = move_to_woods_depth(30) + deep_woods_depth_40 = move_to_woods_depth(40) + deep_woods_depth_50 = move_to_woods_depth(50) + deep_woods_depth_60 = move_to_woods_depth(60) + deep_woods_depth_70 = move_to_woods_depth(70) + deep_woods_depth_80 = move_to_woods_depth(80) + deep_woods_depth_90 = move_to_woods_depth(90) + deep_woods_depth_100 = move_to_woods_depth(100) + + +class EugeneEntrance: + forest_to_garden = "Forest to Eugene's Garden" + garden_to_bedroom = "Eugene's Garden to Eugene's Bedroom" + + +class MagicEntrance: + store_to_altar = "Pierre's General Store to Magic Altar" + + +class JasperEntrance: + museum_to_bedroom = "Museum to Jasper's Bedroom" + + +class AlecEntrance: + forest_to_petshop = "Forest to Alec's Pet Shop" + petshop_to_bedroom = "Alec's Pet Shop to Alec's Bedroom" + + +class YobaEntrance: + secret_woods_to_clearing = "Woods to Yoba's Clearing" + + +class JunaEntrance: + forest_to_juna_cave = "Forest to Juna's Cave" + + +class AyeishaEntrance: + bus_stop_to_mail_van = "Bus Stop to Ayeisha's Mail Van" + + +class RileyEntrance: + town_to_riley = "Town to Riley's House" + diff --git a/worlds/stardew_valley/strings/fertilizer_names.py b/worlds/stardew_valley/strings/fertilizer_names.py new file mode 100644 index 00000000..27d4bf24 --- /dev/null +++ b/worlds/stardew_valley/strings/fertilizer_names.py @@ -0,0 +1,17 @@ +class Fertilizer: + basic = "Basic Fertilizer" + quality = "Quality Fertilizer" + deluxe = "Deluxe Fertilizer" + tree = "Tree Fertilizer" + + +class RetainingSoil: + basic = "Basic Retaining Soil" + quality = "Quality Retaining Soil" + deluxe = "Deluxe Retaining Soil" + + +class SpeedGro: + basic = "Speed-Gro" + deluxe = "Deluxe Speed-Gro" + hyper = "Hyper Speed-Gro" diff --git a/worlds/stardew_valley/strings/festival_check_names.py b/worlds/stardew_valley/strings/festival_check_names.py new file mode 100644 index 00000000..40487899 --- /dev/null +++ b/worlds/stardew_valley/strings/festival_check_names.py @@ -0,0 +1,32 @@ +class FestivalCheck: + cone_hat = "Cone Hat" + dance = "Dance with someone" + egg_hunt = "Egg Hunt Victory" + fair_stardrop = "Fair Stardrop" + fishing_competition = "Win Fishing Competition" + grange_display = "Grange Display" + iridium_fireplace = "Iridium Fireplace" + luau_soup = "Luau Soup" + lupini_1000_years = "Lupini: 1000 Years From Now" + lupini_clouds = "Lupini: Clouds" + lupini_land_of_clay = "Lupini: Land Of Clay" + lupini_portrait_mermaid = "Lupini: Portrait Of A Mermaid" + lupini_red_eagle = "Lupini: Red Eagle" + lupini_solar_kingdom = "Lupini: Solar Kingdom" + lupini_the_serpent = "Lupini: The Serpent" + lupini_three_trees = "Lupini: Three Trees" + lupini_tropical_fish = "Lupini: 'Tropical Fish #173'" + mermaid_pearl = "Mermaid Pearl" + moonlight_jellies = "Dance of the Moonlight Jellies" + rarecrow_1 = "Rarecrow #1 (Turnip Head)" + rarecrow_2 = "Rarecrow #2 (Witch)" + rarecrow_4 = "Rarecrow #4 (Snowman)" + rarecrow_5 = "Rarecrow #5 (Woman)" + rarecrow_7 = "Rarecrow #7 (Tanuki)" + rarecrow_8 = "Rarecrow #8 (Tribal Mask)" + secret_santa = "Secret Santa" + legend_of_the_winter_star = "The Legend of the Winter Star" + smashing_stone = "Smashing Stone" + spirit_eve_maze = "Spirit's Eve Maze" + strawberry_seeds = "Egg Festival: Strawberry Seeds" + all_rarecrows = "Collect All Rarecrows" diff --git a/worlds/stardew_valley/strings/fish_names.py b/worlds/stardew_valley/strings/fish_names.py new file mode 100644 index 00000000..8ee77810 --- /dev/null +++ b/worlds/stardew_valley/strings/fish_names.py @@ -0,0 +1,62 @@ +class Fish: + angler = "Angler" + any = "Any Fish" + blobfish = "Blobfish" + blue_discus = "Blue Discus" + bream = "Bream" + catfish = "Catfish" + crab = "Crab" + crayfish = "Crayfish" + crimsonfish = "Crimsonfish" + dorado = "Dorado" + glacierfish = "Glacierfish" + lava_eel = "Lava Eel" + legend = "Legend" + lionfish = "Lionfish" + lobster = "Lobster" + mussel = "Mussel" + mussel_node = "Mussel Node" + mutant_carp = "Mutant Carp" + octopus = "Octopus" + oyster = "Oyster" + pufferfish = "Pufferfish" + spookfish = "Spook Fish" + squid = "Squid" + stingray = "Stingray" + sturgeon = "Sturgeon" + sunfish = "Sunfish" + void_salmon = "Void Salmon" + albacore = "Albacore" + largemouth_bass = "Largemouth Bass" + smallmouth_bass = "Smallmouth Bass" + sardine = "Sardine" + periwinkle = "Periwinkle" + shrimp = "Shrimp" + snail = "Snail" + tuna = "Tuna" + eel = "Eel" + salmon = "Salmon" + + +class WaterItem: + seaweed = "Seaweed" + green_algae = "Green Algae" + white_algae = "White Algae" + clam = "Clam" + cockle = "Cockle" + coral = "Coral" + nautilus_shell = "Nautilus Shell" + sea_urchin = "Sea Urchin" + + +class Trash: + driftwood = "Driftwood" + trash = "Trash" + broken_cd = "Broken CD" + broken_glasses = "Broken Glasses" + joja_cola = "Joja Cola" + soggy_newspaper = "Soggy Newspaper" + + + + diff --git a/worlds/stardew_valley/strings/flower_names.py b/worlds/stardew_valley/strings/flower_names.py new file mode 100644 index 00000000..a804682f --- /dev/null +++ b/worlds/stardew_valley/strings/flower_names.py @@ -0,0 +1,3 @@ +class Flower: + sunflower = "Sunflower" + poppy = "Poppy" diff --git a/worlds/stardew_valley/strings/food_names.py b/worlds/stardew_valley/strings/food_names.py new file mode 100644 index 00000000..55e3ef0a --- /dev/null +++ b/worlds/stardew_valley/strings/food_names.py @@ -0,0 +1,67 @@ +class Meal: + blueberry_tart = "Blueberry Tart" + bread = "Bread" + fiddlehead_risotto = "Fiddlehead Risotto" + complete_breakfast = "Complete Breakfast" + fried_egg = "Fried Egg" + hashbrowns = "Hashbrowns" + pancakes = "Pancakes" + ice_cream = "Ice Cream" + maki_roll = "Maki Roll" + miners_treat = "Miner's Treat" + omelet = "Omelet" + parsnip_soup = "Parsnip Soup" + pink_cake = "Pink Cake" + pizza = "Pizza" + pumpkin_pie = "Pumpkin Pie" + roasted_hazelnuts = "Roasted Hazelnuts" + salad = "Salad" + spaghetti = "Spaghetti" + tortilla = "Tortilla" + algae_soup = "Algae Soup" + artichoke_dip = "Artichoke Dip" + baked_fish = "Baked Fish" + bean_hotpot = "Bean Hotpot" + blackberry_cobbler = "Blackberry Cobbler" + cheese_cauliflower = "Cheese Cauliflower" + chocolate_cake = "Chocolate Cake" + chowder = "Chowder" + crab_cakes = "Crab Cakes" + cranberry_candy = "Cranberry Candy" + crispy_bass = "Crispy Bass" + dish_o_the_sea = "Dish O' The Sea" + eggplant_parmesan = "Eggplant Parmesan" + escargot = "Escargot" + farmer_lunch = "Farmer's Lunch" + fish_taco = "Fish Taco" + fried_calamari = "Fried Calamari" + fried_eel = "Fried Eel" + fried_mushroom = "Fried Mushroom" + fruit_salad = "Fruit Salad" + glazed_yams = "Glazed Yams" + maple_bar = "Maple Bar" + pale_broth = "Pale Broth" + pepper_poppers = "Pepper Poppers" + plum_pudding = "Plum Pudding" + poppyseed_muffin = "Poppyseed Muffin" + red_plate = "Red Plate" + rhubarb_pie = "Rhubarb Pie" + rice_pudding = "Rice Pudding" + roots_platter = "Roots Platter" + salmon_dinner = "Salmon Dinner" + sashimi = "Sashimi" + stir_fry = "Stir Fry" + strange_bun = "Strange Bun" + stuffing = "Stuffing" + survival_burger = "Survival Burger" + tropical_curry = "Tropical Curry" + vegetable_medley = "Vegetable Medley" + + +class Beverage: + pina_colada = "Piña Colada" + ginger_ale = "Ginger Ale" + coffee = "Coffee" + triple_shot_espresso = "Triple Shot Espresso" + beer = "Beer" + joja_cola = "Joja Cola" diff --git a/worlds/stardew_valley/strings/forageable_names.py b/worlds/stardew_valley/strings/forageable_names.py new file mode 100644 index 00000000..b29ff317 --- /dev/null +++ b/worlds/stardew_valley/strings/forageable_names.py @@ -0,0 +1,35 @@ +class Forageable: + blackberry = "Blackberry" + cactus_fruit = "Cactus Fruit" + cave_carrot = "Cave Carrot" + chanterelle = "Chanterelle" + coconut = "Coconut" + common_mushroom = "Common Mushroom" + crocus = "Crocus" + crystal_fruit = "Crystal Fruit" + daffodil = "Daffodil" + dandelion = "Dandelion" + fiddlehead_fern = "Fiddlehead Fern" + ginger = "Ginger" + hay = "Hay" + hazelnut = "Hazelnut" + holly = "Holly" + leek = "Leek" + magma_cap = "Magma Cap" + morel = "Morel" + secret_note = "Secret Note" + spice_berry = "Spice Berry" + sweet_pea = "Sweet Pea" + wild_horseradish = "Wild Horseradish" + wild_plum = "Wild Plum" + winter_root = "Winter Root" + dragon_tooth = "Dragon Tooth" + red_mushroom = "Red Mushroom" + purple_mushroom = "Purple Mushroom" + rainbow_shell = "Rainbow Shell" + salmonberry = "Salmonberry" + snow_yam = "Snow Yam" + spring_onion = "Spring Onion" + + + diff --git a/worlds/stardew_valley/strings/fruit_tree_names.py b/worlds/stardew_valley/strings/fruit_tree_names.py new file mode 100644 index 00000000..bd49dc57 --- /dev/null +++ b/worlds/stardew_valley/strings/fruit_tree_names.py @@ -0,0 +1,10 @@ +class Sapling: + apple = "Apple Sapling" + apricot = "Apricot Sapling" + cherry = "Cherry Sapling" + orange = "Orange Sapling" + peach = "Peach Sapling" + pomegranate = "Pomegranate Sapling" + banana = "Banana Sapling" + mango = "Mango Sapling" + tea = "Tea Sapling" diff --git a/worlds/stardew_valley/strings/generic_names.py b/worlds/stardew_valley/strings/generic_names.py new file mode 100644 index 00000000..2ad18741 --- /dev/null +++ b/worlds/stardew_valley/strings/generic_names.py @@ -0,0 +1,4 @@ +class Generic: + any = "Any" + all = "All" + bachelor = "Bachelor" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/geode_names.py b/worlds/stardew_valley/strings/geode_names.py new file mode 100644 index 00000000..88d6fd93 --- /dev/null +++ b/worlds/stardew_valley/strings/geode_names.py @@ -0,0 +1,7 @@ +class Geode: + geode = "Geode" + frozen = "Frozen Geode" + magma = "Magma Geode" + omni = "Omni Geode" + artifact_trove = "Artifact Trove" + golden_coconut = "Golden Coconut" diff --git a/worlds/stardew_valley/strings/gift_names.py b/worlds/stardew_valley/strings/gift_names.py new file mode 100644 index 00000000..0baf31d5 --- /dev/null +++ b/worlds/stardew_valley/strings/gift_names.py @@ -0,0 +1,6 @@ +class Gift: + bouquet = "Bouquet" + wilted_bouquet = "Wilted Bouquet" + pearl = "Pearl" + golden_pumpkin = "Golden Pumpkin" + mermaid_pendant = "Mermaid's Pendant" diff --git a/worlds/stardew_valley/strings/goal_names.py b/worlds/stardew_valley/strings/goal_names.py new file mode 100644 index 00000000..da8b7d84 --- /dev/null +++ b/worlds/stardew_valley/strings/goal_names.py @@ -0,0 +1,10 @@ +class Goal: + grandpa_evaluation = "Succeed Grandpa's Evaluation" + community_center = "Complete Community Center" + bottom_of_the_mines = "Reach the Bottom of The Mines" + cryptic_note = "Complete Quest Cryptic Note" + master_angler = "Catch Every Fish" + complete_museum = "Complete the Museum Collection" + full_house = "Full House" + greatest_walnut_hunter = "Greatest Walnut Hunter" + perfection = "Perfection" diff --git a/worlds/stardew_valley/strings/ingredient_names.py b/worlds/stardew_valley/strings/ingredient_names.py new file mode 100644 index 00000000..22271d66 --- /dev/null +++ b/worlds/stardew_valley/strings/ingredient_names.py @@ -0,0 +1,6 @@ +class Ingredient: + wheat_flour = "Wheat Flour" + sugar = "Sugar" + oil = "Oil" + rice = "Rice" + vinegar = "Vinegar" diff --git a/worlds/stardew_valley/strings/machine_names.py b/worlds/stardew_valley/strings/machine_names.py new file mode 100644 index 00000000..55d6cef7 --- /dev/null +++ b/worlds/stardew_valley/strings/machine_names.py @@ -0,0 +1,22 @@ +class Machine: + bee_house = "Bee House" + cask = "Cask" + charcoal_kiln = "Charcoal Kiln" + cheese_press = "Cheese Press" + furnace = "Furnace" + geode_crusher = "Geode Crusher" + keg = "Keg" + lightning_rod = "Lightning Rod" + loom = "Loom" + mayonnaise_machine = "Mayonnaise Machine" + oil_maker = "Oil Maker" + preserves_jar = "Preserves Jar" + recycling_machine = "Recycling Machine" + seed_maker = "Seed Maker" + solar_panel = "Solar Panel" + tapper = "Tapper" + worm_bin = "Worm Bin" + coffee_maker = "Coffee Maker" + crab_pot = "Crab Pot" + ostrich_incubator = "Ostrich Incubator" + diff --git a/worlds/stardew_valley/strings/material_names.py b/worlds/stardew_valley/strings/material_names.py new file mode 100644 index 00000000..16511a5b --- /dev/null +++ b/worlds/stardew_valley/strings/material_names.py @@ -0,0 +1,9 @@ +class Material: + coal = "Coal" + fiber = "Fiber" + hardwood = "Hardwood" + sap = "Sap" + stone = "Stone" + wood = "Wood" + clay = "Clay" + cinder_shard = "Cinder Shard" diff --git a/worlds/stardew_valley/strings/metal_names.py b/worlds/stardew_valley/strings/metal_names.py new file mode 100644 index 00000000..67aefc69 --- /dev/null +++ b/worlds/stardew_valley/strings/metal_names.py @@ -0,0 +1,35 @@ +class Ore: + copper = "Copper Ore" + iron = "Iron Ore" + gold = "Gold Ore" + iridium = "Iridium Ore" + radioactive = "Radioactive Bar" + + +class MetalBar: + quartz = "Refined Quartz" + copper = "Copper Bar" + iron = "Iron Bar" + gold = "Gold Bar" + iridium = "Iridium Bar" + radioactive = "Radioactive Ore" + + +class Mineral: + aquamarine = "Aquamarine" + topaz = "Topaz" + jade = "Jade" + ruby = "Ruby" + emerald = "Emerald" + amethyst = "Amethyst" + + +class Artifact: + pass # Eventually this will be the artifact names + + +class Fossil: + bone_fragment = "Bone Fragment" + + + diff --git a/worlds/stardew_valley/strings/monster_drop_names.py b/worlds/stardew_valley/strings/monster_drop_names.py new file mode 100644 index 00000000..1b9f4242 --- /dev/null +++ b/worlds/stardew_valley/strings/monster_drop_names.py @@ -0,0 +1,6 @@ +class Loot: + slime = "Slime" + bug_meat = "Bug Meat" + bat_wing = "Bat Wing" + solar_essence = "Solar Essence" + void_essence = "Void Essence" diff --git a/worlds/stardew_valley/strings/performance_names.py b/worlds/stardew_valley/strings/performance_names.py new file mode 100644 index 00000000..30ae5bfc --- /dev/null +++ b/worlds/stardew_valley/strings/performance_names.py @@ -0,0 +1,13 @@ +class Performance: + basic = "Basic" + decent = "Decent" + good = "Good" + great = "Great" + galaxy = "Galaxy" + maximum = "Maximum" + tiers = {0: basic, + 1: decent, + 2: good, + 3: great, + 4: galaxy, + 5: maximum} \ No newline at end of file diff --git a/worlds/stardew_valley/strings/quest_names.py b/worlds/stardew_valley/strings/quest_names.py new file mode 100644 index 00000000..112e40a5 --- /dev/null +++ b/worlds/stardew_valley/strings/quest_names.py @@ -0,0 +1,57 @@ +class Quest: + introductions = "Introductions" + how_to_win_friends = "How To Win Friends" + getting_started = "Getting Started" + to_the_beach = "To The Beach" + raising_animals = "Raising Animals" + advancement = "Advancement" + archaeology = "Archaeology" + meet_the_wizard = "Meet The Wizard" + forging_ahead = "Forging Ahead" + smelting = "Smelting" + initiation = "Initiation" + robins_lost_axe = "Robin's Lost Axe" + jodis_request = "Jodi's Request" + mayors_shorts = "Mayor's \"Shorts\"" + blackberry_basket = "Blackberry Basket" + marnies_request = "Marnie's Request" + pam_is_thirsty = "Pam Is Thirsty" + a_dark_reagent = "A Dark Reagent" + cows_delight = "Cow's Delight" + the_skull_key = "The Skull Key" + crop_research = "Crop Research" + knee_therapy = "Knee Therapy" + robins_request = "Robin's Request" + qis_challenge = "Qi's Challenge" + the_mysterious_qi = "The Mysterious Qi" + carving_pumpkins = "Carving Pumpkins" + a_winter_mystery = "A Winter Mystery" + strange_note = "Strange Note" + cryptic_note = "Cryptic Note" + fresh_fruit = "Fresh Fruit" + aquatic_research = "Aquatic Research" + a_soldiers_star = "A Soldier's Star" + mayors_need = "Mayor's Need" + wanted_lobster = "Wanted: Lobster" + pam_needs_juice = "Pam Needs Juice" + fish_casserole = "Fish Casserole" + catch_a_squid = "Catch A Squid" + fish_stew = "Fish Stew" + pierres_notice = "Pierre's Notice" + clints_attempt = "Clint's Attempt" + a_favor_for_clint = "A Favor For Clint" + staff_of_power = "Staff Of Power" + grannys_gift = "Granny's Gift" + exotic_spirits = "Exotic Spirits" + catch_a_lingcod = "Catch a Lingcod" + the_pirates_wife = "The Pirate's Wife" + dark_talisman = "Dark Talisman" + goblin_problem = "Goblin Problem" + magic_ink = "Magic Ink" + +class ModQuest: + MrGinger = "Mr.Ginger's request" + AyeishaEnvelope = "Missing Envelope" + AyeishaRing = "Lost Emerald Ring" + JunaCola = "Juna's Drink Request" + JunaSpaghetti = "Juna's BFF Request" \ No newline at end of file diff --git a/worlds/stardew_valley/data/region_data.py b/worlds/stardew_valley/strings/region_names.py similarity index 54% rename from worlds/stardew_valley/data/region_data.py rename to worlds/stardew_valley/strings/region_names.py index 8f1eb1ab..9fa25711 100644 --- a/worlds/stardew_valley/data/region_data.py +++ b/worlds/stardew_valley/strings/region_names.py @@ -1,4 +1,4 @@ -class SVRegion: +class Region: menu = "Menu" stardew_valley = "Stardew Valley" farm_house = "Farmhouse" @@ -10,6 +10,7 @@ class SVRegion: forest = "Forest" bus_stop = "Bus Stop" backwoods = "Backwoods" + bus_tunnel = "Bus Tunnel" railroad = "Railroad" secret_woods = "Secret Woods" community_center = "Community Center" @@ -19,17 +20,49 @@ class SVRegion: boiler_room = "Boiler Room" vault = "Vault" bulletin_board = "Bulletin Board" - desert = "The Desert" + desert = "Desert" + oasis = "Oasis" + casino = "Casino" mines = "The Mines" + mines_dwarf_shop = "Mines Dwarf Shop" skull_cavern_entrance = "Skull Cavern Entrance" skull_cavern = "Skull Cavern" - sewers = "Sewers" + sewer = "Sewer" mutant_bug_lair = "Mutant Bug Lair" witch_swamp = "Witch's Swamp" - ginger_island = "Ginger Island" - pirate_cove = ginger_island - dig_site = ginger_island - perfect_skull_cavern = "Skull Cavern Floor 100" + witch_hut = "Witch's Hut" + island_south = "Island South" + island_resort = "Island Resort" + island_south_east = "Island Southeast" + pirate_cove = "Pirate Cove" + island_east = "Island East" + island_north = "Island North" + island_shrine = "Island Shrine" + leo_hut = "Leo's Hut" + leo_treehouse = "Leo's TreeHouse" + island_farmhouse = "Island Farmhouse" + dig_site = "Dig Site" + professor_snail_cave = "Professor Snail Cave" + field_office = "Field Office" + volcano = "Volcano Entrance" + volcano_secret_beach = "Volcano Secret Beach" + island_trader = "Island Trader" + volcano_floor_5 = "Volcano - Floor 5" + volcano_dwarf_shop = "Volcano Dwarf Shop" + volcano_floor_10 = "Volcano - Floor 10" + island_west = "Island West" + gourmand_frog_cave = "Gourmand Frog Cave" + colored_crystals_cave = "Colored Crystals Cave" + shipwreck = "Shipwreck" + qi_walnut_room = "Qi's Walnut Room" + skull_cavern_25 = "Skull Cavern Floor 25" + skull_cavern_50 = "Skull Cavern Floor 50" + skull_cavern_75 = "Skull Cavern Floor 75" + skull_cavern_100 = "Skull Cavern Floor 100" + skull_cavern_125 = "Skull Cavern Floor 125" + skull_cavern_150 = "Skull Cavern Floor 150" + skull_cavern_175 = "Skull Cavern Floor 175" + skull_cavern_200 = "Skull Cavern Floor 200" hospital = "Hospital" carpenter = "Carpenter Shop" alex_house = "Alex's House" @@ -38,12 +71,12 @@ class SVRegion: traveling_cart = "Traveling Cart" farm_cave = "Farmcave" greenhouse = "Greenhouse" - tunnel = "Tunnel" tunnel_entrance = "Tunnel Entrance" leah_house = "Leah's Cottage" wizard_tower = "Wizard Tower" wizard_basement = "Wizard Basement" tent = "Tent" + maru_room = "Maru's Room" sebastian_room = "Sebastian's Room" adventurer_guild = "Adventurer's Guild" quarry = "Quarry" @@ -62,6 +95,7 @@ class SVRegion: sam_house = "Sam's House" jojamart = "JojaMart" fish_shop = "Willy's Fish Shop" + boat_tunnel = "Boat Tunnel" tide_pools = "Tide Pools" bathhouse_entrance = "Bathhouse Entrance" locker_room = "Locker Room" @@ -97,3 +131,52 @@ class SVRegion: mines_floor_115 = "The Mines - Floor 115" mines_floor_120 = "The Mines - Floor 120" + +class DeepWoodsRegion: + main_lichtung = "Entrance to the Deep Woods" + abandoned_home = "Abandoned Home in Deep Woods" + woods_obelisk_menu = "Woods Obelisk Menu" + floor_10 = "The Deep Woods Depth 10" + floor_20 = "The Deep Woods Depth 20" + floor_30 = "The Deep Woods Depth 30" + floor_40 = "The Deep Woods Depth 40" + floor_50 = "The Deep Woods Depth 50" + floor_60 = "The Deep Woods Depth 60" + floor_70 = "The Deep Woods Depth 70" + floor_80 = "The Deep Woods Depth 80" + floor_90 = "The Deep Woods Depth 90" + floor_100 = "The Deep Woods Depth 100" + + +class EugeneRegion: + eugene_garden = "Eugene's Garden" + eugene_bedroom = "Eugene's Bedroom" + + +class MagicRegion: + altar = "Magic Altar" + + +class JasperRegion: + jasper_bedroom = "Jasper's Bedroom" + + +class AlecRegion: + pet_store = "Alec's Pet Shop" + alec_bedroom = "Alec's Bedroom" + + +class YobaRegion: + yoba_clearing = "Yoba's Clearing" + + +class JunaRegion: + juna_cave = "Juna's Cave" + + +class AyeishaRegion: + mail_van = "Ayeisha's Mail Van" + + +class RileyRegion: + riley_house = "Riley's House" diff --git a/worlds/stardew_valley/strings/season_names.py b/worlds/stardew_valley/strings/season_names.py new file mode 100644 index 00000000..93c58fce --- /dev/null +++ b/worlds/stardew_valley/strings/season_names.py @@ -0,0 +1,6 @@ +class Season: + spring = "Spring" + summer = "Summer" + fall = "Fall" + winter = "Winter" + progressive = "Progressive Season" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/seed_names.py b/worlds/stardew_valley/strings/seed_names.py new file mode 100644 index 00000000..080bdf85 --- /dev/null +++ b/worlds/stardew_valley/strings/seed_names.py @@ -0,0 +1,9 @@ +class Seed: + sunflower = "Sunflower Seeds" + tomato = "Tomato Seeds" + melon = "Melon Seeds" + wheat = "Wheat Seeds" + garlic = "Garlic Seeds" + pineapple = "Pineapple Seeds" + taro = "Taro Tuber" + coffee = "Coffee Bean" diff --git a/worlds/stardew_valley/strings/skill_names.py b/worlds/stardew_valley/strings/skill_names.py new file mode 100644 index 00000000..7e7fdb79 --- /dev/null +++ b/worlds/stardew_valley/strings/skill_names.py @@ -0,0 +1,15 @@ +class Skill: + farming = "Farming" + foraging = "Foraging" + fishing = "Fishing" + mining = "Mining" + combat = "Combat" + + +class ModSkill: + luck = "Luck" + binning = "Binning" + archaeology = "Archaeology" + cooking = "Cooking" + magic = "Magic" + socializing = "Socializing" diff --git a/worlds/stardew_valley/strings/special_order_names.py b/worlds/stardew_valley/strings/special_order_names.py new file mode 100644 index 00000000..04eec828 --- /dev/null +++ b/worlds/stardew_valley/strings/special_order_names.py @@ -0,0 +1,33 @@ +class SpecialOrder: + island_ingredients = "Island Ingredients" + cave_patrol = "Cave Patrol" + aquatic_overpopulation = "Aquatic Overpopulation" + biome_balance = "Biome Balance" + rock_rejuivenation = "Rock Rejuvenation" + gifts_for_george = "Gifts for George" + fragments_of_the_past = "Fragments of the past" + gus_famous_omelet = "Gus' Famous Omelet" + crop_order = "Crop Order" + community_cleanup = "Community Cleanup" + the_strong_stuff = "The Strong Stuff" + pierres_prime_produce = "Pierre's Prime Produce" + robins_project = "Robin's Project" + robins_resource_rush = "Robin's Resource Rush" + juicy_bugs_wanted_yum = "Juicy Bugs Wanted!" + tropical_fish = "Tropical Fish" + a_curious_substance = "A Curious Substance" + prismatic_jelly = "Prismatic Jelly" + qis_crop = "Qi's Crop" + lets_play_a_game = "Let's Play A Game" + four_precious_stones = "Four Precious Stones" + qis_hungry_challenge = "Qi's Hungry Challenge" + qis_cuisine = "Qi's Cuisine" + qis_kindness = "Qi's Kindness" + extended_family = "Extended Family" + danger_in_the_deep = "Danger In The Deep" + skull_cavern_invasion = "Skull Cavern Invasion" + qis_prismatic_grange = "Qi's Prismatic Grange" + + +class ModSpecialOrder: + junas_monster_mash = "Juna's Monster Mash" diff --git a/worlds/stardew_valley/strings/spells.py b/worlds/stardew_valley/strings/spells.py new file mode 100644 index 00000000..fd2a515d --- /dev/null +++ b/worlds/stardew_valley/strings/spells.py @@ -0,0 +1,22 @@ +class MagicSpell: + clear_debris = "Spell: Clear Debris" + till = "Spell: Till" + water = "Spell: Water" + blink = "Spell: Blink" + evac = "Spell: Evac" + haste = "Spell: Haste" + heal = "Spell: Heal" + buff = "Spell: Buff" + shockwave = "Spell: Shockwave" + fireball = "Spell: Fireball" + frostbite = "Spell: Frostbite" + teleport = "Spell: Teleport" + lantern = "Spell: Lantern" + tendrils = "Spell: Tendrils" + photosynthesis = "Spell: Photosynthesis" + descend = "Spell: Descend" + meteor = "Spell: Meteor" + bloodmana = "Spell: Bloodmana" + lucksteal = "Spell: Lucksteal" + spirit = "Spell: Spirit" + rewind = "Spell: Rewind" diff --git a/worlds/stardew_valley/strings/tool_names.py b/worlds/stardew_valley/strings/tool_names.py new file mode 100644 index 00000000..ea8c00b9 --- /dev/null +++ b/worlds/stardew_valley/strings/tool_names.py @@ -0,0 +1,31 @@ +class Tool: + pickaxe = "Pickaxe" + axe = "Axe" + hoe = "Hoe" + watering_can = "Watering Can" + trash_can = "Trash Can" + fishing_rod = "Fishing Rod" + scythe = "Scythe" + golden_scythe = "Golden Scythe" + + +class ToolMaterial: + basic = "Basic" + copper = "Copper" + iron = "Iron" + gold = "Gold" + iridium = "Iridium" + tiers = {0: basic, + 1: copper, + 2: iron, + 3: gold, + 4: iridium} + + +class APTool: + pickaxe = f"Progressive {Tool.pickaxe}" + axe = f"Progressive {Tool.axe}" + hoe = f"Progressive {Tool.hoe}" + watering_can = f"Progressive {Tool.watering_can}" + trash_can = f"Progressive {Tool.trash_can}" + fishing_rod = f"Progressive {Tool.fishing_rod}" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/tv_channel_names.py b/worlds/stardew_valley/strings/tv_channel_names.py new file mode 100644 index 00000000..e01bd55e --- /dev/null +++ b/worlds/stardew_valley/strings/tv_channel_names.py @@ -0,0 +1,2 @@ +class Channel: + queen_of_sauce = "The Queen of Sauce" diff --git a/worlds/stardew_valley/strings/villager_names.py b/worlds/stardew_valley/strings/villager_names.py new file mode 100644 index 00000000..5bf13ea8 --- /dev/null +++ b/worlds/stardew_valley/strings/villager_names.py @@ -0,0 +1,49 @@ +class NPC: + alex = "Alex" + elliott = "Elliott" + harvey = "Harvey" + sam = "Sam" + sebastian = "Sebastian" + shane = "Shane" + abigail = "Abigail" + emily = "Emily" + haley = "Haley" + leah = "Leah" + maru = "Maru" + penny = "Penny" + caroline = "Caroline" + clint = "Clint" + demetrius = "Demetrius" + dwarf = "Dwarf" + evelyn = "Evelyn" + george = "George" + gus = "Gus" + jas = "Jas" + jodi = "Jodi" + kent = "Kent" + krobus = "Krobus" + leo = "Leo" + lewis = "Lewis" + linus = "Linus" + marnie = "Marnie" + pam = "Pam" + pierre = "Pierre" + robin = "Robin" + sandy = "Sandy" + vincent = "Vincent" + willy = "Willy" + wizard = "Wizard" + pet = "Pet" + +class ModNPC: + alec = "Alec" + ayeisha = "Ayeisha" + delores = "Delores" + eugene = "Eugene" + jasper = "Jasper" + juna = "Juna" + mr_ginger = "Mr. Ginger" + riley = "Riley" + shiko = "Shiko" + wellwick = "Wellwick" + yoba = "Yoba" \ No newline at end of file diff --git a/worlds/stardew_valley/strings/wallet_item_names.py b/worlds/stardew_valley/strings/wallet_item_names.py new file mode 100644 index 00000000..31026ebb --- /dev/null +++ b/worlds/stardew_valley/strings/wallet_item_names.py @@ -0,0 +1,5 @@ +class Wallet: + magnifying_glass = "Magnifying Glass" + rusty_key = "Rusty Key" + skull_key = "Skull Key" + dark_talisman = "Dark Talisman" diff --git a/worlds/stardew_valley/strings/weapon_names.py b/worlds/stardew_valley/strings/weapon_names.py new file mode 100644 index 00000000..009cd6df --- /dev/null +++ b/worlds/stardew_valley/strings/weapon_names.py @@ -0,0 +1,4 @@ +class Weapon: + slingshot = "Slingshot" + master_slingshot = "Master Slingshot" + any_slingshot = "Any Slingshot" diff --git a/worlds/stardew_valley/test/TestGeneration.py b/worlds/stardew_valley/test/TestGeneration.py index 16c216b2..a80f334d 100644 --- a/worlds/stardew_valley/test/TestGeneration.py +++ b/worlds/stardew_valley/test/TestGeneration.py @@ -1,30 +1,119 @@ -from BaseClasses import ItemClassification -from . import SVTestBase +from BaseClasses import ItemClassification, MultiWorld +from . import setup_solo_multiworld, SVTestBase from .. import locations, items, location_table, options -from ..data.villagers_data import all_villagers_by_name +from ..data.villagers_data import all_villagers_by_name, all_villagers_by_mod_by_name from ..items import items_by_group, Group from ..locations import LocationTags +from ..mods.mod_data import ModNames + + +def get_real_locations(tester: SVTestBase, multiworld: MultiWorld): + return [location for location in multiworld.get_locations(tester.player) if not location.event] + + +def get_real_location_names(tester: SVTestBase, multiworld: MultiWorld): + return [location.name for location in multiworld.get_locations(tester.player) if not location.event] class TestBaseItemGeneration(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + } def test_all_progression_items_are_added_to_the_pool(self): - for classification in [ItemClassification.progression, ItemClassification.useful]: - with self.subTest(classification=classification): - - all_classified_items = {self.world.create_item(item) - for item in items.items_by_group[items.Group.COMMUNITY_REWARD] - if item.classification is classification} - - for item in all_classified_items: - self.assertIn(item, self.multiworld.itempool) + all_created_items = [item.name for item in self.multiworld.itempool] + # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression + items_to_ignore = [event.name for event in items.events] + items_to_ignore.extend(item.name for item in items.all_items if item.mod_name is not None) + items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) + items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) + items_to_ignore.extend(footwear.name for footwear in items.items_by_group[Group.FOOTWEAR]) + items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) + items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) + progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression + and item.name not in items_to_ignore] + for progression_item in progression_items: + with self.subTest(f"{progression_item.name}"): + self.assertIn(progression_item.name, all_created_items) def test_creates_as_many_item_as_non_event_locations(self): - non_event_locations = [location for location in self.multiworld.get_locations(self.player) if + non_event_locations = [location for location in get_real_locations(self, self.multiworld) if not location.event] self.assertEqual(len(non_event_locations), len(self.multiworld.itempool)) + def test_does_not_create_deprecated_items(self): + all_created_items = [item.name for item in self.multiworld.itempool] + for deprecated_item in items.items_by_group[items.Group.DEPRECATED]: + with self.subTest(f"{deprecated_item.name}"): + self.assertNotIn(deprecated_item.name, all_created_items) + + def test_does_not_create_more_than_one_maximum_one_items(self): + all_created_items = [item.name for item in self.multiworld.itempool] + for maximum_one_item in items.items_by_group[items.Group.MAXIMUM_ONE]: + with self.subTest(f"{maximum_one_item.name}"): + self.assertLessEqual(all_created_items.count(maximum_one_item.name), 1) + + def test_does_not_create_exactly_two_items(self): + all_created_items = [item.name for item in self.multiworld.itempool] + for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: + with self.subTest(f"{exactly_two_item.name}"): + count = all_created_items.count(exactly_two_item.name) + self.assertTrue(count == 0 or count == 2) + + +class TestNoGingerIslandItemGeneration(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true + } + + def test_all_progression_items_except_island_are_added_to_the_pool(self): + all_created_items = [item.name for item in self.multiworld.itempool] + # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression + items_to_ignore = [event.name for event in items.events] + items_to_ignore.extend(item.name for item in items.all_items if item.mod_name is not None) + items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) + items_to_ignore.extend(season.name for season in items.items_by_group[Group.WEAPON]) + items_to_ignore.extend(season.name for season in items.items_by_group[Group.FOOTWEAR]) + items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) + progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression + and item.name not in items_to_ignore] + for progression_item in progression_items: + with self.subTest(f"{progression_item.name}"): + if Group.GINGER_ISLAND in progression_item.groups: + self.assertNotIn(progression_item.name, all_created_items) + else: + self.assertIn(progression_item.name, all_created_items) + + def test_creates_as_many_item_as_non_event_locations(self): + non_event_locations = [location for location in get_real_locations(self, self.multiworld) if + not location.event] + + self.assertEqual(len(non_event_locations), len(self.multiworld.itempool)) + + def test_does_not_create_deprecated_items(self): + all_created_items = [item.name for item in self.multiworld.itempool] + for deprecated_item in items.items_by_group[items.Group.DEPRECATED]: + with self.subTest(f"Deprecated item: {deprecated_item.name}"): + self.assertNotIn(deprecated_item.name, all_created_items) + + def test_does_not_create_more_than_one_maximum_one_items(self): + all_created_items = [item.name for item in self.multiworld.itempool] + for maximum_one_item in items.items_by_group[items.Group.MAXIMUM_ONE]: + with self.subTest(f"{maximum_one_item.name}"): + self.assertLessEqual(all_created_items.count(maximum_one_item.name), 1) + + def test_does_not_create_exactly_two_items(self): + all_created_items = [item.name for item in self.multiworld.itempool] + for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: + with self.subTest(f"{exactly_two_item.name}"): + count = all_created_items.count(exactly_two_item.name) + self.assertTrue(count == 0 or count == 2) + class TestGivenProgressiveBackpack(SVTestBase): options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive} @@ -35,7 +124,8 @@ class TestGivenProgressiveBackpack(SVTestBase): def test_when_generate_world_then_backpack_locations_are_added(self): created_locations = {location.name for location in self.multiworld.get_locations(1)} backpacks_exist = [location.name in created_locations - for location in locations.locations_by_tag[LocationTags.BACKPACK]] + for location in locations.locations_by_tag[LocationTags.BACKPACK] + if location.name != "Premium Pack"] all_exist = all(backpacks_exist) self.assertTrue(all_exist) @@ -72,7 +162,7 @@ class TestRemixedMineRewards(SVTestBase): class TestProgressiveElevator(SVTestBase): options = { - options.TheMinesElevatorsProgression.internal_name: options.TheMinesElevatorsProgression.option_progressive, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, options.ToolProgression.internal_name: options.ToolProgression.option_progressive, options.SkillProgression.internal_name: options.SkillProgression.option_progressive, } @@ -110,30 +200,44 @@ class TestProgressiveElevator(SVTestBase): class TestLocationGeneration(SVTestBase): def test_all_location_created_are_in_location_table(self): - for location in self.multiworld.get_locations(self.player): + for location in get_real_locations(self, self.multiworld): if not location.event: self.assertIn(location.name, location_table) class TestLocationAndItemCount(SVTestBase): - options = { - options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, - options.SeedShuffle.internal_name: options.SeedShuffle.option_shuffled, - options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla, - options.ToolProgression.internal_name: options.ToolProgression.option_vanilla, - options.SkillProgression.internal_name: options.SkillProgression.option_vanilla, - options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla, - options.TheMinesElevatorsProgression.internal_name: options.TheMinesElevatorsProgression.option_vanilla, - options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, - options.HelpWantedLocations.internal_name: 0, - options.Fishsanity.internal_name: options.Fishsanity.option_none, - options.Museumsanity.internal_name: options.Museumsanity.option_none, - options.Friendsanity.internal_name: options.Museumsanity.option_none, - options.NumberOfPlayerBuffs.internal_name: 12, - } def test_minimal_location_maximal_items_still_valid(self): - self.assertGreaterEqual(len(self.multiworld.get_locations()), len(self.multiworld.get_items())) + min_max_options = self.minimal_locations_maximal_items() + multiworld = setup_solo_multiworld(min_max_options) + valid_locations = get_real_locations(self, multiworld) + self.assertGreaterEqual(len(valid_locations), len(multiworld.itempool)) + + def test_allsanity_without_mods_has_at_least_locations(self): + expected_locations = 993 + allsanity_options = self.allsanity_options_without_mods() + multiworld = setup_solo_multiworld(allsanity_options) + number_locations = len(get_real_locations(self, multiworld)) + self.assertGreaterEqual(number_locations, expected_locations) + print(f"Stardew Valley - Allsanity Locations without mods: {number_locations}") + if number_locations != expected_locations: + print(f"\tNew locations detected!" + f"\n\tPlease update test_allsanity_without_mods_has_at_least_locations" + f"\n\t\tExpected: {expected_locations}" + f"\n\t\tActual: {number_locations}") + + def test_allsanity_with_mods_has_at_least_locations(self): + expected_locations = 1245 + allsanity_options = self.allsanity_options_with_mods() + multiworld = setup_solo_multiworld(allsanity_options) + number_locations = len(get_real_locations(self, multiworld)) + self.assertGreaterEqual(number_locations, expected_locations) + print(f"\nStardew Valley - Allsanity Locations with all mods: {number_locations}") + if number_locations != expected_locations: + print(f"\tNew locations detected!" + f"\n\tPlease update test_allsanity_with_mods_has_at_least_locations" + f"\n\t\tExpected: {expected_locations}" + f"\n\t\tActual: {number_locations}") class TestFriendsanityNone(SVTestBase): @@ -142,24 +246,25 @@ class TestFriendsanityNone(SVTestBase): } def test_no_friendsanity_items(self): - for item in self.multiworld.get_items(): - self.assertFalse(item.name.endswith(": 1 <3")) + for item in self.multiworld.itempool: + self.assertFalse(item.name.endswith(" <3")) def test_no_friendsanity_locations(self): - for location in self.multiworld.get_locations(): - self.assertFalse(location.name.startswith("Friendsanity")) + for location_name in get_real_location_names(self, self.multiworld): + self.assertFalse(location_name.startswith("Friendsanity")) class TestFriendsanityBachelors(SVTestBase): options = { options.Friendsanity.internal_name: options.Friendsanity.option_bachelors, + options.FriendsanityHeartSize.internal_name: 1, } bachelors = {"Harvey", "Elliott", "Sam", "Alex", "Shane", "Sebastian", "Emily", "Haley", "Leah", "Abigail", "Penny", "Maru"} def test_friendsanity_only_bachelor_items(self): - suffix = ": 1 <3" - for item in self.multiworld.get_items(): + suffix = " <3" + for item in self.multiworld.itempool: if item.name.endswith(suffix): villager_name = item.name[:item.name.index(suffix)] self.assertIn(villager_name, self.bachelors) @@ -167,9 +272,9 @@ class TestFriendsanityBachelors(SVTestBase): def test_friendsanity_only_bachelor_locations(self): prefix = "Friendsanity: " suffix = " <3" - for location in self.multiworld.get_locations(): - if location.name.startswith(prefix): - name_no_prefix = location.name[len(prefix):] + for location_name in get_real_location_names(self, self.multiworld): + if location_name.startswith(prefix): + name_no_prefix = location_name[len(prefix):] name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] parts = name_trimmed.split(" ") name = parts[0] @@ -181,12 +286,13 @@ class TestFriendsanityBachelors(SVTestBase): class TestFriendsanityStartingNpcs(SVTestBase): options = { options.Friendsanity.internal_name: options.Friendsanity.option_starting_npcs, + options.FriendsanityHeartSize.internal_name: 1, } excluded_npcs = {"Leo", "Krobus", "Dwarf", "Sandy", "Kent"} def test_friendsanity_only_starting_npcs_items(self): - suffix = ": 1 <3" - for item in self.multiworld.get_items(): + suffix = " <3" + for item in self.multiworld.itempool: if item.name.endswith(suffix): villager_name = item.name[:item.name.index(suffix)] self.assertNotIn(villager_name, self.excluded_npcs) @@ -194,15 +300,15 @@ class TestFriendsanityStartingNpcs(SVTestBase): def test_friendsanity_only_starting_npcs_locations(self): prefix = "Friendsanity: " suffix = " <3" - for location in self.multiworld.get_locations(): - if location.name.startswith(prefix): - name_no_prefix = location.name[len(prefix):] + for location_name in get_real_location_names(self, self.multiworld): + if location_name.startswith(prefix): + name_no_prefix = location_name[len(prefix):] name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] parts = name_trimmed.split(" ") name = parts[0] hearts = parts[1] self.assertNotIn(name, self.excluded_npcs) - self.assertTrue(name in all_villagers_by_name or name == "Pet") + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") if name == "Pet": self.assertLessEqual(int(hearts), 5) elif all_villagers_by_name[name].bachelor: @@ -214,26 +320,62 @@ class TestFriendsanityStartingNpcs(SVTestBase): class TestFriendsanityAllNpcs(SVTestBase): options = { options.Friendsanity.internal_name: options.Friendsanity.option_all, + options.FriendsanityHeartSize.internal_name: 1, } def test_friendsanity_all_items(self): - suffix = ": 1 <3" - for item in self.multiworld.get_items(): + suffix = " <3" + for item in self.multiworld.itempool: if item.name.endswith(suffix): villager_name = item.name[:item.name.index(suffix)] - self.assertTrue(villager_name in all_villagers_by_name or villager_name == "Pet") + self.assertTrue(villager_name in all_villagers_by_mod_by_name[ModNames.vanilla] or villager_name == "Pet") def test_friendsanity_all_locations(self): prefix = "Friendsanity: " suffix = " <3" - for location in self.multiworld.get_locations(): - if location.name.startswith(prefix): - name_no_prefix = location.name[len(prefix):] + for location_name in get_real_location_names(self, self.multiworld): + if location_name.startswith(prefix): + name_no_prefix = location_name[len(prefix):] name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] parts = name_trimmed.split(" ") name = parts[0] hearts = parts[1] - self.assertTrue(name in all_villagers_by_name or name == "Pet") + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") + if name == "Pet": + self.assertLessEqual(int(hearts), 5) + elif all_villagers_by_name[name].bachelor: + self.assertLessEqual(int(hearts), 8) + else: + self.assertLessEqual(int(hearts), 10) + + +class TestFriendsanityAllNpcsExcludingGingerIsland(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all, + options.FriendsanityHeartSize.internal_name: 1, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true + } + + def test_friendsanity_all_items(self): + suffix = " <3" + for item in self.multiworld.itempool: + if item.name.endswith(suffix): + villager_name = item.name[:item.name.index(suffix)] + self.assertNotEqual(villager_name, "Leo") + self.assertTrue(villager_name in all_villagers_by_mod_by_name[ModNames.vanilla] or villager_name == "Pet") + + def test_friendsanity_all_locations(self): + prefix = "Friendsanity: " + suffix = " <3" + for location_name in get_real_location_names(self, self.multiworld): + if location_name.startswith(prefix): + name_no_prefix = location_name[len(prefix):] + name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] + parts = name_trimmed.split(" ") + name = parts[0] + hearts = parts[1] + self.assertNotEqual(name, "Leo") + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") if name == "Pet": self.assertLessEqual(int(hearts), 5) elif all_villagers_by_name[name].bachelor: @@ -245,29 +387,182 @@ class TestFriendsanityAllNpcs(SVTestBase): class TestFriendsanityAllNpcsWithMarriage(SVTestBase): options = { options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 1, } def test_friendsanity_all_with_marriage_items(self): - suffix = ": 1 <3" - for item in self.multiworld.get_items(): + suffix = " <3" + for item in self.multiworld.itempool: if item.name.endswith(suffix): villager_name = item.name[:item.name.index(suffix)] - self.assertTrue(villager_name in all_villagers_by_name or villager_name == "Pet") + self.assertTrue(villager_name in all_villagers_by_mod_by_name[ModNames.vanilla] or villager_name == "Pet") def test_friendsanity_all_with_marriage_locations(self): prefix = "Friendsanity: " suffix = " <3" - for location in self.multiworld.get_locations(): - if location.name.startswith(prefix): - name_no_prefix = location.name[len(prefix):] + for location_name in get_real_location_names(self, self.multiworld): + if location_name.startswith(prefix): + name_no_prefix = location_name[len(prefix):] name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] parts = name_trimmed.split(" ") name = parts[0] hearts = parts[1] - self.assertTrue(name in all_villagers_by_name or name == "Pet") + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") if name == "Pet": self.assertLessEqual(int(hearts), 5) elif all_villagers_by_name[name].bachelor: self.assertLessEqual(int(hearts), 14) else: self.assertLessEqual(int(hearts), 10) + + +class TestFriendsanityAllNpcsWithMarriageHeartSize2(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 2, + } + + def test_friendsanity_all_with_marriage_items(self): + suffix = " <3" + item_names = [item.name for item in self.multiworld.itempool] + for villager_name in all_villagers_by_mod_by_name[ModNames.vanilla]: + heart_item_name = f"{villager_name}{suffix}" + number_heart_items = item_names.count(heart_item_name) + if all_villagers_by_name[villager_name].bachelor: + self.assertEqual(number_heart_items, 7) + else: + self.assertEqual(number_heart_items, 5) + self.assertEqual(item_names.count("Pet <3"), 3) + + def test_friendsanity_all_with_marriage_locations(self): + prefix = "Friendsanity: " + suffix = " <3" + for location_name in get_real_location_names(self, self.multiworld): + if not location_name.startswith(prefix): + continue + name_no_prefix = location_name[len(prefix):] + name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] + parts = name_trimmed.split(" ") + name = parts[0] + hearts = int(parts[1]) + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") + if name == "Pet": + self.assertTrue(hearts == 2 or hearts == 4 or hearts == 5) + elif all_villagers_by_name[name].bachelor: + self.assertTrue(hearts == 2 or hearts == 4 or hearts == 6 or hearts == 8 or hearts == 10 or hearts == 12 or hearts == 14) + else: + self.assertTrue(hearts == 2 or hearts == 4 or hearts == 6 or hearts == 8 or hearts == 10) + + +class TestFriendsanityAllNpcsWithMarriageHeartSize3(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 3, + } + + def test_friendsanity_all_with_marriage_items(self): + suffix = " <3" + item_names = [item.name for item in self.multiworld.itempool] + for villager_name in all_villagers_by_mod_by_name[ModNames.vanilla]: + heart_item_name = f"{villager_name}{suffix}" + number_heart_items = item_names.count(heart_item_name) + if all_villagers_by_name[villager_name].bachelor: + self.assertEqual(number_heart_items, 5) + else: + self.assertEqual(number_heart_items, 4) + self.assertEqual(item_names.count("Pet <3"), 2) + + def test_friendsanity_all_with_marriage_locations(self): + prefix = "Friendsanity: " + suffix = " <3" + for location_name in get_real_location_names(self, self.multiworld): + if not location_name.startswith(prefix): + continue + name_no_prefix = location_name[len(prefix):] + name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] + parts = name_trimmed.split(" ") + name = parts[0] + hearts = int(parts[1]) + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") + if name == "Pet": + self.assertTrue(hearts == 3 or hearts == 5) + elif all_villagers_by_name[name].bachelor: + self.assertTrue(hearts == 3 or hearts == 6 or hearts == 9 or hearts == 12 or hearts == 14) + else: + self.assertTrue(hearts == 3 or hearts == 6 or hearts == 9 or hearts == 10) + + +class TestFriendsanityAllNpcsWithMarriageHeartSize4(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 4, + } + + def test_friendsanity_all_with_marriage_items(self): + suffix = " <3" + item_names = [item.name for item in self.multiworld.itempool] + for villager_name in all_villagers_by_mod_by_name[ModNames.vanilla]: + heart_item_name = f"{villager_name}{suffix}" + number_heart_items = item_names.count(heart_item_name) + if all_villagers_by_name[villager_name].bachelor: + self.assertEqual(number_heart_items, 4) + else: + self.assertEqual(number_heart_items, 3) + self.assertEqual(item_names.count("Pet <3"), 2) + + def test_friendsanity_all_with_marriage_locations(self): + prefix = "Friendsanity: " + suffix = " <3" + for location_name in get_real_location_names(self, self.multiworld): + if not location_name.startswith(prefix): + continue + name_no_prefix = location_name[len(prefix):] + name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] + parts = name_trimmed.split(" ") + name = parts[0] + hearts = int(parts[1]) + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") + if name == "Pet": + self.assertTrue(hearts == 4 or hearts == 5) + elif all_villagers_by_name[name].bachelor: + self.assertTrue(hearts == 4 or hearts == 8 or hearts == 12 or hearts == 14) + else: + self.assertTrue(hearts == 4 or hearts == 8 or hearts == 10) + + +class TestFriendsanityAllNpcsWithMarriageHeartSize5(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 5, + } + + def test_friendsanity_all_with_marriage_items(self): + suffix = " <3" + item_names = [item.name for item in self.multiworld.itempool] + for villager_name in all_villagers_by_mod_by_name[ModNames.vanilla]: + heart_item_name = f"{villager_name}{suffix}" + number_heart_items = item_names.count(heart_item_name) + if all_villagers_by_name[villager_name].bachelor: + self.assertEqual(number_heart_items, 3) + else: + self.assertEqual(number_heart_items, 2) + self.assertEqual(item_names.count("Pet <3"), 1) + + def test_friendsanity_all_with_marriage_locations(self): + prefix = "Friendsanity: " + suffix = " <3" + for location_name in get_real_location_names(self, self.multiworld): + if not location_name.startswith(prefix): + continue + name_no_prefix = location_name[len(prefix):] + name_trimmed = name_no_prefix[:name_no_prefix.index(suffix)] + parts = name_trimmed.split(" ") + name = parts[0] + hearts = int(parts[1]) + self.assertTrue(name in all_villagers_by_mod_by_name[ModNames.vanilla] or name == "Pet") + if name == "Pet": + self.assertTrue(hearts == 5) + elif all_villagers_by_name[name].bachelor: + self.assertTrue(hearts == 5 or hearts == 10 or hearts == 14) + else: + self.assertTrue(hearts == 5 or hearts == 10) diff --git a/worlds/stardew_valley/test/TestItems.py b/worlds/stardew_valley/test/TestItems.py index 8c318184..7f48f934 100644 --- a/worlds/stardew_valley/test/TestItems.py +++ b/worlds/stardew_valley/test/TestItems.py @@ -1,14 +1,17 @@ import itertools import math +import sys import unittest - +import random +from typing import Set from BaseClasses import ItemClassification, MultiWorld +from . import setup_solo_multiworld, SVTestBase from .. import ItemData, StardewValleyWorld -from ..items import Group, ResourcePackData, item_table +from ..items import Group, item_table -class TestItems(unittest.TestCase): +class TestItems(SVTestBase): def test_can_create_item_of_resource_pack(self): item_name = "Resource Pack: 500 Money" @@ -20,77 +23,30 @@ class TestItems(unittest.TestCase): assert item.name == item_name - def test_items_table_footprint_is_between_717000_and_727000(self): + def test_items_table_footprint_is_between_717000_and_737000(self): item_with_lowest_id = min((item for item in item_table.values() if item.code is not None), key=lambda x: x.code) item_with_highest_id = max((item for item in item_table.values() if item.code is not None), key=lambda x: x.code) assert item_with_lowest_id.code >= 717000 - assert item_with_highest_id.code < 727000 + assert item_with_highest_id.code < 737000 + def test_babies_come_in_all_shapes_and_sizes(self): + baby_permutations = set() + for attempt_number in range(50): + if len(baby_permutations) >= 4: + print(f"Already got all 4 baby permutations, breaking early [{attempt_number} generations]") + break + seed = random.randrange(sys.maxsize) + multiworld = setup_solo_multiworld(seed=seed) + baby_items = [item for item in multiworld.get_items() if "Baby" in item.name] + self.assertEqual(len(baby_items), 2) + baby_permutations.add(f"{baby_items[0]} - {baby_items[1]}") + self.assertEqual(len(baby_permutations), 4) -class TestResourcePacks: - def test_can_transform_resource_pack_data_into_idem_data(self): - resource_pack = ResourcePackData("item name", 1, 1, ItemClassification.filler, frozenset()) - - items = resource_pack.as_item_data(itertools.count()) - - assert ItemData(0, "Resource Pack: 1 item name", ItemClassification.filler, {Group.RESOURCE_PACK}) in items - assert ItemData(1, "Resource Pack: 2 item name", ItemClassification.filler, {Group.RESOURCE_PACK}) in items - assert len(items) == 2 - - def test_when_scale_quantity_then_generate_a_possible_quantity_from_minimal_scaling_to_double(self): - resource_pack = ResourcePackData("item name", default_amount=4, scaling_factor=2) - - quantities = resource_pack.scale_quantity.items() - - assert (50, 2) in quantities - assert (100, 4) in quantities - assert (150, 6) in quantities - assert (200, 8) in quantities - assert len(quantities) == (4 / 2) * 2 - - def test_given_scaling_not_multiple_of_default_amount_when_scale_quantity_then_double_is_added_at_200_scaling(self): - resource_pack = ResourcePackData("item name", default_amount=5, scaling_factor=3) - - quantities = resource_pack.scale_quantity.items() - - assert (40, 2) in quantities - assert (100, 5) in quantities - assert (160, 8) in quantities - assert (200, 10) in quantities - assert len(quantities) == math.ceil(5 / 3) * 2 - - def test_given_large_default_amount_multiple_of_scaling_factor_when_scale_quantity_then_scaled_amount_multiple(self): - resource_pack = ResourcePackData("item name", default_amount=500, scaling_factor=50) - - quantities = resource_pack.scale_quantity.items() - - assert (10, 50) in quantities - assert (20, 100) in quantities - assert (30, 150) in quantities - assert (40, 200) in quantities - assert (50, 250) in quantities - assert (60, 300) in quantities - assert (70, 350) in quantities - assert (80, 400) in quantities - assert (90, 450) in quantities - assert (100, 500) in quantities - assert (110, 550) in quantities - assert (120, 600) in quantities - assert (130, 650) in quantities - assert (140, 700) in quantities - assert (150, 750) in quantities - assert (160, 800) in quantities - assert (170, 850) in quantities - assert (180, 900) in quantities - assert (190, 950) in quantities - assert (200, 1000) in quantities - assert len(quantities) == math.ceil(500 / 50) * 2 - - def test_given_smallest_multiplier_possible_when_generate_resource_pack_name_then_quantity_is_not_0(self): - resource_pack = ResourcePackData("item name", default_amount=10, scaling_factor=5) - - name = resource_pack.create_name_from_multiplier(1) - - assert name == "Resource Pack: 5 item name" + def test_correct_number_of_stardrops(self): + seed = random.randrange(sys.maxsize) + allsanity_options = self.allsanity_options_without_mods() + multiworld = setup_solo_multiworld(allsanity_options, seed=seed) + stardrop_items = [item for item in multiworld.get_items() if "Stardrop" in item.name] + self.assertEqual(len(stardrop_items), 5) diff --git a/worlds/stardew_valley/test/TestLogic.py b/worlds/stardew_valley/test/TestLogic.py index e1c5d215..7965d05b 100644 --- a/worlds/stardew_valley/test/TestLogic.py +++ b/worlds/stardew_valley/test/TestLogic.py @@ -22,67 +22,81 @@ class TestLogic(unittest.TestCase): def test_given_bundle_item_then_is_available_in_logic(self): for bundle_item in all_bundle_items_except_money: with self.subTest(msg=bundle_item.item.name): - assert bundle_item.item.name in logic.item_rules + self.assertIn(bundle_item.item.name, logic.item_rules) def test_given_item_rule_then_can_be_resolved(self): for item in logic.item_rules.keys(): with self.subTest(msg=item): rule = logic.item_rules[item] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve item rule for {item} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve item rule for {item} {rule}") def test_given_building_rule_then_can_be_resolved(self): for building in logic.building_rules.keys(): with self.subTest(msg=building): rule = logic.building_rules[building] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve building rule for {building} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve building rule for {building} {rule}") def test_given_quest_rule_then_can_be_resolved(self): for quest in logic.quest_rules.keys(): with self.subTest(msg=quest): rule = logic.quest_rules[quest] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve quest rule for {quest} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve quest rule for {quest} {rule}") + + def test_given_special_order_rule_then_can_be_resolved(self): + for special_order in logic.special_order_rules.keys(): + with self.subTest(msg=special_order): + rule = logic.special_order_rules[special_order] + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve special order rule for {special_order} {rule}") def test_given_tree_fruit_rule_then_can_be_resolved(self): for tree_fruit in logic.tree_fruit_rules.keys(): with self.subTest(msg=tree_fruit): rule = logic.tree_fruit_rules[tree_fruit] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve tree fruit rule for {tree_fruit} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve tree fruit rule for {tree_fruit} {rule}") def test_given_seed_rule_then_can_be_resolved(self): for seed in logic.seed_rules.keys(): with self.subTest(msg=seed): rule = logic.seed_rules[seed] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve seed rule for {seed} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve seed rule for {seed} {rule}") def test_given_crop_rule_then_can_be_resolved(self): for crop in logic.crop_rules.keys(): with self.subTest(msg=crop): rule = logic.crop_rules[crop] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve crop rule for {crop} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve crop rule for {crop} {rule}") def test_given_fish_rule_then_can_be_resolved(self): for fish in logic.fish_rules.keys(): with self.subTest(msg=fish): rule = logic.fish_rules[fish] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve fish rule for {fish} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve fish rule for {fish} {rule}") def test_given_museum_rule_then_can_be_resolved(self): for donation in logic.museum_rules.keys(): with self.subTest(msg=donation): rule = logic.museum_rules[donation] - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve museum rule for {donation} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve museum rule for {donation} {rule}") + + def test_given_cooking_rule_then_can_be_resolved(self): + for cooking_rule in logic.cooking_rules.keys(): + with self.subTest(msg=cooking_rule): + rule = logic.cooking_rules[cooking_rule] + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve cooking rule for {cooking_rule} {rule}") def test_given_location_rule_then_can_be_resolved(self): for location in multi_world.get_locations(1): with self.subTest(msg=location.name): rule = location.access_rule - assert MISSING_ITEM not in repr(rule) - assert rule == False_() or rule(multi_world.state), f"Could not resolve location rule for {location} {rule}" + self.assertNotIn(MISSING_ITEM, repr(rule)) + self.assertTrue(rule == False_() or rule(multi_world.state), f"Could not resolve location rule for {location} {rule}") diff --git a/worlds/stardew_valley/test/TestLogicSimplification.py b/worlds/stardew_valley/test/TestLogicSimplification.py index c37f5782..83d779ce 100644 --- a/worlds/stardew_valley/test/TestLogicSimplification.py +++ b/worlds/stardew_valley/test/TestLogicSimplification.py @@ -55,11 +55,3 @@ def test_simplify_true_in_or(): def test_simplify_false_in_and(): rule = And(False_(), Received('Summer', 0, 1)) assert rule.simplify() == False_() - - -def test_simplify_coffee(): - logic = StardewLogic(1, StardewOptions(default_options)) - - simplified_coffee = logic.has("Coffee").simplify() - - assert simplified_coffee == True_() diff --git a/worlds/stardew_valley/test/TestMods.py b/worlds/stardew_valley/test/TestMods.py new file mode 100644 index 00000000..0dc66c3f --- /dev/null +++ b/worlds/stardew_valley/test/TestMods.py @@ -0,0 +1,192 @@ +from typing import List, Union +import unittest +import random +import sys + +from BaseClasses import MultiWorld +from . import setup_solo_multiworld +from .TestOptions import basic_checks, SVTestBase +from .. import options, locations, items, Group, ItemClassification, StardewOptions +from ..regions import RandomizationFlag, create_final_connections, randomize_connections, create_final_regions +from ..items import item_table, items_by_group +from ..locations import location_table, LocationTags +from ..options import stardew_valley_option_classes, Mods, EntranceRandomization + +mod_list = ["DeepWoods", "Tractor Mod", "Bigger Backpack", + "Luck Skill", "Magic", "Socializing Skill", "Archaeology", + "Cooking Skill", "Binning Skill", "Juna - Roommate NPC", + "Professor Jasper Thomas", "Alec Revisited", "Custom NPC - Yoba", "Custom NPC Eugene", + "'Prophet' Wellwick", "Mister Ginger (cat npc)", "Shiko - New Custom NPC", "Delores - Custom NPC", + "Ayeisha - The Postal Worker (Custom NPC)", "Custom NPC - Riley", "Skull Cavern Elevator"] + + +def check_stray_mod_items(chosen_mods: Union[List[str], str], tester: SVTestBase, multiworld: MultiWorld): + if isinstance(chosen_mods, str): + chosen_mods = [chosen_mods] + for multiworld_item in multiworld.get_items(): + item = item_table[multiworld_item.name] + tester.assertTrue(item.mod_name is None or item.mod_name in chosen_mods) + for multiworld_location in multiworld.get_locations(): + if multiworld_location.event: + continue + location = location_table[multiworld_location.name] + tester.assertTrue(location.mod_name is None or location.mod_name in chosen_mods) + + +class TestGenerateModsOptions(SVTestBase): + + def test_given_single_mods_when_generate_then_basic_checks(self): + for mod in mod_list: + with self.subTest(f"Mod: {mod}"): + multi_world = setup_solo_multiworld({Mods: mod}) + basic_checks(self, multi_world) + check_stray_mod_items(mod, self, multi_world) + + def test_given_mod_pairs_when_generate_then_basic_checks(self): + if self.skip_long_tests: + return + num_mods = len(mod_list) + for mod1_index in range(0, num_mods): + for mod2_index in range(mod1_index + 1, num_mods): + mod1 = mod_list[mod1_index] + mod2 = mod_list[mod2_index] + mods = (mod1, mod2) + with self.subTest(f"Mods: {mods}"): + multiworld = setup_solo_multiworld({Mods: mods}) + basic_checks(self, multiworld) + check_stray_mod_items(list(mods), self, multiworld) + + def test_given_mod_names_when_generate_paired_with_entrance_randomizer_then_basic_checks(self): + for option in EntranceRandomization.options: + for mod in mod_list: + with self.subTest(f"entrance_randomization: {option}, Mod: {mod}"): + multiworld = setup_solo_multiworld({EntranceRandomization.internal_name: option, Mods: mod}) + basic_checks(self, multiworld) + check_stray_mod_items(mod, self, multiworld) + + def test_given_mod_names_when_generate_paired_with_other_options_then_basic_checks(self): + if self.skip_long_tests: + return + for option in stardew_valley_option_classes: + if not option.options: + continue + for value in option.options: + for mod in mod_list: + with self.subTest(f"{option.internal_name}: {value}, Mod: {mod}"): + multiworld = setup_solo_multiworld({option.internal_name: option.options[value], Mods: mod}) + basic_checks(self, multiworld) + check_stray_mod_items(mod, self, multiworld) + + +class TestGivenModdedProgressiveBackpack(SVTestBase): + options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.Mods.internal_name: "Bigger Backpack"} + + def test_when_generate_world_then_three_progressive_backpack_are_added(self): + self.assertEqual(self.multiworld.itempool.count(self.world.create_item("Progressive Backpack")), 3) + + def test_when_generate_world_then_all_backpack_locations_are_added(self): + created_locations = {location.name for location in self.multiworld.get_locations(1)} + backpacks_exist = [location.name in created_locations + for location in locations.locations_by_tag[LocationTags.BACKPACK]] + all_exist = all(backpacks_exist) + self.assertTrue(all_exist) + + +class TestBaseItemGeneration(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.Mods.internal_name: mod_list + } + + def test_all_progression_items_are_added_to_the_pool(self): + all_created_items = [item.name for item in self.multiworld.itempool] + # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression + items_to_ignore = [event.name for event in items.events] + items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) + items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) + items_to_ignore.extend(footwear.name for footwear in items.items_by_group[Group.FOOTWEAR]) + items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) + items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) + progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression + and item.name not in items_to_ignore] + for progression_item in progression_items: + with self.subTest(f"{progression_item.name}"): + self.assertIn(progression_item.name, all_created_items) + + +class TestNoGingerIslandModItemGeneration(SVTestBase): + options = { + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + options.Mods.internal_name: mod_list + } + + def test_all_progression_items_except_island_are_added_to_the_pool(self): + all_created_items = [item.name for item in self.multiworld.itempool] + # Ignore all the stuff that the algorithm chooses one of, instead of all, to fulfill logical progression + items_to_ignore = [event.name for event in items.events] + items_to_ignore.extend(season.name for season in items.items_by_group[Group.SEASON]) + items_to_ignore.extend(weapon.name for weapon in items.items_by_group[Group.WEAPON]) + items_to_ignore.extend(footwear.name for footwear in items.items_by_group[Group.FOOTWEAR]) + items_to_ignore.extend(baby.name for baby in items.items_by_group[Group.BABY]) + items_to_ignore.extend(resource_pack.name for resource_pack in items.items_by_group[Group.RESOURCE_PACK]) + progression_items = [item for item in items.all_items if item.classification is ItemClassification.progression + and item.name not in items_to_ignore] + for progression_item in progression_items: + with self.subTest(f"{progression_item.name}"): + if Group.GINGER_ISLAND in progression_item.groups: + self.assertNotIn(progression_item.name, all_created_items) + else: + self.assertIn(progression_item.name, all_created_items) + + +class TestModEntranceRando(unittest.TestCase): + + def test_mod_entrance_randomization(self): + + for option, flag in [(options.EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), + (options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), + (options.EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: + with self.subTest(option=option, flag=flag): + seed = random.randrange(sys.maxsize) + rand = random.Random(seed) + world_options = StardewOptions({options.EntranceRandomization.internal_name: option, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.Mods.internal_name: mod_list}) + final_regions = create_final_regions(world_options) + final_connections = create_final_connections(world_options) + + regions_by_name = {region.name: region for region in final_regions} + _, randomized_connections = randomize_connections(rand, world_options, regions_by_name) + + for connection in final_connections: + if flag in connection.flag: + connection_in_randomized = connection.name in randomized_connections + reverse_in_randomized = connection.reverse in randomized_connections + self.assertTrue(connection_in_randomized, + f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}") + self.assertTrue(reverse_in_randomized, + f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}") + + self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), + f"Connections are duplicated in randomization. Seed = {seed}") + + +class TestModTraps(SVTestBase): + def test_given_traps_when_generate_then_all_traps_in_pool(self): + trap_option = options.TrapItems + for value in trap_option.options: + if value == "no_traps": + continue + world_options = self.allsanity_options_without_mods() + world_options.update({options.TrapItems.internal_name: trap_option.options[value], Mods: "Magic"}) + multi_world = setup_solo_multiworld(world_options) + trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if Group.DEPRECATED not in item_data.groups] + multiworld_items = [item.name for item in multi_world.get_items()] + for item in trap_items: + with self.subTest(f"Option: {value}, Item: {item}"): + self.assertIn(item, multiworld_items) diff --git a/worlds/stardew_valley/test/TestOptions.py b/worlds/stardew_valley/test/TestOptions.py index 2d946e2f..d3e9848a 100644 --- a/worlds/stardew_valley/test/TestOptions.py +++ b/worlds/stardew_valley/test/TestOptions.py @@ -1,28 +1,52 @@ import itertools import unittest +from random import random +from typing import Dict from BaseClasses import ItemClassification, MultiWorld -from Options import SpecialRange +from Options import SpecialRange, OptionSet from . import setup_solo_multiworld, SVTestBase -from .. import StardewItem, options -from ..options import StardewOption, stardew_valley_option_classes +from .. import StardewItem, options, items_by_group, Group +from ..locations import locations_by_tag, LocationTags, location_table +from ..options import StardewOption, stardew_valley_option_classes, Mods +from ..strings.goal_names import Goal +from ..strings.season_names import Season +from ..strings.special_order_names import SpecialOrder +from ..strings.tool_names import ToolMaterial, Tool -SEASONS = {"Spring", "Summer", "Fall", "Winter"} +SEASONS = {Season.spring, Season.summer, Season.fall, Season.winter} TOOLS = {"Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can", "Fishing Rod"} -def assert_can_win(multiworld: MultiWorld): +def assert_can_win(tester: SVTestBase, multiworld: MultiWorld): for item in multiworld.get_items(): multiworld.state.collect(item) - assert multiworld.find_item("Victory", 1).can_reach(multiworld.state) + tester.assertTrue(multiworld.find_item("Victory", 1).can_reach(multiworld.state)) -def basic_checks(multiworld: MultiWorld): - assert StardewItem("Victory", ItemClassification.progression, None, 1) in multiworld.get_items() - assert_can_win(multiworld) - assert len(multiworld.itempool) == len( - [location for location in multiworld.get_locations() if not location.event]) +def basic_checks(tester: SVTestBase, multiworld: MultiWorld): + tester.assertIn(StardewItem("Victory", ItemClassification.progression, None, 1), multiworld.get_items()) + assert_can_win(tester, multiworld) + non_event_locations = [location for location in multiworld.get_locations() if not location.event] + tester.assertEqual(len(multiworld.itempool), len(non_event_locations)) + + +def check_no_ginger_island(tester: SVTestBase, multiworld: MultiWorld): + ginger_island_items = [item_data.name for item_data in items_by_group[Group.GINGER_ISLAND]] + ginger_island_locations = [location_data.name for location_data in locations_by_tag[LocationTags.GINGER_ISLAND]] + for item in multiworld.get_items(): + tester.assertNotIn(item.name, ginger_island_items) + for location in multiworld.get_locations(): + tester.assertNotIn(location.name, ginger_island_locations) + + +def get_option_choices(option) -> Dict[str, int]: + if issubclass(option, SpecialRange): + return option.special_range_names + elif option.options: + return option.options + return {} class TestGenerateDynamicOptions(SVTestBase): @@ -30,45 +54,39 @@ class TestGenerateDynamicOptions(SVTestBase): for option in stardew_valley_option_classes: if not issubclass(option, SpecialRange): continue - with self.subTest(msg=option.internal_name): - for value in option.special_range_names: - multiworld = setup_solo_multiworld({option.internal_name: option.special_range_names[value]}) - basic_checks(multiworld) + for value in option.special_range_names: + with self.subTest(f"{option.internal_name}: {value}"): + choices = {option.internal_name: option.special_range_names[value]} + multiworld = setup_solo_multiworld(choices) + basic_checks(self, multiworld) def test_given_choice_when_generate_then_basic_checks(self): + seed = int(random() * pow(10, 18) - 1) for option in stardew_valley_option_classes: if not option.options: continue - with self.subTest(msg=option.internal_name): - for value in option.options: - multiworld = setup_solo_multiworld({option.internal_name: option.options[value]}) - basic_checks(multiworld) - - def test_given_option_combination_when_generate_then_basic_checks(self): - option_combinations = [{options.Goal.internal_name: options.Goal.option_master_angler, - options.ToolProgression.internal_name: options.ToolProgression.option_vanilla}] - ids = ["Master Angler + Vanilla tools"] - - for i in range(0, len(option_combinations)): - option_combination = option_combinations[i] - id = ids[i] - with self.subTest(msg=f"{id}"): - multi_world = setup_solo_multiworld(option_combination) - basic_checks(multi_world) + for value in option.options: + with self.subTest(f"{option.internal_name}: {value} [Seed: {seed}]"): + choices = {option.internal_name: option.options[value]} + multiworld = setup_solo_multiworld(choices, seed) + basic_checks(self, multiworld) class TestGoal(SVTestBase): def test_given_goal_when_generate_then_victory_is_in_correct_location(self): - for goal, location in [("community_center", "Complete Community Center"), - ("grandpa_evaluation", "Succeed Grandpa's Evaluation"), - ("bottom_of_the_mines", "Reach the Bottom of The Mines"), - ("cryptic_note", "Complete Quest Cryptic Note"), - ("master_angler", "Catch Every Fish")]: + for goal, location in [("community_center", Goal.community_center), + ("grandpa_evaluation", Goal.grandpa_evaluation), + ("bottom_of_the_mines", Goal.bottom_of_the_mines), + ("cryptic_note", Goal.cryptic_note), + ("master_angler", Goal.master_angler), + ("complete_collection", Goal.complete_museum), + ("full_house", Goal.full_house), + ("perfection", Goal.perfection)]: with self.subTest(msg=f"Goal: {goal}, Location: {location}"): world_options = {options.Goal.internal_name: options.Goal.options[goal]} multi_world = setup_solo_multiworld(world_options) victory = multi_world.find_item("Victory", 1) - assert victory.name == location + self.assertEqual(victory.name, location) class TestSeasonRandomization(SVTestBase): @@ -77,22 +95,22 @@ class TestSeasonRandomization(SVTestBase): multi_world = setup_solo_multiworld(world_options) precollected_items = {item.name for item in multi_world.precollected_items[1]} - assert all([season in precollected_items for season in SEASONS]) + self.assertTrue(all([season in precollected_items for season in SEASONS])) def test_given_randomized_when_generate_then_all_seasons_are_in_the_pool_or_precollected(self): world_options = {options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized} multi_world = setup_solo_multiworld(world_options) precollected_items = {item.name for item in multi_world.precollected_items[1]} items = {item.name for item in multi_world.get_items()} | precollected_items - assert all([season in items for season in SEASONS]) - assert len(SEASONS.intersection(precollected_items)) == 1 + self.assertTrue(all([season in items for season in SEASONS])) + self.assertEqual(len(SEASONS.intersection(precollected_items)), 1) def test_given_progressive_when_generate_then_3_progressive_seasons_are_in_the_pool(self): world_options = {options.SeasonRandomization.internal_name: options.SeasonRandomization.option_progressive} multi_world = setup_solo_multiworld(world_options) items = [item.name for item in multi_world.get_items()] - assert items.count("Progressive Season") == 3 + self.assertEqual(items.count(Season.progressive), 3) class TestBackpackProgression(SVTestBase): @@ -106,21 +124,22 @@ class TestBackpackProgression(SVTestBase): world_options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive} multi_world = setup_solo_multiworld(world_options) items = [item.name for item in multi_world.get_items()] - assert items.count("Progressive Backpack") == 2 + self.assertEqual(items.count("Progressive Backpack"), 2) def test_given_progressive_when_generate_then_backpack_upgrades_are_locations(self): world_options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive} multi_world = setup_solo_multiworld(world_options) locations = {locations.name for locations in multi_world.get_locations(1)} - assert "Large Pack" in locations - assert "Deluxe Pack" in locations + self.assertIn("Large Pack", locations) + self.assertIn("Deluxe Pack", locations) def test_given_early_progressive_when_generate_then_progressive_backpack_is_in_early_pool(self): - world_options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive} + world_options = { + options.BackpackProgression.internal_name: options.BackpackProgression.option_early_progressive} multi_world = setup_solo_multiworld(world_options) - assert "Progressive Backpack" in multi_world.early_items[1] + self.assertIn("Progressive Backpack", multi_world.early_items[1]) class TestToolProgression(SVTestBase): @@ -130,25 +149,147 @@ class TestToolProgression(SVTestBase): items = {item.name for item in multi_world.get_items()} for tool in TOOLS: - assert tool not in items + self.assertNotIn(tool, items) def test_given_progressive_when_generate_then_progressive_tool_of_each_is_in_pool_four_times(self): - world_options = {options.ToolProgression.internal_name:options.ToolProgression.option_progressive} + world_options = {options.ToolProgression.internal_name: options.ToolProgression.option_progressive} multi_world = setup_solo_multiworld(world_options) items = [item.name for item in multi_world.get_items()] for tool in TOOLS: - assert items.count("Progressive " + tool) == 4 + self.assertEqual(items.count("Progressive " + tool), 4) def test_given_progressive_when_generate_then_tool_upgrades_are_locations(self): world_options = {options.ToolProgression.internal_name: options.ToolProgression.option_progressive} multi_world = setup_solo_multiworld(world_options) locations = {locations.name for locations in multi_world.get_locations(1)} - for material, tool in itertools.product(["Copper", "Iron", "Gold", "Iridium"], - ["Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can"]): - assert f"{material} {tool} Upgrade" in locations - assert "Purchase Training Rod" in locations - assert "Bamboo Pole Cutscene" in locations - assert "Purchase Fiberglass Rod" in locations - assert "Purchase Iridium Rod" in locations + for material, tool in itertools.product(ToolMaterial.tiers.values(), + [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can]): + if material == ToolMaterial.basic: + continue + self.assertIn(f"{material} {tool} Upgrade", locations) + self.assertIn("Purchase Training Rod", locations) + self.assertIn("Bamboo Pole Cutscene", locations) + self.assertIn("Purchase Fiberglass Rod", locations) + self.assertIn("Purchase Iridium Rod", locations) + + +class TestGenerateAllOptionsWithExcludeGingerIsland(SVTestBase): + def test_given_special_range_when_generate_exclude_ginger_island(self): + for option in stardew_valley_option_classes: + if not issubclass(option, + SpecialRange) or option.internal_name == options.ExcludeGingerIsland.internal_name: + continue + for value in option.special_range_names: + with self.subTest(f"{option.internal_name}: {value}"): + multiworld = setup_solo_multiworld( + {options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true, + option.internal_name: option.special_range_names[value]}) + check_no_ginger_island(self, multiworld) + + def test_given_choice_when_generate_exclude_ginger_island(self): + seed = int(random() * pow(10, 18) - 1) + island_option = options.ExcludeGingerIsland + for option in stardew_valley_option_classes: + if not option.options or option.internal_name == island_option.internal_name: + continue + for value in option.options: + with self.subTest(f"{option.internal_name}: {value} [Seed: {seed}]"): + multiworld = setup_solo_multiworld( + {island_option.internal_name: island_option.option_true, + option.internal_name: option.options[value]}, seed) + if multiworld.worlds[self.player].options[island_option.internal_name] != island_option.option_true: + continue + basic_checks(self, multiworld) + check_no_ginger_island(self, multiworld) + + def test_given_island_related_goal_then_override_exclude_ginger_island(self): + island_goals = [value for value in options.Goal.options if value in ["walnut_hunter", "perfection"]] + island_option = options.ExcludeGingerIsland + for goal in island_goals: + for value in island_option.options: + with self.subTest(f"Goal: {goal}, {island_option.internal_name}: {value}"): + multiworld = setup_solo_multiworld( + {options.Goal.internal_name: options.Goal.options[goal], + island_option.internal_name: island_option.options[value]}) + self.assertEqual(multiworld.worlds[self.player].options[island_option.internal_name], island_option.option_false) + basic_checks(self, multiworld) + + +class TestTraps(SVTestBase): + def test_given_no_traps_when_generate_then_no_trap_in_pool(self): + world_options = self.allsanity_options_without_mods() + world_options.update({options.TrapItems.internal_name: options.TrapItems.option_no_traps}) + multi_world = setup_solo_multiworld(world_options) + + trap_items = [item_data.name for item_data in items_by_group[Group.TRAP]] + multiworld_items = [item.name for item in multi_world.get_items()] + + for item in trap_items: + with self.subTest(f"{item}"): + self.assertNotIn(item, multiworld_items) + + def test_given_traps_when_generate_then_all_traps_in_pool(self): + trap_option = options.TrapItems + for value in trap_option.options: + if value == "no_traps": + continue + world_options = self.allsanity_options_with_mods() + world_options.update({options.TrapItems.internal_name: trap_option.options[value]}) + multi_world = setup_solo_multiworld(world_options) + trap_items = [item_data.name for item_data in items_by_group[Group.TRAP] if Group.DEPRECATED not in item_data.groups and item_data.mod_name is None] + multiworld_items = [item.name for item in multi_world.get_items()] + for item in trap_items: + with self.subTest(f"Option: {value}, Item: {item}"): + self.assertIn(item, multiworld_items) + + +class TestSpecialOrders(SVTestBase): + def test_given_disabled_then_no_order_in_pool(self): + world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_disabled} + multi_world = setup_solo_multiworld(world_options) + + locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table} + for location_name in locations_in_pool: + location = location_table[location_name] + self.assertNotIn(LocationTags.SPECIAL_ORDER_BOARD, location.tags) + self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags) + + def test_given_board_only_then_no_qi_order_in_pool(self): + world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_only} + multi_world = setup_solo_multiworld(world_options) + + locations_in_pool = {location.name for location in multi_world.get_locations() if location.name in location_table} + for location_name in locations_in_pool: + location = location_table[location_name] + self.assertNotIn(LocationTags.SPECIAL_ORDER_QI, location.tags) + + for board_location in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: + if board_location.mod_name: + continue + self.assertIn(board_location.name, locations_in_pool) + + def test_given_board_and_qi_then_all_orders_in_pool(self): + world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_victories} + multi_world = setup_solo_multiworld(world_options) + + locations_in_pool = {location.name for location in multi_world.get_locations()} + for qi_location in locations_by_tag[LocationTags.SPECIAL_ORDER_QI]: + if qi_location.mod_name: + continue + self.assertIn(qi_location.name, locations_in_pool) + + for board_location in locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: + if board_location.mod_name: + continue + self.assertIn(board_location.name, locations_in_pool) + + def test_given_board_and_qi_without_arcade_machines_then_lets_play_a_game_not_in_pool(self): + world_options = {options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled} + multi_world = setup_solo_multiworld(world_options) + + locations_in_pool = {location.name for location in multi_world.get_locations()} + self.assertNotIn(SpecialOrder.lets_play_a_game, locations_in_pool) diff --git a/worlds/stardew_valley/test/TestRegions.py b/worlds/stardew_valley/test/TestRegions.py index 3aadc7e4..8045e336 100644 --- a/worlds/stardew_valley/test/TestRegions.py +++ b/worlds/stardew_valley/test/TestRegions.py @@ -3,44 +3,82 @@ import sys import unittest from .. import StardewOptions, options -from ..regions import stardew_valley_regions, mandatory_connections, randomize_connections, RandomizationFlag +from ..regions import vanilla_regions, vanilla_connections, randomize_connections, RandomizationFlag -connections_by_name = {connection.name for connection in mandatory_connections} -regions_by_name = {region.name for region in stardew_valley_regions} +connections_by_name = {connection.name for connection in vanilla_connections} +regions_by_name = {region.name for region in vanilla_regions} class TestRegions(unittest.TestCase): def test_region_exits_lead_somewhere(self): - for region in stardew_valley_regions: + for region in vanilla_regions: with self.subTest(region=region): for exit in region.exits: - assert exit in connections_by_name, f"{region.name} is leading to {exit} but it does not exist." + self.assertIn(exit, connections_by_name, + f"{region.name} is leading to {exit} but it does not exist.") def test_connection_lead_somewhere(self): - for connection in mandatory_connections: + for connection in vanilla_connections: with self.subTest(connection=connection): - assert connection.destination in regions_by_name, \ - f"{connection.name} is leading to {connection.destination} but it does not exist." + self.assertIn(connection.destination, regions_by_name, + f"{connection.name} is leading to {connection.destination} but it does not exist.") class TestEntranceRando(unittest.TestCase): - def test_pelican_town_entrance_randomization(self): + def test_entrance_randomization(self): for option, flag in [(options.EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), - (options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION)]: + (options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), + (options.EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: + # option = options.EntranceRandomization.option_buildings + # flag = RandomizationFlag.BUILDINGS + # for i in range(0, 100000): + seed = random.randrange(sys.maxsize) + with self.subTest(flag=flag, msg=f"Seed: {seed}"): + rand = random.Random(seed) + world_options = StardewOptions({options.EntranceRandomization.internal_name: option, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false}) + regions_by_name = {region.name: region for region in vanilla_regions} + + _, randomized_connections = randomize_connections(rand, world_options, regions_by_name) + + for connection in vanilla_connections: + if flag in connection.flag: + connection_in_randomized = connection.name in randomized_connections + reverse_in_randomized = connection.reverse in randomized_connections + self.assertTrue(connection_in_randomized, + f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}") + self.assertTrue(reverse_in_randomized, + f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}") + + self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), + f"Connections are duplicated in randomization. Seed = {seed}") + + def test_entrance_randomization_without_island(self): + for option, flag in [(options.EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN), + (options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION), + (options.EntranceRandomization.option_buildings, RandomizationFlag.BUILDINGS)]: with self.subTest(option=option, flag=flag): seed = random.randrange(sys.maxsize) rand = random.Random(seed) - world_options = StardewOptions({options.EntranceRandomization.internal_name: option}) + world_options = StardewOptions({options.EntranceRandomization.internal_name: option, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_true}) + regions_by_name = {region.name: region for region in vanilla_regions} - _, randomized_connections = randomize_connections(rand, world_options) + _, randomized_connections = randomize_connections(rand, world_options, regions_by_name) - for connection in mandatory_connections: + for connection in vanilla_connections: if flag in connection.flag: - assert connection.name in randomized_connections, \ - f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}" - assert connection.reverse in randomized_connections, \ - f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}" + if RandomizationFlag.GINGER_ISLAND in connection.flag: + self.assertNotIn(connection.name, randomized_connections, + f"Connection {connection.name} should not be randomized but it is in the output. Seed = {seed}") + self.assertNotIn(connection.reverse, randomized_connections, + f"Connection {connection.reverse} should not be randomized but it is in the output. Seed = {seed}") + else: + self.assertIn(connection.name, randomized_connections, + f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}") + self.assertIn(connection.reverse, randomized_connections, + f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}") - assert len(set(randomized_connections.values())) == len( - randomized_connections.values()), f"Connections are duplicated in randomization. Seed = {seed}" + self.assertEqual(len(set(randomized_connections.values())), len(randomized_connections.values()), + f"Connections are duplicated in randomization. Seed = {seed}") diff --git a/worlds/stardew_valley/test/TestRules.py b/worlds/stardew_valley/test/TestRules.py index 19638012..be7105aa 100644 --- a/worlds/stardew_valley/test/TestRules.py +++ b/worlds/stardew_valley/test/TestRules.py @@ -2,6 +2,16 @@ from collections import Counter from . import SVTestBase from .. import options +from ..strings.animal_names import Animal +from ..strings.animal_product_names import AnimalProduct +from ..strings.artisan_good_names import ArtisanGood +from ..strings.crop_names import Vegetable +from ..strings.food_names import Meal +from ..strings.ingredient_names import Ingredient +from ..strings.machine_names import Machine +from ..strings.region_names import Region +from ..strings.season_names import Season +from ..strings.seed_names import Seed class TestProgressiveToolsLogic(SVTestBase): @@ -15,67 +25,71 @@ class TestProgressiveToolsLogic(SVTestBase): self.multiworld.state.prog_items = Counter() def test_sturgeon(self): - assert not self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertFalse(self.world.logic.has("Sturgeon")(self.multiworld.state)) summer = self.world.create_item("Summer") self.multiworld.state.collect(summer, event=True) - assert not self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertFalse(self.world.logic.has("Sturgeon")(self.multiworld.state)) fishing_rod = self.world.create_item("Progressive Fishing Rod") self.multiworld.state.collect(fishing_rod, event=True) self.multiworld.state.collect(fishing_rod, event=True) - assert not self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertFalse(self.world.logic.has("Sturgeon")(self.multiworld.state)) fishing_level = self.world.create_item("Fishing Level") self.multiworld.state.collect(fishing_level, event=True) - assert not self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertFalse(self.world.logic.has("Sturgeon")(self.multiworld.state)) self.multiworld.state.collect(fishing_level, event=True) self.multiworld.state.collect(fishing_level, event=True) self.multiworld.state.collect(fishing_level, event=True) self.multiworld.state.collect(fishing_level, event=True) self.multiworld.state.collect(fishing_level, event=True) - assert self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertTrue(self.world.logic.has("Sturgeon")(self.multiworld.state)) self.remove(summer) - assert not self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertFalse(self.world.logic.has("Sturgeon")(self.multiworld.state)) winter = self.world.create_item("Winter") self.multiworld.state.collect(winter, event=True) - assert self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertTrue(self.world.logic.has("Sturgeon")(self.multiworld.state)) self.remove(fishing_rod) - assert not self.world.logic.has("Sturgeon")(self.multiworld.state) + self.assertFalse(self.world.logic.has("Sturgeon")(self.multiworld.state)) def test_old_master_cannoli(self): self.multiworld.state.collect(self.world.create_item("Progressive Axe"), event=True) self.multiworld.state.collect(self.world.create_item("Progressive Axe"), event=True) self.multiworld.state.collect(self.world.create_item("Summer"), event=True) - assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) fall = self.world.create_item("Fall") self.multiworld.state.collect(fall, event=True) - assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) tuesday = self.world.create_item("Traveling Merchant: Tuesday") self.multiworld.state.collect(tuesday, event=True) - assert self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) + + rare_seed = self.world.create_item("Rare Seed") + self.multiworld.state.collect(rare_seed, event=True) + self.assertTrue(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) self.remove(fall) - assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) self.remove(tuesday) green_house = self.world.create_item("Greenhouse") self.multiworld.state.collect(green_house, event=True) - assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) friday = self.world.create_item("Traveling Merchant: Friday") self.multiworld.state.collect(friday, event=True) - assert self.multiworld.get_location("Old Master Cannoli", 1).access_rule(self.multiworld.state) + self.assertTrue(self.multiworld.get_location("Old Master Cannoli", 1).access_rule(self.multiworld.state)) self.remove(green_house) - assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)) self.remove(friday) @@ -84,7 +98,7 @@ class TestBundlesLogic(SVTestBase): } def test_vault_2500g_bundle(self): - assert self.world.logic.can_reach_location("2,500g Bundle")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_location("2,500g Bundle")(self.multiworld.state)) class TestBuildingLogic(SVTestBase): @@ -93,27 +107,27 @@ class TestBuildingLogic(SVTestBase): } def test_coop_blueprint(self): - assert not self.world.logic.can_reach_location("Coop Blueprint")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Coop Blueprint")(self.multiworld.state)) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) - assert self.world.logic.can_reach_location("Coop Blueprint")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_location("Coop Blueprint")(self.multiworld.state)) def test_big_coop_blueprint(self): - assert not self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), \ - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}" + self.assertFalse(self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), + f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) - assert not self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), \ - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}" + self.assertFalse(self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), + f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") self.multiworld.state.collect(self.world.create_item("Progressive Coop"), event=True) - assert self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), \ - f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}" + self.assertTrue(self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), + f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}") def test_deluxe_coop_blueprint(self): - assert not self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) @@ -122,17 +136,17 @@ class TestBuildingLogic(SVTestBase): self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) - assert not self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) self.multiworld.state.collect(self.world.create_item("Progressive Coop"), event=True) - assert not self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) self.multiworld.state.collect(self.world.create_item("Progressive Coop"), event=True) - assert self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)) def test_big_shed_blueprint(self): - assert not self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), \ - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}" + self.assertFalse(self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), + f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) @@ -140,12 +154,12 @@ class TestBuildingLogic(SVTestBase): self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) self.multiworld.state.collect(self.world.create_item("Month End"), event=True) - assert not self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), \ - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}" + self.assertFalse(self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), + f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") self.multiworld.state.collect(self.world.create_item("Progressive Shed"), event=True) - assert self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), \ - f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}" + self.assertTrue(self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), + f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}") class TestArcadeMachinesLogic(SVTestBase): @@ -154,10 +168,10 @@ class TestArcadeMachinesLogic(SVTestBase): } def test_prairie_king(self): - assert not self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state) - assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) boots = self.world.create_item("JotPK: Progressive Boots") gun = self.world.create_item("JotPK: Progressive Gun") @@ -167,19 +181,19 @@ class TestArcadeMachinesLogic(SVTestBase): self.multiworld.state.collect(boots, event=True) self.multiworld.state.collect(gun, event=True) - assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state) - assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) self.remove(boots) self.remove(gun) self.multiworld.state.collect(boots, event=True) self.multiworld.state.collect(boots, event=True) - assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state) - assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) self.remove(boots) self.remove(boots) @@ -187,10 +201,10 @@ class TestArcadeMachinesLogic(SVTestBase): self.multiworld.state.collect(gun, event=True) self.multiworld.state.collect(ammo, event=True) self.multiworld.state.collect(life, event=True) - assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state) - assert self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state) - assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state) - assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) self.remove(boots) self.remove(gun) self.remove(ammo) @@ -203,10 +217,10 @@ class TestArcadeMachinesLogic(SVTestBase): self.multiworld.state.collect(ammo, event=True) self.multiworld.state.collect(life, event=True) self.multiworld.state.collect(drop, event=True) - assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state) - assert self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state) - assert self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state) - assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)) + self.assertFalse(self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) self.remove(boots) self.remove(gun) self.remove(gun) @@ -226,10 +240,10 @@ class TestArcadeMachinesLogic(SVTestBase): self.multiworld.state.collect(ammo, event=True) self.multiworld.state.collect(life, event=True) self.multiworld.state.collect(drop, event=True) - assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state) - assert self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state) - assert self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state) - assert self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)) + self.assertTrue(self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)) + self.assertTrue(self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)) self.remove(boots) self.remove(boots) self.remove(gun) @@ -282,28 +296,76 @@ class TestWeaponsLogic(SVTestBase): item = self.multiworld.create_item(item_name, self.player) self.multiworld.state.collect(item, event=True) if reachable_level > 0: - assert self.world.logic.can_mine_in_the_mines_floor_1_40()(self.multiworld.state) + self.assertTrue(self.world.logic.can_mine_in_the_mines_floor_1_40()(self.multiworld.state)) else: - assert not self.world.logic.can_mine_in_the_mines_floor_1_40()(self.multiworld.state) + self.assertFalse(self.world.logic.can_mine_in_the_mines_floor_1_40()(self.multiworld.state)) if reachable_level > 1: - assert self.world.logic.can_mine_in_the_mines_floor_41_80()(self.multiworld.state) + self.assertTrue(self.world.logic.can_mine_in_the_mines_floor_41_80()(self.multiworld.state)) else: - assert not self.world.logic.can_mine_in_the_mines_floor_41_80()(self.multiworld.state) + self.assertFalse(self.world.logic.can_mine_in_the_mines_floor_41_80()(self.multiworld.state)) if reachable_level > 2: - assert self.world.logic.can_mine_in_the_mines_floor_81_120()(self.multiworld.state) + self.assertTrue(self.world.logic.can_mine_in_the_mines_floor_81_120()(self.multiworld.state)) else: - assert not self.world.logic.can_mine_in_the_mines_floor_81_120()(self.multiworld.state) + self.assertFalse(self.world.logic.can_mine_in_the_mines_floor_81_120()(self.multiworld.state)) if reachable_level > 3: - assert self.world.logic.can_mine_in_the_skull_cavern()(self.multiworld.state) + self.assertTrue(self.world.logic.can_mine_in_the_skull_cavern()(self.multiworld.state)) else: - assert not self.world.logic.can_mine_in_the_skull_cavern()(self.multiworld.state) + self.assertFalse(self.world.logic.can_mine_in_the_skull_cavern()(self.multiworld.state)) if reachable_level > 4: - assert self.world.logic.can_mine_perfectly_in_the_skull_cavern()(self.multiworld.state) + self.assertTrue(self.world.logic.can_mine_perfectly_in_the_skull_cavern()(self.multiworld.state)) else: - assert not self.world.logic.can_mine_perfectly_in_the_skull_cavern()(self.multiworld.state) + self.assertFalse(self.world.logic.can_mine_perfectly_in_the_skull_cavern()(self.multiworld.state)) self.remove(item) + + +class TestRecipeLogic(SVTestBase): + options = { + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.Cropsanity.internal_name: options.Cropsanity.option_shuffled, + } + + # I wanted to make a test for different ways to obtain a pizza, but I'm stuck not knowing how to block the immediate purchase from Gus + # def test_pizza(self): + # world = self.world + # logic = world.logic + # multiworld = self.multiworld + # + # self.assertTrue(logic.has(Ingredient.wheat_flour)(multiworld.state)) + # self.assertTrue(logic.can_spend_money_at(Region.saloon, 150)(multiworld.state)) + # self.assertFalse(logic.has(Meal.pizza)(multiworld.state)) + # + # self.assertFalse(logic.can_cook()(multiworld.state)) + # self.collect(world.create_item("Progressive House")) + # self.assertTrue(logic.can_cook()(multiworld.state)) + # self.assertFalse(logic.has(Meal.pizza)(multiworld.state)) + # + # self.assertFalse(logic.has(Seed.tomato)(multiworld.state)) + # self.collect(world.create_item(Seed.tomato)) + # self.assertTrue(logic.has(Seed.tomato)(multiworld.state)) + # self.assertFalse(logic.has(Meal.pizza)(multiworld.state)) + # + # self.assertFalse(logic.has(Vegetable.tomato)(multiworld.state)) + # self.collect(world.create_item(Season.summer)) + # self.assertTrue(logic.has(Vegetable.tomato)(multiworld.state)) + # self.assertFalse(logic.has(Meal.pizza)(multiworld.state)) + # + # self.assertFalse(logic.has(Animal.cow)(multiworld.state)) + # self.assertFalse(logic.has(AnimalProduct.cow_milk)(multiworld.state)) + # self.collect(world.create_item("Progressive Barn")) + # self.assertTrue(logic.has(Animal.cow)(multiworld.state)) + # self.assertTrue(logic.has(AnimalProduct.cow_milk)(multiworld.state)) + # self.assertFalse(logic.has(Meal.pizza)(multiworld.state)) + # + # self.assertFalse(logic.has(Machine.cheese_press)(self.multiworld.state)) + # self.assertFalse(logic.has(ArtisanGood.cheese)(self.multiworld.state)) + # self.collect(world.create_item(item) for item in ["Farming Level"] * 6) + # self.collect(world.create_item(item) for item in ["Progressive Axe"] * 2) + # self.assertTrue(logic.has(Machine.cheese_press)(self.multiworld.state)) + # self.assertTrue(logic.has(ArtisanGood.cheese)(self.multiworld.state)) + # self.assertTrue(logic.has(Meal.pizza)(self.multiworld.state)) diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py index 9d2fac02..8975358b 100644 --- a/worlds/stardew_valley/test/__init__.py +++ b/worlds/stardew_valley/test/__init__.py @@ -1,10 +1,12 @@ +import os from argparse import Namespace from typing import Dict, FrozenSet, Tuple, Any, ClassVar from BaseClasses import MultiWorld from test.TestBase import WorldTestBase from test.general import gen_steps -from .. import StardewValleyWorld +from .. import StardewValleyWorld, options +from ..mods.mod_data import ModNames from ...AutoWorld import call_all @@ -12,36 +14,103 @@ class SVTestBase(WorldTestBase): game = "Stardew Valley" world: StardewValleyWorld player: ClassVar[int] = 1 + skip_long_tests: bool = True def world_setup(self, *args, **kwargs): super().world_setup(*args, **kwargs) + long_tests_key = "long" + if long_tests_key in os.environ: + self.skip_long_tests = not bool(os.environ[long_tests_key]) if self.constructed: self.world = self.multiworld.worlds[self.player] # noqa @property def run_default_tests(self) -> bool: # world_setup is overridden, so it'd always run default tests when importing SVTestBase - return type(self) is not SVTestBase and super().run_default_tests + is_not_stardew_test = type(self) is not SVTestBase + should_run_default_tests = is_not_stardew_test and super().run_default_tests + return should_run_default_tests + def minimal_locations_maximal_items(self): + min_max_options = { + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Cropsanity.internal_name: options.Cropsanity.option_shuffled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla, + options.ToolProgression.internal_name: options.ToolProgression.option_vanilla, + options.SkillProgression.internal_name: options.SkillProgression.option_vanilla, + options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_vanilla, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_disabled, + options.HelpWantedLocations.internal_name: 0, + options.Fishsanity.internal_name: options.Fishsanity.option_none, + options.Museumsanity.internal_name: options.Museumsanity.option_none, + options.Friendsanity.internal_name: options.Friendsanity.option_none, + options.NumberOfMovementBuffs.internal_name: 12, + options.NumberOfLuckBuffs.internal_name: 12, + } + return min_max_options + + def allsanity_options_without_mods(self): + allsanity = { + options.Goal.internal_name: options.Goal.option_perfection, + options.BundleRandomization.internal_name: options.BundleRandomization.option_shuffled, + options.BundlePrice.internal_name: options.BundlePrice.option_expensive, + options.SeasonRandomization.internal_name: options.SeasonRandomization.option_randomized, + options.Cropsanity.internal_name: options.Cropsanity.option_shuffled, + options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive, + options.ToolProgression.internal_name: options.ToolProgression.option_progressive, + options.SkillProgression.internal_name: options.SkillProgression.option_progressive, + options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive, + options.FestivalLocations.internal_name: options.FestivalLocations.option_hard, + options.ElevatorProgression.internal_name: options.ElevatorProgression.option_progressive, + options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling, + options.SpecialOrderLocations.internal_name: options.SpecialOrderLocations.option_board_qi, + options.HelpWantedLocations.internal_name: 56, + options.Fishsanity.internal_name: options.Fishsanity.option_all, + options.Museumsanity.internal_name: options.Museumsanity.option_all, + options.Friendsanity.internal_name: options.Friendsanity.option_all_with_marriage, + options.FriendsanityHeartSize.internal_name: 1, + options.NumberOfMovementBuffs.internal_name: 12, + options.NumberOfLuckBuffs.internal_name: 12, + options.ExcludeGingerIsland.internal_name: options.ExcludeGingerIsland.option_false, + options.TrapItems.internal_name: options.TrapItems.option_nightmare, + } + return allsanity + + def allsanity_options_with_mods(self): + allsanity = {} + allsanity.update(self.allsanity_options_without_mods()) + all_mods = ( + ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack, + ModNames.luck_skill, ModNames.magic, ModNames.socializing_skill, ModNames.archaeology, + ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna, + ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene, + ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores, + ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator + ) + allsanity.update({options.Mods.internal_name: all_mods}) + return allsanity pre_generated_worlds = {} # Mostly a copy of test.general.setup_solo_multiworld, I just don't want to change the core. -def setup_solo_multiworld(test_options=None, +def setup_solo_multiworld(test_options=None, seed=None, _cache: Dict[FrozenSet[Tuple[str, Any]], MultiWorld] = {}) -> MultiWorld: # noqa if test_options is None: test_options = {} # Yes I reuse the worlds generated between tests, its speeds the execution by a couple seconds - frozen_options = frozenset(test_options.items()) + frozen_options = frozenset(test_options.items()).union({seed}) if frozen_options in _cache: return _cache[frozen_options] multiworld = MultiWorld(1) multiworld.game[1] = StardewValleyWorld.game multiworld.player_name = {1: "Tester"} - multiworld.set_seed() + multiworld.set_seed(seed) + # print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test args = Namespace() for name, option in StardewValleyWorld.option_definitions.items(): value = option(test_options[name]) if name in test_options else option.from_any(option.default) diff --git a/worlds/stardew_valley/test/checks/__init__.py b/worlds/stardew_valley/test/checks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/stardew_valley/test/checks/goal_checks.py b/worlds/stardew_valley/test/checks/goal_checks.py new file mode 100644 index 00000000..e1059fe2 --- /dev/null +++ b/worlds/stardew_valley/test/checks/goal_checks.py @@ -0,0 +1,55 @@ +from BaseClasses import MultiWorld +from .option_checks import is_setting, assert_is_setting +from ... import options +from .. import SVTestBase + + +def is_goal(multiworld: MultiWorld, goal: int) -> bool: + return is_setting(multiworld, options.Goal.internal_name, goal) + + +def is_bottom_mines(multiworld: MultiWorld) -> bool: + return is_goal(multiworld, options.Goal.option_bottom_of_the_mines) + + +def is_not_bottom_mines(multiworld: MultiWorld) -> bool: + return not is_bottom_mines(multiworld) + + +def is_walnut_hunter(multiworld: MultiWorld) -> bool: + return is_goal(multiworld, options.Goal.option_greatest_walnut_hunter) + + +def is_not_walnut_hunter(multiworld: MultiWorld) -> bool: + return not is_walnut_hunter(multiworld) + + +def is_perfection(multiworld: MultiWorld) -> bool: + return is_goal(multiworld, options.Goal.option_perfection) + + +def is_not_perfection(multiworld: MultiWorld) -> bool: + return not is_perfection(multiworld) + + +def assert_ginger_island_is_included(tester: SVTestBase, multiworld: MultiWorld): + assert_is_setting(tester, multiworld, options.ExcludeGingerIsland.internal_name, options.ExcludeGingerIsland.option_false) + + +def assert_walnut_hunter_world_is_valid(tester: SVTestBase, multiworld: MultiWorld): + if is_not_walnut_hunter(multiworld): + return + + assert_ginger_island_is_included(tester, multiworld) + + +def assert_perfection_world_is_valid(tester: SVTestBase, multiworld: MultiWorld): + if is_not_perfection(multiworld): + return + + assert_ginger_island_is_included(tester, multiworld) + + +def assert_goal_world_is_valid(tester: SVTestBase, multiworld: MultiWorld): + assert_walnut_hunter_world_is_valid(tester, multiworld) + assert_perfection_world_is_valid(tester, multiworld) diff --git a/worlds/stardew_valley/test/checks/option_checks.py b/worlds/stardew_valley/test/checks/option_checks.py new file mode 100644 index 00000000..e6bced5b --- /dev/null +++ b/worlds/stardew_valley/test/checks/option_checks.py @@ -0,0 +1,90 @@ +from typing import Union + +from BaseClasses import MultiWorld +from .world_checks import get_all_item_names, get_all_location_names +from .. import SVTestBase +from ... import StardewValleyWorld, options, item_table, Group, location_table +from ...locations import LocationTags +from ...strings.ap_names.transport_names import Transportation + + +def get_stardew_world(multiworld: MultiWorld) -> Union[StardewValleyWorld, None]: + for world_key in multiworld.worlds: + world = multiworld.worlds[world_key] + if isinstance(world, StardewValleyWorld): + return world + return None + + +def is_setting(multiworld: MultiWorld, setting_name: str, setting_value: int) -> bool: + stardew_world = get_stardew_world(multiworld) + if not stardew_world: + return False + current_value = stardew_world.options[setting_name] + return current_value == setting_value + + +def is_not_setting(multiworld: MultiWorld, setting_name: str, setting_value: int) -> bool: + return not is_setting(multiworld, setting_name, setting_value) + + +def assert_is_setting(tester: SVTestBase, multiworld: MultiWorld, setting_name: str, setting_value: int) -> bool: + stardew_world = get_stardew_world(multiworld) + if not stardew_world: + return False + current_value = stardew_world.options[setting_name] + tester.assertEqual(current_value, setting_value) + + +def assert_can_reach_island(tester: SVTestBase, multiworld: MultiWorld): + all_item_names = get_all_item_names(multiworld) + tester.assertIn(Transportation.boat_repair, all_item_names) + tester.assertIn(Transportation.island_obelisk, all_item_names) + + +def assert_cannot_reach_island(tester: SVTestBase, multiworld: MultiWorld): + all_item_names = get_all_item_names(multiworld) + tester.assertNotIn(Transportation.boat_repair, all_item_names) + tester.assertNotIn(Transportation.island_obelisk, all_item_names) + + +def assert_can_reach_island_if_should(tester: SVTestBase, multiworld: MultiWorld): + include_island = is_setting(multiworld, options.ExcludeGingerIsland.internal_name, options.ExcludeGingerIsland.option_false) + if include_island: + assert_can_reach_island(tester, multiworld) + else: + assert_cannot_reach_island(tester, multiworld) + + +def assert_cropsanity_same_number_items_and_locations(tester: SVTestBase, multiworld: MultiWorld): + is_cropsanity = is_setting(multiworld, options.Cropsanity.internal_name, options.Cropsanity.option_shuffled) + if not is_cropsanity: + return + + all_item_names = set(get_all_item_names(multiworld)) + all_location_names = set(get_all_location_names(multiworld)) + all_cropsanity_item_names = {item_name for item_name in all_item_names if Group.CROPSANITY in item_table[item_name].groups} + all_cropsanity_location_names = {location_name for location_name in all_location_names if LocationTags.CROPSANITY in location_table[location_name].tags} + tester.assertEqual(len(all_cropsanity_item_names), len(all_cropsanity_location_names)) + + +def assert_all_rarecrows_exist(tester: SVTestBase, multiworld: MultiWorld): + all_item_names = set(get_all_item_names(multiworld)) + for rarecrow_number in range(1, 9): + tester.assertIn(f"Rarecrow #{rarecrow_number}", all_item_names) + + +def assert_has_deluxe_scarecrow_recipe(tester: SVTestBase, multiworld: MultiWorld): + all_item_names = set(get_all_item_names(multiworld)) + tester.assertIn(f"Deluxe Scarecrow Recipe", all_item_names) + + +def assert_festivals_give_access_to_deluxe_scarecrow(tester: SVTestBase, multiworld: MultiWorld): + has_festivals = is_not_setting(multiworld, options.FestivalLocations.internal_name, options.FestivalLocations.option_disabled) + if not has_festivals: + return + + assert_all_rarecrows_exist(tester, multiworld) + assert_has_deluxe_scarecrow_recipe(tester, multiworld) + + diff --git a/worlds/stardew_valley/test/checks/world_checks.py b/worlds/stardew_valley/test/checks/world_checks.py new file mode 100644 index 00000000..2cdb0534 --- /dev/null +++ b/worlds/stardew_valley/test/checks/world_checks.py @@ -0,0 +1,33 @@ +from typing import List + +from BaseClasses import MultiWorld, ItemClassification +from ... import StardewItem +from .. import SVTestBase + + +def get_all_item_names(multiworld: MultiWorld) -> List[str]: + return [item.name for item in multiworld.itempool] + + +def get_all_location_names(multiworld: MultiWorld) -> List[str]: + return [location.name for location in multiworld.get_locations() if not location.event] + + +def assert_victory_exists(tester: SVTestBase, multiworld: MultiWorld): + tester.assertIn(StardewItem("Victory", ItemClassification.progression, None, 1), multiworld.get_items()) + + +def collect_all_then_assert_can_win(tester: SVTestBase, multiworld: MultiWorld): + for item in multiworld.get_items(): + multiworld.state.collect(item) + tester.assertTrue(multiworld.find_item("Victory", 1).can_reach(multiworld.state)) + + +def assert_can_win(tester: SVTestBase, multiworld: MultiWorld): + assert_victory_exists(tester, multiworld) + collect_all_then_assert_can_win(tester, multiworld) + + +def assert_same_number_items_locations(tester: SVTestBase, multiworld: MultiWorld): + non_event_locations = [location for location in multiworld.get_locations() if not location.event] + tester.assertEqual(len(multiworld.itempool), len(non_event_locations)) \ No newline at end of file diff --git a/worlds/stardew_valley/test/long/TestOptionsLong.py b/worlds/stardew_valley/test/long/TestOptionsLong.py new file mode 100644 index 00000000..c614ddcc --- /dev/null +++ b/worlds/stardew_valley/test/long/TestOptionsLong.py @@ -0,0 +1,41 @@ +from typing import Dict + +from BaseClasses import MultiWorld +from Options import SpecialRange +from .option_names import options_to_include +from worlds.stardew_valley.test.checks.world_checks import assert_can_win, assert_same_number_items_locations +from .. import setup_solo_multiworld, SVTestBase + + +def basic_checks(tester: SVTestBase, multiworld: MultiWorld): + assert_can_win(tester, multiworld) + assert_same_number_items_locations(tester, multiworld) + + +def get_option_choices(option) -> Dict[str, int]: + if issubclass(option, SpecialRange): + return option.special_range_names + elif option.options: + return option.options + return {} + + +class TestGenerateDynamicOptions(SVTestBase): + def test_given_option_pair_when_generate_then_basic_checks(self): + if self.skip_long_tests: + return + + num_options = len(options_to_include) + for option1_index in range(0, num_options): + for option2_index in range(option1_index + 1, num_options): + option1 = options_to_include[option1_index] + option2 = options_to_include[option2_index] + option1_choices = get_option_choices(option1) + option2_choices = get_option_choices(option2) + for key1 in option1_choices: + for key2 in option2_choices: + with self.subTest(f"{option1.internal_name}: {key1}, {option2.internal_name}: {key2}"): + choices = {option1.internal_name: option1_choices[key1], + option2.internal_name: option2_choices[key2]} + multiworld = setup_solo_multiworld(choices) + basic_checks(self, multiworld) \ No newline at end of file diff --git a/worlds/stardew_valley/test/long/TestRandomWorlds.py b/worlds/stardew_valley/test/long/TestRandomWorlds.py new file mode 100644 index 00000000..6ba814ab --- /dev/null +++ b/worlds/stardew_valley/test/long/TestRandomWorlds.py @@ -0,0 +1,99 @@ +from typing import Dict, List +import random + +from BaseClasses import MultiWorld +from Options import SpecialRange, Range +from .option_names import options_to_include +from .. import setup_solo_multiworld, SVTestBase +from ..checks.goal_checks import assert_perfection_world_is_valid, assert_goal_world_is_valid +from ..checks.option_checks import assert_can_reach_island_if_should, assert_cropsanity_same_number_items_and_locations, \ + assert_festivals_give_access_to_deluxe_scarecrow +from ..checks.world_checks import assert_same_number_items_locations, assert_victory_exists +from ... import options + + +def get_option_choices(option) -> Dict[str, int]: + if issubclass(option, SpecialRange): + return option.special_range_names + if issubclass(option, Range): + return {f"{val}": val for val in range(option.range_start, option.range_end + 1)} + elif option.options: + return option.options + return {} + + +def generate_random_multiworld(world_id: int): + world_options = generate_random_world_options(world_id) + multiworld = setup_solo_multiworld(world_options, seed=world_id) + return multiworld + + +def generate_random_world_options(world_id: int) -> Dict[str, int]: + num_options = len(options_to_include) + world_options = dict() + rng = random.Random(world_id) + for option_index in range(0, num_options): + option = options_to_include[option_index] + option_choices = get_option_choices(option) + if not option_choices: + continue + chosen_option_value = rng.choice(list(option_choices.values())) + world_options[option.internal_name] = chosen_option_value + return world_options + + +def get_number_log_steps(number_worlds: int) -> int: + if number_worlds <= 10: + return 2 + if number_worlds <= 100: + return 5 + if number_worlds <= 500: + return 10 + if number_worlds <= 1000: + return 20 + if number_worlds <= 5000: + return 25 + if number_worlds <= 10000: + return 50 + return 100 + + +def generate_many_worlds(number_worlds: int, start_index: int) -> Dict[int, MultiWorld]: + num_steps = get_number_log_steps(number_worlds) + log_step = number_worlds / num_steps + multiworlds = dict() + print(f"Generating {number_worlds} Solo Multiworlds [Start Seed: {start_index}] for Stardew Valley...") + for world_number in range(0, number_worlds + 1): + world_id = world_number + start_index + multiworld = generate_random_multiworld(world_id) + multiworlds[world_id] = multiworld + if world_number > 0 and world_number % log_step == 0: + print(f"Generated {world_number}/{number_worlds} worlds [{(world_number * 100) // number_worlds}%]") + print(f"Finished generating {number_worlds} Solo Multiworlds for Stardew Valley") + return multiworlds + + +def check_every_multiworld_is_valid(tester: SVTestBase, multiworlds: Dict[int, MultiWorld]): + for multiworld_id in multiworlds: + multiworld = multiworlds[multiworld_id] + with tester.subTest(f"Checking validity of world {multiworld_id}"): + check_multiworld_is_valid(tester, multiworld_id, multiworld) + + +def check_multiworld_is_valid(tester: SVTestBase, multiworld_id: int, multiworld: MultiWorld): + assert_victory_exists(tester, multiworld) + assert_same_number_items_locations(tester, multiworld) + assert_goal_world_is_valid(tester, multiworld) + assert_can_reach_island_if_should(tester, multiworld) + assert_cropsanity_same_number_items_and_locations(tester, multiworld) + assert_festivals_give_access_to_deluxe_scarecrow(tester, multiworld) + + +class TestGenerateManyWorlds(SVTestBase): + def test_generate_many_worlds_then_check_results(self): + if self.skip_long_tests: + return + number_worlds = 1000 + start_index = random.Random().randint(0, 9999999999) + multiworlds = generate_many_worlds(number_worlds, start_index) + check_every_multiworld_is_valid(self, multiworlds) diff --git a/worlds/stardew_valley/test/long/__init__.py b/worlds/stardew_valley/test/long/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/stardew_valley/test/long/option_names.py b/worlds/stardew_valley/test/long/option_names.py new file mode 100644 index 00000000..9bb950d3 --- /dev/null +++ b/worlds/stardew_valley/test/long/option_names.py @@ -0,0 +1,7 @@ +from worlds.stardew_valley.options import stardew_valley_option_classes + +options_to_exclude = ["profit_margin", "starting_money", "multiple_day_sleep_enabled", "multiple_day_sleep_cost", + "experience_multiplier", "friendship_multiplier", "debris_multiplier", + "quick_start", "gifting", "gift_tax"] +options_to_include = [option_to_include for option_to_include in stardew_valley_option_classes + if option_to_include.internal_name not in options_to_exclude]