From b23c1202582cbdf9a8e882d6b65cb580c5c5a382 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:17:43 -0400 Subject: [PATCH 01/21] Subnautica: Fix deprecated option getting (#3685) --- worlds/subnautica/rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/subnautica/rules.py b/worlds/subnautica/rules.py index 3b6c5cd4dd..ea9ec6a805 100644 --- a/worlds/subnautica/rules.py +++ b/worlds/subnautica/rules.py @@ -150,7 +150,7 @@ def has_ultra_glide_fins(state: "CollectionState", player: int) -> bool: def get_max_swim_depth(state: "CollectionState", player: int) -> int: - swim_rule: SwimRule = state.multiworld.swim_rule[player] + swim_rule: SwimRule = state.multiworld.worlds[player].options.swim_rule depth: int = swim_rule.base_depth if swim_rule.consider_items: if has_seaglide(state, player): @@ -296,7 +296,7 @@ def set_rules(subnautica_world: "SubnauticaWorld"): set_location_rule(multiworld, player, loc) if subnautica_world.creatures_to_scan: - option = multiworld.creature_scan_logic[player] + option = multiworld.worlds[player].options.creature_scan_logic for creature_name in subnautica_world.creatures_to_scan: location = set_creature_rule(multiworld, player, creature_name) From 2307694012f3d49dbacbcdb059473f52c87aedea Mon Sep 17 00:00:00 2001 From: qwint Date: Wed, 24 Jul 2024 20:08:58 -0500 Subject: [PATCH 02/21] HK: fix remove issues failing collect/remove test (#3667) --- worlds/hk/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 78287305df..fbc6461f6a 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -554,7 +554,8 @@ class HKWorld(World): for effect_name, effect_value in item_effects.get(item.name, {}).items(): if state.prog_items[item.player][effect_name] == effect_value: del state.prog_items[item.player][effect_name] - state.prog_items[item.player][effect_name] -= effect_value + else: + state.prog_items[item.player][effect_name] -= effect_value return change From 697f7495184bbc7791ab3ad93bd2d7f4fa468ea5 Mon Sep 17 00:00:00 2001 From: Silent <110704408+silent-destroyer@users.noreply.github.com> Date: Thu, 25 Jul 2024 00:06:45 -0400 Subject: [PATCH 03/21] TUNIC: Missing slot data bugfix (#3628) * Fix certain items not being added to slot data * Change where items get added to slot data From 94e6e978f330c25eeca37692f03b52cbfeb4c386 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Thu, 25 Jul 2024 00:07:20 -0400 Subject: [PATCH 04/21] =?UTF-8?q?Pok=C3=A9mon=20R/B:=20Also=20fix=20Rt=204?= =?UTF-8?q?=20Hidden=20Item=20(#3668)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: alchav --- worlds/pokemon_rb/locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index 251beb59cc..6aee25df26 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -427,7 +427,7 @@ location_data = [ LocationData("Seafoam Islands B3F", "Hidden Item Rock", "Max Elixir", rom_addresses['Hidden_Item_Seafoam_Islands_B3F'], Hidden(50), inclusion=hidden_items), LocationData("Vermilion City", "Hidden Item In Water Near Fan Club", "Max Ether", rom_addresses['Hidden_Item_Vermilion_City'], Hidden(51), inclusion=hidden_items), LocationData("Cerulean City-Badge House Backyard", "Hidden Item Gym Badge Guy's Backyard", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_City'], Hidden(52), inclusion=hidden_items), - LocationData("Route 4-E", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items), + LocationData("Route 4-C", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items), LocationData("Oak's Lab", "Oak's Parcel Reward", "Pokedex", rom_addresses["Event_Pokedex"], EventFlag(0x38)), From f34da74012ba0f69433b158a68e008dc36a258d7 Mon Sep 17 00:00:00 2001 From: agilbert1412 Date: Thu, 25 Jul 2024 00:13:16 -0400 Subject: [PATCH 05/21] Stardew Valley: Make Fairy Dust a Ginger Island only item and location (#3650) --- worlds/stardew_valley/data/items.csv | 2 +- worlds/stardew_valley/data/locations.csv | 2 +- worlds/stardew_valley/items.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index 2604ad2c46..e026090f86 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -307,7 +307,7 @@ id,name,classification,groups,mod_name 322,Phoenix Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", 323,Immunity Band,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", 324,Glowstone Ring,useful,"RING,RESOURCE_PACK,MAXIMUM_ONE", -325,Fairy Dust Recipe,progression,, +325,Fairy Dust Recipe,progression,"GINGER_ISLAND", 326,Heavy Tapper Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", 327,Hyper Speed-Gro Recipe,progression,"QI_CRAFTING_RECIPE,GINGER_ISLAND", 328,Deluxe Fertilizer Recipe,progression,QI_CRAFTING_RECIPE, diff --git a/worlds/stardew_valley/data/locations.csv b/worlds/stardew_valley/data/locations.csv index 6e30d2b8c8..242d00b445 100644 --- a/worlds/stardew_valley/data/locations.csv +++ b/worlds/stardew_valley/data/locations.csv @@ -2088,7 +2088,7 @@ id,region,name,tags,mod_name 3472,Farm,Craft Life Elixir,CRAFTSANITY, 3473,Farm,Craft Oil of Garlic,CRAFTSANITY, 3474,Farm,Craft Monster Musk,CRAFTSANITY, -3475,Farm,Craft Fairy Dust,CRAFTSANITY, +3475,Farm,Craft Fairy Dust,"CRAFTSANITY,GINGER_ISLAND", 3476,Farm,Craft Warp Totem: Beach,CRAFTSANITY, 3477,Farm,Craft Warp Totem: Mountains,CRAFTSANITY, 3478,Farm,Craft Warp Totem: Farm,CRAFTSANITY, diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index cb61020169..31c7da5e3a 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -409,8 +409,9 @@ def create_special_quest_rewards(item_factory: StardewItemFactory, options: Star else: items.append(item_factory(Wallet.bears_knowledge, ItemClassification.useful)) # Not necessary outside of SVE items.append(item_factory(Wallet.iridium_snake_milk)) - items.append(item_factory("Fairy Dust Recipe")) items.append(item_factory("Dark Talisman")) + if options.exclude_ginger_island == ExcludeGingerIsland.option_false: + items.append(item_factory("Fairy Dust Recipe")) def create_help_wanted_quest_rewards(item_factory: StardewItemFactory, options: StardewValleyOptions, items: List[Item]): From 496f0e09afe7e863cf1bcce948d48bf591086274 Mon Sep 17 00:00:00 2001 From: qwint Date: Thu, 25 Jul 2024 01:21:51 -0500 Subject: [PATCH 06/21] CommonClient: forget password when disconnecting (#3641) * makes the kivy connect button do the same username forgetting that /connect does to fix an issue where losing connection would make you unable to connect to a different server * extract duplicate code * per request, adds handling on any disconnect to forget the saved password as to not leak it to other servers --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- CommonClient.py | 2 ++ kvui.py | 1 + 2 files changed, 3 insertions(+) diff --git a/CommonClient.py b/CommonClient.py index f8d1fcb7a2..09937e4b9a 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -61,6 +61,7 @@ class ClientCommandProcessor(CommandProcessor): if address: self.ctx.server_address = None self.ctx.username = None + self.ctx.password = None elif not self.ctx.server_address: self.output("Please specify an address.") return False @@ -514,6 +515,7 @@ class CommonContext: async def shutdown(self): self.server_address = "" self.username = None + self.password = None self.cancel_autoreconnect() if self.server and not self.server.socket.closed: await self.server.socket.close() diff --git a/kvui.py b/kvui.py index a63d636960..f83590a819 100644 --- a/kvui.py +++ b/kvui.py @@ -596,6 +596,7 @@ class GameManager(App): def connect_button_action(self, button): self.ctx.username = None + self.ctx.password = None if self.ctx.server: async_start(self.ctx.disconnect()) else: From deae524e9ba9f1bab1c48642f11108799d7b3b3a Mon Sep 17 00:00:00 2001 From: qwint Date: Thu, 25 Jul 2024 02:05:04 -0500 Subject: [PATCH 07/21] Docs: add a living faq document for sharing dev solutions (#3156) * adding one faq :) * adding another faq that links to the relevant file * add lined line breaks between questions and lower the heading size of the question so sub-divisions can be added later * missed some newlines * updating best practice filler method * add note about get_filler_item_name() * updates to wording from review * add section to CODEOWNERS for maintainers of this doc * use underscores to reference the file easier in CODEOWNERS * update link to be direct and filter to function name --- docs/CODEOWNERS | 14 +++++++++++--- docs/apworld_dev_faq.md | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 docs/apworld_dev_faq.md diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 3b40d7e77a..ab841e65ee 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -1,8 +1,8 @@ # Archipelago World Code Owners / Maintainers Document # -# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder. For any pull -# requests that modify these worlds, a code owner must approve the PR in addition to a core maintainer. This is not to -# be used for files/folders outside the /worlds folder, those will always need sign off from a core maintainer. +# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder as well as +# certain documentation. For any pull requests that modify these worlds/docs, a code owner must approve the PR in +# addition to a core maintainer. All other files and folders are owned and maintained by core maintainers directly. # # All usernames must be GitHub usernames (and are case sensitive). @@ -226,3 +226,11 @@ # Ori and the Blind Forest # /worlds_disabled/oribf/ + +################### +## Documentation ## +################### + +# Apworld Dev Faq +/docs/apworld_dev_faq.md @qwint @ScipioWright + diff --git a/docs/apworld_dev_faq.md b/docs/apworld_dev_faq.md new file mode 100644 index 0000000000..059c33844f --- /dev/null +++ b/docs/apworld_dev_faq.md @@ -0,0 +1,32 @@ +# APWorld Dev FAQ + +This document is meant as a reference tool to show solutions to common problems when developing an apworld. + +--- + +### My game has a restrictive start that leads to fill errors + +Hint to the Generator that an item needs to be in sphere one with local_early_items +```py +early_item_name = "Sword" +self.multiworld.local_early_items[self.player][early_item_name] = 1 +``` + +--- + +### I have multiple settings that change the item/location pool counts and need to balance them out + +In an ideal situation your system for producing locations and items wouldn't leave any opportunity for them to be unbalanced. But in real, complex situations, that might be unfeasible. + +If that's the case, you can create extra filler based on the difference between your unfilled locations and your itempool by comparing [get_unfilled_locations](https://github.com/ArchipelagoMW/Archipelago/blob/main/BaseClasses.py#:~:text=get_unfilled_locations) to your list of items to submit + +Note: to use self.create_filler(), self.get_filler_item_name() should be defined to only return valid filler item names +```py +total_locations = len(self.multiworld.get_unfilled_locations(self.player)) +item_pool = self.create_non_filler_items() + +while len(item_pool) < total_locations: + item_pool.append(self.create_filler()) + +self.multiworld.itempool += item_pool +``` From 8949e215654c29f3e46e254bd28fa8572c0acdbc Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:10:36 +0200 Subject: [PATCH 08/21] settings: safer writing (#3644) * settings: clean up imports * settings: try to use atomic rename * settings: flush, sync and validate new yaml before replacing the old one * settings: add test for Settings.save --- settings.py | 18 +++++++++++++----- test/general/test_host_yaml.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/settings.py b/settings.py index 7ab618c344..7927705214 100644 --- a/settings.py +++ b/settings.py @@ -3,6 +3,7 @@ Application settings / host.yaml interface using type hints. This is different from player options. """ +import os import os.path import shutil import sys @@ -11,7 +12,6 @@ import warnings from enum import IntEnum from threading import Lock from typing import cast, Any, BinaryIO, ClassVar, Dict, Iterator, List, Optional, TextIO, Tuple, Union, TypeVar -import os __all__ = [ "get_settings", "fmt_doc", "no_gui", @@ -798,6 +798,7 @@ class Settings(Group): atexit.register(autosave) def save(self, location: Optional[str] = None) -> None: # as above + from Utils import parse_yaml location = location or self._filename assert location, "No file specified" temp_location = location + ".tmp" # not using tempfile to test expected file access @@ -807,10 +808,18 @@ class Settings(Group): # can't use utf-8-sig because it breaks backward compat: pyyaml on Windows with bytes does not strip the BOM with open(temp_location, "w", encoding="utf-8") as f: self.dump(f) - # replace old with new - if os.path.exists(location): + f.flush() + if hasattr(os, "fsync"): + os.fsync(f.fileno()) + # validate new file is valid yaml + with open(temp_location, encoding="utf-8") as f: + parse_yaml(f.read()) + # replace old with new, try atomic operation first + try: + os.rename(temp_location, location) + except (OSError, FileExistsError): os.unlink(location) - os.rename(temp_location, location) + os.rename(temp_location, location) self._filename = location def dump(self, f: TextIO, level: int = 0) -> None: @@ -832,7 +841,6 @@ def get_settings() -> Settings: with _lock: # make sure we only have one instance res = getattr(get_settings, "_cache", None) if not res: - import os from Utils import user_path, local_path filenames = ("options.yaml", "host.yaml") locations: List[str] = [] diff --git a/test/general/test_host_yaml.py b/test/general/test_host_yaml.py index 7174befca4..3edbd34a51 100644 --- a/test/general/test_host_yaml.py +++ b/test/general/test_host_yaml.py @@ -1,11 +1,12 @@ import os +import os.path import unittest from io import StringIO -from tempfile import TemporaryFile +from tempfile import TemporaryDirectory, TemporaryFile from typing import Any, Dict, List, cast import Utils -from settings import Settings, Group +from settings import Group, Settings, ServerOptions class TestIDs(unittest.TestCase): @@ -80,3 +81,27 @@ class TestSettingsDumper(unittest.TestCase): self.assertEqual(value_spaces[2], value_spaces[0]) # start of sub-list self.assertGreater(value_spaces[3], value_spaces[0], f"{value_lines[3]} should have more indentation than {value_lines[0]} in {lines}") + + +class TestSettingsSave(unittest.TestCase): + def test_save(self) -> None: + """Test that saving and updating works""" + with TemporaryDirectory() as d: + filename = os.path.join(d, "host.yaml") + new_release_mode = ServerOptions.ReleaseMode("enabled") + # create default host.yaml + settings = Settings(None) + settings.save(filename) + self.assertTrue(os.path.exists(filename), + "Default settings could not be saved") + self.assertNotEqual(settings.server_options.release_mode, new_release_mode, + "Unexpected default release mode") + # update host.yaml + settings.server_options.release_mode = new_release_mode + settings.save(filename) + self.assertFalse(os.path.exists(filename + ".tmp"), + "Temp file was not removed during save") + # read back host.yaml + settings = Settings(filename) + self.assertEqual(settings.server_options.release_mode, new_release_mode, + "Settings were not overwritten") From 205ca7fa37cbcfc1515eaace6239a1acbb34f6a2 Mon Sep 17 00:00:00 2001 From: Witchybun <96719127+Witchybun@users.noreply.github.com> Date: Thu, 25 Jul 2024 02:22:46 -0500 Subject: [PATCH 09/21] Stardew Valley: Fix Daggerfish, Cropsanity; Move Some Rules to Content Packs; Add Missing Shipsanity Location (#3626) * Fix logic bug on daggerfish * Make new region for pond. * Fix SVE logic for crops * Fix Distant Lands Cropsanity * Fix failing tests. * Reverting removing these for now. * Fix bugs, add combat requirement * convert str into tuple directly * add ginger island to mod tests * Move a lot of mod item logic to content pack * Gut the rules from DL while we're at it. * Import nuke * Fix alecto * Move back some rules for now. * Move archaeology rules * Add some comments why its done. * Clean up archaeology and fix sve * Moved dulse to water item class * Remove digging like worms for now * fix * Add missing shipsanity location * Move background names around or something idk * Revert ArchaeologyTrash for now --------- Co-authored-by: Jouramie --- worlds/stardew_valley/content/mods/alecto.py | 33 +++++ .../stardew_valley/content/mods/archeology.py | 36 ++++-- .../content/mods/distant_lands.py | 31 ++++- .../stardew_valley/content/mods/npc_mods.py | 7 -- worlds/stardew_valley/content/mods/sve.py | 102 ++++++++++++++-- .../content/vanilla/pelican_town.py | 6 +- worlds/stardew_valley/data/fish_data.py | 7 +- worlds/stardew_valley/data/locations.csv | 3 +- worlds/stardew_valley/data/recipe_data.py | 4 +- worlds/stardew_valley/data/requirement.py | 21 ++++ worlds/stardew_valley/data/shop.py | 4 +- .../stardew_valley/logic/requirement_logic.py | 27 +++- .../stardew_valley/mods/logic/item_logic.py | 115 +----------------- worlds/stardew_valley/mods/mod_regions.py | 4 +- worlds/stardew_valley/rules.py | 1 + worlds/stardew_valley/strings/book_names.py | 4 - .../stardew_valley/strings/entrance_names.py | 1 + worlds/stardew_valley/strings/fish_names.py | 5 +- worlds/stardew_valley/strings/food_names.py | 1 + worlds/stardew_valley/strings/region_names.py | 1 + worlds/stardew_valley/test/mods/TestMods.py | 8 +- 21 files changed, 258 insertions(+), 163 deletions(-) create mode 100644 worlds/stardew_valley/content/mods/alecto.py diff --git a/worlds/stardew_valley/content/mods/alecto.py b/worlds/stardew_valley/content/mods/alecto.py new file mode 100644 index 0000000000..c05c936de3 --- /dev/null +++ b/worlds/stardew_valley/content/mods/alecto.py @@ -0,0 +1,33 @@ +from ..game_content import ContentPack, StardewContent +from ..mod_registry import register_mod_content_pack +from ...data import villagers_data +from ...data.harvest import ForagingSource +from ...data.requirement import QuestRequirement +from ...mods.mod_data import ModNames +from ...strings.quest_names import ModQuest +from ...strings.region_names import Region +from ...strings.seed_names import DistantLandsSeed + + +class AlectoContentPack(ContentPack): + + def harvest_source_hook(self, content: StardewContent): + if ModNames.distant_lands in content.registered_packs: + content.game_items.pop(DistantLandsSeed.void_mint) + content.game_items.pop(DistantLandsSeed.vile_ancient_fruit) + content.source_item(DistantLandsSeed.void_mint, + ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.WitchOrder),)),), + content.source_item(DistantLandsSeed.vile_ancient_fruit, + ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.WitchOrder),)), ), + + +register_mod_content_pack(ContentPack( + ModNames.alecto, + weak_dependencies=( + ModNames.distant_lands, # For Witch's order + ), + villagers=( + villagers_data.alecto, + ) + +)) diff --git a/worlds/stardew_valley/content/mods/archeology.py b/worlds/stardew_valley/content/mods/archeology.py index 97d38085d3..5eb8af4cfc 100644 --- a/worlds/stardew_valley/content/mods/archeology.py +++ b/worlds/stardew_valley/content/mods/archeology.py @@ -1,20 +1,34 @@ -from ..game_content import ContentPack +from ..game_content import ContentPack, StardewContent from ..mod_registry import register_mod_content_pack -from ...data.game_item import ItemTag, Tag -from ...data.shop import ShopSource +from ...data.artisan import MachineSource from ...data.skill import Skill from ...mods.mod_data import ModNames -from ...strings.book_names import ModBook -from ...strings.region_names import LogicRegion +from ...strings.craftable_names import ModMachine +from ...strings.fish_names import ModTrash +from ...strings.metal_names import all_artifacts, all_fossils from ...strings.skill_names import ModSkill -register_mod_content_pack(ContentPack( + +class ArchaeologyContentPack(ContentPack): + def artisan_good_hook(self, content: StardewContent): + # Done as honestly there are too many display items to put into the initial registration traditionally. + display_items = all_artifacts + all_fossils + for item in display_items: + self.source_display_items(item, content) + content.source_item(ModTrash.rusty_scrap, *(MachineSource(item=artifact, machine=ModMachine.grinder) for artifact in all_artifacts)) + + def source_display_items(self, item: str, content: StardewContent): + wood_display = f"Wooden Display: {item}" + hardwood_display = f"Hardwood Display: {item}" + if item == "Trilobite": + wood_display = f"Wooden Display: Trilobite Fossil" + hardwood_display = f"Hardwood Display: Trilobite Fossil" + content.source_item(wood_display, MachineSource(item=str(item), machine=ModMachine.preservation_chamber)) + content.source_item(hardwood_display, MachineSource(item=str(item), machine=ModMachine.hardwood_preservation_chamber)) + + +register_mod_content_pack(ArchaeologyContentPack( ModNames.archaeology, - shop_sources={ - ModBook.digging_like_worms: ( - Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL), - ShopSource(money_price=500, shop_region=LogicRegion.bookseller_1),), - }, skills=(Skill(name=ModSkill.archaeology, has_mastery=False),), )) diff --git a/worlds/stardew_valley/content/mods/distant_lands.py b/worlds/stardew_valley/content/mods/distant_lands.py index 19380d4ff5..c5614d1302 100644 --- a/worlds/stardew_valley/content/mods/distant_lands.py +++ b/worlds/stardew_valley/content/mods/distant_lands.py @@ -1,9 +1,26 @@ -from ..game_content import ContentPack +from ..game_content import ContentPack, StardewContent from ..mod_registry import register_mod_content_pack from ...data import villagers_data, fish_data +from ...data.game_item import ItemTag, Tag +from ...data.harvest import ForagingSource, HarvestCropSource +from ...data.requirement import QuestRequirement from ...mods.mod_data import ModNames +from ...strings.crop_names import DistantLandsCrop +from ...strings.forageable_names import DistantLandsForageable +from ...strings.quest_names import ModQuest +from ...strings.region_names import Region +from ...strings.season_names import Season +from ...strings.seed_names import DistantLandsSeed -register_mod_content_pack(ContentPack( + +class DistantLandsContentPack(ContentPack): + + def harvest_source_hook(self, content: StardewContent): + content.untag_item(DistantLandsSeed.void_mint, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(DistantLandsSeed.vile_ancient_fruit, tag=ItemTag.CROPSANITY_SEED) + + +register_mod_content_pack(DistantLandsContentPack( ModNames.distant_lands, fishes=( fish_data.void_minnow, @@ -13,5 +30,13 @@ register_mod_content_pack(ContentPack( ), villagers=( villagers_data.zic, - ) + ), + harvest_sources={ + DistantLandsForageable.swamp_herb: (ForagingSource(regions=(Region.witch_swamp,)),), + DistantLandsForageable.brown_amanita: (ForagingSource(regions=(Region.witch_swamp,)),), + DistantLandsSeed.void_mint: (ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.CorruptedCropsTask),)),), + DistantLandsCrop.void_mint: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=DistantLandsSeed.void_mint, seasons=(Season.spring, Season.summer, Season.fall)),), + DistantLandsSeed.vile_ancient_fruit: (ForagingSource(regions=(Region.witch_swamp,), other_requirements=(QuestRequirement(ModQuest.CorruptedCropsTask),)),), + DistantLandsCrop.vile_ancient_fruit: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=DistantLandsSeed.vile_ancient_fruit, seasons=(Season.spring, Season.summer, Season.fall)),) + } )) diff --git a/worlds/stardew_valley/content/mods/npc_mods.py b/worlds/stardew_valley/content/mods/npc_mods.py index 3172a55dbf..52d97d5c52 100644 --- a/worlds/stardew_valley/content/mods/npc_mods.py +++ b/worlds/stardew_valley/content/mods/npc_mods.py @@ -73,13 +73,6 @@ register_mod_content_pack(ContentPack( ) )) -register_mod_content_pack(ContentPack( - ModNames.alecto, - villagers=( - villagers_data.alecto, - ) -)) - register_mod_content_pack(ContentPack( ModNames.lacey, villagers=( diff --git a/worlds/stardew_valley/content/mods/sve.py b/worlds/stardew_valley/content/mods/sve.py index f74b80948c..a68d4ae9c0 100644 --- a/worlds/stardew_valley/content/mods/sve.py +++ b/worlds/stardew_valley/content/mods/sve.py @@ -3,15 +3,27 @@ from ..mod_registry import register_mod_content_pack from ..override import override from ..vanilla.ginger_island import ginger_island_content_pack as ginger_island_content_pack from ...data import villagers_data, fish_data -from ...data.harvest import ForagingSource -from ...data.requirement import YearRequirement +from ...data.game_item import ItemTag, Tag +from ...data.harvest import ForagingSource, HarvestCropSource +from ...data.requirement import YearRequirement, CombatRequirement, RelationshipRequirement, ToolRequirement, SkillRequirement, FishingRequirement +from ...data.shop import ShopSource from ...mods.mod_data import ModNames -from ...strings.crop_names import Fruit -from ...strings.fish_names import WaterItem +from ...strings.craftable_names import ModEdible +from ...strings.crop_names import Fruit, SVEVegetable, SVEFruit +from ...strings.fish_names import WaterItem, SVEFish, SVEWaterItem from ...strings.flower_names import Flower -from ...strings.forageable_names import Mushroom, Forageable -from ...strings.region_names import Region, SVERegion +from ...strings.food_names import SVEMeal, SVEBeverage +from ...strings.forageable_names import Mushroom, Forageable, SVEForage +from ...strings.gift_names import SVEGift +from ...strings.metal_names import Ore +from ...strings.monster_drop_names import ModLoot, Loot +from ...strings.performance_names import Performance +from ...strings.region_names import Region, SVERegion, LogicRegion from ...strings.season_names import Season +from ...strings.seed_names import SVESeed +from ...strings.skill_names import Skill +from ...strings.tool_names import Tool, ToolMaterial +from ...strings.villager_names import ModNPC class SVEContentPack(ContentPack): @@ -38,6 +50,24 @@ class SVEContentPack(ContentPack): # Remove Lance if Ginger Island is not in content since he is first encountered in Volcano Forge content.villagers.pop(villagers_data.lance.name) + def harvest_source_hook(self, content: StardewContent): + content.untag_item(SVESeed.shrub, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.fungus, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.slime, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.stalk, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.void, tag=ItemTag.CROPSANITY_SEED) + content.untag_item(SVESeed.ancient_fern, tag=ItemTag.CROPSANITY_SEED) + if ginger_island_content_pack.name not in content.registered_packs: + # Remove Highlands seeds as these are behind Lance existing. + content.game_items.pop(SVESeed.void) + content.game_items.pop(SVEVegetable.void_root) + content.game_items.pop(SVESeed.stalk) + content.game_items.pop(SVEFruit.monster_fruit) + content.game_items.pop(SVESeed.fungus) + content.game_items.pop(SVEVegetable.monster_mushroom) + content.game_items.pop(SVESeed.slime) + content.game_items.pop(SVEFruit.slime_berry) + register_mod_content_pack(SVEContentPack( ModNames.sve, @@ -45,12 +75,24 @@ register_mod_content_pack(SVEContentPack( ginger_island_content_pack.name, ModNames.jasper, # To override Marlon and Gunther ), + shop_sources={ + SVEGift.aged_blue_moon_wine: (ShopSource(money_price=28000, shop_region=SVERegion.blue_moon_vineyard),), + SVEGift.blue_moon_wine: (ShopSource(money_price=3000, shop_region=SVERegion.blue_moon_vineyard),), + ModEdible.lightning_elixir: (ShopSource(money_price=12000, shop_region=SVERegion.galmoran_outpost),), + ModEdible.barbarian_elixir: (ShopSource(money_price=22000, shop_region=SVERegion.galmoran_outpost),), + ModEdible.gravity_elixir: (ShopSource(money_price=4000, shop_region=SVERegion.galmoran_outpost),), + SVEMeal.grampleton_orange_chicken: (ShopSource(money_price=650, shop_region=Region.saloon, other_requirements=(RelationshipRequirement(ModNPC.sophia, 6),)),), + ModEdible.hero_elixir: (ShopSource(money_price=8000, shop_region=SVERegion.isaac_shop),), + ModEdible.aegis_elixir: (ShopSource(money_price=28000, shop_region=SVERegion.galmoran_outpost),), + SVEBeverage.sports_drink: (ShopSource(money_price=750, shop_region=Region.hospital),), + SVEMeal.stamina_capsule: (ShopSource(money_price=4000, shop_region=Region.hospital),), + }, harvest_sources={ Mushroom.red: ( ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.summer, Season.fall)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), ) ), Mushroom.purple: ( - ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), ) + ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave, SVERegion.junimo_woods), ) ), Mushroom.morel: ( ForagingSource(regions=(SVERegion.forest_west,), seasons=(Season.fall,)), ForagingSource(regions=(SVERegion.sprite_spring_cave,), ) @@ -64,17 +106,59 @@ register_mod_content_pack(SVEContentPack( Flower.sunflower: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.summer,)),), Flower.fairy_rose: (ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.fall,)),), Fruit.ancient_fruit: ( - ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring, Season.summer, Season.fall), other_requirements=(YearRequirement(3),)), + ForagingSource(regions=(SVERegion.sprite_spring,), seasons=Season.not_winter, other_requirements=(YearRequirement(3),)), ForagingSource(regions=(SVERegion.sprite_spring_cave,)), ), Fruit.sweet_gem_berry: ( - ForagingSource(regions=(SVERegion.sprite_spring,), seasons=(Season.spring, Season.summer, Season.fall), other_requirements=(YearRequirement(3),)), + ForagingSource(regions=(SVERegion.sprite_spring,), seasons=Season.not_winter, other_requirements=(YearRequirement(3),)), ), + # New items + + ModLoot.green_mushroom: (ForagingSource(regions=(SVERegion.highlands_pond,), seasons=Season.not_winter),), + ModLoot.ornate_treasure_chest: (ForagingSource(regions=(SVERegion.highlands_outside,), + other_requirements=(CombatRequirement(Performance.galaxy), ToolRequirement(Tool.axe, ToolMaterial.iron))),), + ModLoot.swirl_stone: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.galaxy),)),), + ModLoot.void_soul: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEForage.winter_star_rose: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.winter,)),), + SVEForage.bearberry: (ForagingSource(regions=(Region.secret_woods,), seasons=(Season.winter,)),), + SVEForage.poison_mushroom: (ForagingSource(regions=(Region.secret_woods,), seasons=(Season.summer, Season.fall)),), + SVEForage.red_baneberry: (ForagingSource(regions=(Region.secret_woods,), seasons=(Season.summer, Season.summer)),), + SVEForage.ferngill_primrose: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.spring,)),), + SVEForage.goldenrod: (ForagingSource(regions=(SVERegion.summit,), seasons=(Season.summer, Season.fall)),), + SVEForage.conch: (ForagingSource(regions=(Region.beach, SVERegion.fable_reef,)),), + SVEForage.dewdrop_berry: (ForagingSource(regions=(SVERegion.enchanted_grove,)),), + SVEForage.sand_dollar: (ForagingSource(regions=(Region.beach, SVERegion.fable_reef,), seasons=(Season.spring, Season.summer)),), + SVEForage.golden_ocean_flower: (ForagingSource(regions=(SVERegion.fable_reef,)),), + SVEForage.four_leaf_clover: (ForagingSource(regions=(Region.secret_woods, SVERegion.forest_west,), seasons=(Season.summer, Season.fall)),), + SVEForage.mushroom_colony: (ForagingSource(regions=(Region.secret_woods, SVERegion.junimo_woods, SVERegion.forest_west,), seasons=(Season.fall,)),), + SVEForage.rusty_blade: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.great),)),), + SVEForage.rafflesia: (ForagingSource(regions=(Region.secret_woods,), seasons=Season.not_winter),), + SVEForage.thistle: (ForagingSource(regions=(SVERegion.summit,)),), + ModLoot.void_pebble: (ForagingSource(regions=(SVERegion.crimson_badlands,), other_requirements=(CombatRequirement(Performance.great),)),), + ModLoot.void_shard: (ForagingSource(regions=(SVERegion.crimson_badlands,), + other_requirements=(CombatRequirement(Performance.galaxy), SkillRequirement(Skill.combat, 10), YearRequirement(3),)),), + SVEWaterItem.dulse_seaweed: (ForagingSource(regions=(Region.beach,), other_requirements=(FishingRequirement(Region.beach),)),), + # Fable Reef WaterItem.coral: (ForagingSource(regions=(SVERegion.fable_reef,)),), Forageable.rainbow_shell: (ForagingSource(regions=(SVERegion.fable_reef,)),), WaterItem.sea_urchin: (ForagingSource(regions=(SVERegion.fable_reef,)),), + + # Crops + SVESeed.shrub: (ForagingSource(regions=(Region.secret_woods,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEFruit.salal_berry: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=SVESeed.shrub, seasons=(Season.spring,)),), + SVESeed.slime: (ForagingSource(regions=(SVERegion.highlands_outside,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEFruit.slime_berry: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=SVESeed.slime, seasons=(Season.spring,)),), + SVESeed.ancient_fern: (ForagingSource(regions=(Region.secret_woods,)),), + SVEVegetable.ancient_fiber: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=SVESeed.ancient_fern, seasons=(Season.summer,)),), + SVESeed.stalk: (ForagingSource(regions=(SVERegion.highlands_outside,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEFruit.monster_fruit: (Tag(ItemTag.FRUIT), HarvestCropSource(seed=SVESeed.stalk, seasons=(Season.summer,)),), + SVESeed.fungus: (ForagingSource(regions=(SVERegion.highlands_pond,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEVegetable.monster_mushroom: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=SVESeed.fungus, seasons=(Season.fall,)),), + SVESeed.void: (ForagingSource(regions=(SVERegion.highlands_cavern,), other_requirements=(CombatRequirement(Performance.good),)),), + SVEVegetable.void_root: (Tag(ItemTag.VEGETABLE), HarvestCropSource(seed=SVESeed.void, seasons=(Season.winter,)),), + }, fishes=( fish_data.baby_lunaloo, # Removed when no ginger island diff --git a/worlds/stardew_valley/content/vanilla/pelican_town.py b/worlds/stardew_valley/content/vanilla/pelican_town.py index 917e8cca22..220b46eae2 100644 --- a/worlds/stardew_valley/content/vanilla/pelican_town.py +++ b/worlds/stardew_valley/content/vanilla/pelican_town.py @@ -229,7 +229,7 @@ pelican_town = ContentPack( ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.mapping_cave_systems: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=Region.adventurer_guild_bedroom), + GenericSource(regions=(Region.adventurer_guild_bedroom,)), ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.monster_compendium: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), @@ -243,12 +243,12 @@ pelican_town = ContentPack( ShopSource(money_price=3000, shop_region=LogicRegion.bookseller_2),), Book.the_alleyway_buffet: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=Region.town, + GenericSource(regions=(Region.town,), other_requirements=(ToolRequirement(Tool.axe, ToolMaterial.iron), ToolRequirement(Tool.pickaxe, ToolMaterial.iron))), ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),), Book.the_art_o_crabbing: ( Tag(ItemTag.BOOK, ItemTag.BOOK_POWER), - GenericSource(regions=Region.beach, + GenericSource(regions=(Region.beach,), other_requirements=(ToolRequirement(Tool.fishing_rod, ToolMaterial.iridium), SkillRequirement(Skill.fishing, 6), SeasonRequirement(Season.winter))), diff --git a/worlds/stardew_valley/data/fish_data.py b/worlds/stardew_valley/data/fish_data.py index c6f0c30d41..26b1a0d58a 100644 --- a/worlds/stardew_valley/data/fish_data.py +++ b/worlds/stardew_valley/data/fish_data.py @@ -46,7 +46,8 @@ pirate_cove = (Region.pirate_cove,) crimson_badlands = (SVERegion.crimson_badlands,) shearwater = (SVERegion.shearwater,) -highlands = (SVERegion.highlands_outside,) +highlands_pond = (SVERegion.highlands_pond,) +highlands_cave = (SVERegion.highlands_cavern,) sprite_spring = (SVERegion.sprite_spring,) fable_reef = (SVERegion.fable_reef,) vineyard = (SVERegion.blue_moon_vineyard,) @@ -133,9 +134,9 @@ bonefish = create_fish(SVEFish.bonefish, crimson_badlands, season.all_seasons, 7 bull_trout = create_fish(SVEFish.bull_trout, forest_river, season.not_spring, 45, mod_name=ModNames.sve) butterfish = create_fish(SVEFish.butterfish, shearwater, season.not_winter, 75, mod_name=ModNames.sve) clownfish = create_fish(SVEFish.clownfish, ginger_island_ocean, season.all_seasons, 45, mod_name=ModNames.sve) -daggerfish = create_fish(SVEFish.daggerfish, highlands, season.all_seasons, 50, mod_name=ModNames.sve) +daggerfish = create_fish(SVEFish.daggerfish, highlands_pond, season.all_seasons, 50, mod_name=ModNames.sve) frog = create_fish(SVEFish.frog, mountain_lake, (season.spring, season.summer), 70, mod_name=ModNames.sve) -gemfish = create_fish(SVEFish.gemfish, highlands, season.all_seasons, 100, mod_name=ModNames.sve) +gemfish = create_fish(SVEFish.gemfish, highlands_cave, season.all_seasons, 100, mod_name=ModNames.sve) goldenfish = create_fish(SVEFish.goldenfish, sprite_spring, season.all_seasons, 60, mod_name=ModNames.sve) grass_carp = create_fish(SVEFish.grass_carp, secret_woods, (season.spring, season.summer), 85, mod_name=ModNames.sve) king_salmon = create_fish(SVEFish.king_salmon, forest_river, (season.spring, season.summer), 80, mod_name=ModNames.sve) diff --git a/worlds/stardew_valley/data/locations.csv b/worlds/stardew_valley/data/locations.csv index 242d00b445..0d7a10f954 100644 --- a/worlds/stardew_valley/data/locations.csv +++ b/worlds/stardew_valley/data/locations.csv @@ -2900,7 +2900,6 @@ id,region,name,tags,mod_name 7055,Abandoned Mines - 3,Abandoned Treasure - Floor 3,MANDATORY,Boarding House and Bus Stop Extension 7056,Abandoned Mines - 4,Abandoned Treasure - Floor 4,MANDATORY,Boarding House and Bus Stop Extension 7057,Abandoned Mines - 5,Abandoned Treasure - Floor 5,MANDATORY,Boarding House and Bus Stop Extension -7351,Farm,Read Digging Like Worms,"BOOKSANITY,BOOKSANITY_SKILL",Archaeology 7401,Farm,Cook Magic Elixir,COOKSANITY,Magic 7402,Farm,Craft Travel Core,CRAFTSANITY,Magic 7403,Farm,Craft Haste Elixir,CRAFTSANITY,Stardew Valley Expanded @@ -3280,10 +3279,10 @@ id,region,name,tags,mod_name 8237,Shipping,Shipsanity: Pterodactyl R Wing Bone,SHIPSANITY,Boarding House and Bus Stop Extension 8238,Shipping,Shipsanity: Scrap Rust,SHIPSANITY,Archaeology 8239,Shipping,Shipsanity: Rusty Path,SHIPSANITY,Archaeology -8240,Shipping,Shipsanity: Digging Like Worms,SHIPSANITY,Archaeology 8241,Shipping,Shipsanity: Digger's Delight,SHIPSANITY,Archaeology 8242,Shipping,Shipsanity: Rocky Root Coffee,SHIPSANITY,Archaeology 8243,Shipping,Shipsanity: Ancient Jello,SHIPSANITY,Archaeology 8244,Shipping,Shipsanity: Bone Fence,SHIPSANITY,Archaeology 8245,Shipping,Shipsanity: Grilled Cheese,SHIPSANITY,Binning Skill 8246,Shipping,Shipsanity: Fish Casserole,SHIPSANITY,Binning Skill +8247,Shipping,Shipsanity: Snatcher Worm,SHIPSANITY,Stardew Valley Expanded diff --git a/worlds/stardew_valley/data/recipe_data.py b/worlds/stardew_valley/data/recipe_data.py index b482468762..3123bb9243 100644 --- a/worlds/stardew_valley/data/recipe_data.py +++ b/worlds/stardew_valley/data/recipe_data.py @@ -5,7 +5,7 @@ from ..strings.animal_product_names import AnimalProduct from ..strings.artisan_good_names import ArtisanGood from ..strings.craftable_names import ModEdible, Edible from ..strings.crop_names import Fruit, Vegetable, SVEFruit, DistantLandsCrop -from ..strings.fish_names import Fish, SVEFish, WaterItem, DistantLandsFish +from ..strings.fish_names import Fish, SVEFish, WaterItem, DistantLandsFish, SVEWaterItem from ..strings.flower_names import Flower from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable, Mushroom from ..strings.ingredient_names import Ingredient @@ -195,7 +195,7 @@ mixed_berry_pie = shop_recipe(SVEMeal.mixed_berry_pie, Region.saloon, 3500, {Fru ModNames.sve) mushroom_berry_rice = friendship_and_shop_recipe(SVEMeal.mushroom_berry_rice, ModNPC.marlon, 6, Region.adventurer_guild, 1500, {SVEForage.poison_mushroom: 3, SVEForage.red_baneberry: 10, Ingredient.rice: 1, Ingredient.sugar: 2}, ModNames.sve) -seaweed_salad = shop_recipe(SVEMeal.seaweed_salad, Region.fish_shop, 1250, {SVEFish.dulse_seaweed: 2, WaterItem.seaweed: 2, Ingredient.oil: 1}, ModNames.sve) +seaweed_salad = shop_recipe(SVEMeal.seaweed_salad, Region.fish_shop, 1250, {SVEWaterItem.dulse_seaweed: 2, WaterItem.seaweed: 2, Ingredient.oil: 1}, ModNames.sve) void_delight = friendship_and_shop_recipe(SVEMeal.void_delight, NPC.krobus, 10, Region.sewer, 5000, {SVEFish.void_eel: 1, Loot.void_essence: 50, Loot.solar_essence: 20}, ModNames.sve) void_salmon_sushi = friendship_and_shop_recipe(SVEMeal.void_salmon_sushi, NPC.krobus, 10, Region.sewer, 5000, diff --git a/worlds/stardew_valley/data/requirement.py b/worlds/stardew_valley/data/requirement.py index 4744f9dffd..b2416d8d0b 100644 --- a/worlds/stardew_valley/data/requirement.py +++ b/worlds/stardew_valley/data/requirement.py @@ -31,6 +31,27 @@ class YearRequirement(Requirement): year: int +@dataclass(frozen=True) +class CombatRequirement(Requirement): + level: str + + +@dataclass(frozen=True) +class QuestRequirement(Requirement): + quest: str + + +@dataclass(frozen=True) +class RelationshipRequirement(Requirement): + npc: str + hearts: int + + +@dataclass(frozen=True) +class FishingRequirement(Requirement): + region: str + + @dataclass(frozen=True) class WalnutRequirement(Requirement): amount: int diff --git a/worlds/stardew_valley/data/shop.py b/worlds/stardew_valley/data/shop.py index ca54d35e14..f14dbac821 100644 --- a/worlds/stardew_valley/data/shop.py +++ b/worlds/stardew_valley/data/shop.py @@ -16,8 +16,8 @@ class ShopSource(ItemSource): other_requirements: Tuple[Requirement, ...] = () def __post_init__(self): - assert self.money_price or self.items_price, "At least money price or items price need to be defined." - assert self.items_price is None or all(type(p) == tuple for p in self.items_price), "Items price should be a tuple." + assert self.money_price is not None or self.items_price is not None, "At least money price or items price need to be defined." + assert self.items_price is None or all(isinstance(p, tuple) for p in self.items_price), "Items price should be a tuple." @dataclass(frozen=True, **kw_only) diff --git a/worlds/stardew_valley/logic/requirement_logic.py b/worlds/stardew_valley/logic/requirement_logic.py index 9356440ac6..6a5adf4890 100644 --- a/worlds/stardew_valley/logic/requirement_logic.py +++ b/worlds/stardew_valley/logic/requirement_logic.py @@ -3,15 +3,20 @@ from typing import Union, Iterable from .base_logic import BaseLogicMixin, BaseLogic from .book_logic import BookLogicMixin +from .combat_logic import CombatLogicMixin +from .fishing_logic import FishingLogicMixin from .has_logic import HasLogicMixin +from .quest_logic import QuestLogicMixin from .received_logic import ReceivedLogicMixin +from .relationship_logic import RelationshipLogicMixin from .season_logic import SeasonLogicMixin from .skill_logic import SkillLogicMixin from .time_logic import TimeLogicMixin from .tool_logic import ToolLogicMixin from .walnut_logic import WalnutLogicMixin from ..data.game_item import Requirement -from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, WalnutRequirement +from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, CombatRequirement, QuestRequirement, \ + RelationshipRequirement, FishingRequirement, WalnutRequirement class RequirementLogicMixin(BaseLogicMixin): @@ -21,7 +26,7 @@ class RequirementLogicMixin(BaseLogicMixin): class RequirementLogic(BaseLogic[Union[RequirementLogicMixin, HasLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, BookLogicMixin, -SeasonLogicMixin, TimeLogicMixin, WalnutLogicMixin]]): +SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, RelationshipLogicMixin, FishingLogicMixin, WalnutLogicMixin]]): def meet_all_requirements(self, requirements: Iterable[Requirement]): if not requirements: @@ -55,3 +60,21 @@ SeasonLogicMixin, TimeLogicMixin, WalnutLogicMixin]]): @meet_requirement.register def _(self, requirement: WalnutRequirement): return self.logic.walnut.has_walnut(requirement.amount) + + @meet_requirement.register + def _(self, requirement: CombatRequirement): + return self.logic.combat.can_fight_at_level(requirement.level) + + @meet_requirement.register + def _(self, requirement: QuestRequirement): + return self.logic.quest.can_complete_quest(requirement.quest) + + @meet_requirement.register + def _(self, requirement: RelationshipRequirement): + return self.logic.relationship.has_hearts(requirement.npc, requirement.hearts) + + @meet_requirement.register + def _(self, requirement: FishingRequirement): + return self.logic.fishing.can_fish_at(requirement.region) + + diff --git a/worlds/stardew_valley/mods/logic/item_logic.py b/worlds/stardew_valley/mods/logic/item_logic.py index cfafc88e83..ef5eab0134 100644 --- a/worlds/stardew_valley/mods/logic/item_logic.py +++ b/worlds/stardew_valley/mods/logic/item_logic.py @@ -23,24 +23,15 @@ from ...logic.tool_logic import ToolLogicMixin from ...options import Cropsanity from ...stardew_rule import StardewRule, True_ from ...strings.artisan_good_names import ModArtisanGood -from ...strings.craftable_names import ModCraftable, ModEdible, ModMachine -from ...strings.crop_names import SVEVegetable, SVEFruit, DistantLandsCrop -from ...strings.fish_names import ModTrash, SVEFish -from ...strings.food_names import SVEMeal, SVEBeverage -from ...strings.forageable_names import SVEForage, DistantLandsForageable -from ...strings.gift_names import SVEGift +from ...strings.craftable_names import ModCraftable, ModMachine +from ...strings.fish_names import ModTrash from ...strings.ingredient_names import Ingredient from ...strings.material_names import Material from ...strings.metal_names import all_fossils, all_artifacts, Ore, ModFossil -from ...strings.monster_drop_names import ModLoot, Loot +from ...strings.monster_drop_names import Loot from ...strings.performance_names import Performance -from ...strings.quest_names import ModQuest -from ...strings.region_names import Region, SVERegion, DeepWoodsRegion, BoardingHouseRegion -from ...strings.season_names import Season -from ...strings.seed_names import SVESeed, DistantLandsSeed -from ...strings.skill_names import Skill +from ...strings.region_names import SVERegion, DeepWoodsRegion, BoardingHouseRegion from ...strings.tool_names import Tool, ToolMaterial -from ...strings.villager_names import ModNPC display_types = [ModCraftable.wooden_display, ModCraftable.hardwood_display] display_items = all_artifacts + all_fossils @@ -58,12 +49,6 @@ FarmingLogicMixin]]): def get_modded_item_rules(self) -> Dict[str, StardewRule]: items = dict() - if ModNames.sve in self.options.mods: - items.update(self.get_sve_item_rules()) - if ModNames.archaeology in self.options.mods: - items.update(self.get_archaeology_item_rules()) - if ModNames.distant_lands in self.options.mods: - items.update(self.get_distant_lands_item_rules()) if ModNames.boarding_house in self.options.mods: items.update(self.get_boarding_house_item_rules()) return items @@ -75,61 +60,6 @@ FarmingLogicMixin]]): item_rule.update(self.get_modified_item_rules_for_deep_woods(item_rule)) return item_rule - def get_sve_item_rules(self): - return {SVEGift.aged_blue_moon_wine: self.logic.money.can_spend_at(SVERegion.sophias_house, 28000), - SVEGift.blue_moon_wine: self.logic.money.can_spend_at(SVERegion.sophias_house, 3000), - SVESeed.fungus: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon, - ModLoot.green_mushroom: self.logic.region.can_reach(SVERegion.highlands_outside) & - self.logic.tool.has_tool(Tool.axe, ToolMaterial.iron) & self.logic.season.has_any_not_winter(), - SVEFruit.monster_fruit: self.logic.season.has(Season.summer) & self.logic.has(SVESeed.stalk), - SVEVegetable.monster_mushroom: self.logic.season.has(Season.fall) & self.logic.has(SVESeed.fungus), - ModLoot.ornate_treasure_chest: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_galaxy_weapon & - self.logic.tool.has_tool(Tool.axe, ToolMaterial.iron), - SVEFruit.slime_berry: self.logic.season.has(Season.spring) & self.logic.has(SVESeed.slime), - SVESeed.slime: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon, - SVESeed.stalk: self.logic.region.can_reach(SVERegion.highlands_outside) & self.logic.combat.has_good_weapon, - ModLoot.swirl_stone: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon, - SVEVegetable.void_root: self.logic.season.has(Season.winter) & self.logic.has(SVESeed.void), - SVESeed.void: self.logic.region.can_reach(SVERegion.highlands_cavern) & self.logic.combat.has_good_weapon, - ModLoot.void_soul: self.logic.region.can_reach( - SVERegion.crimson_badlands) & self.logic.combat.has_good_weapon & self.logic.cooking.can_cook(), - SVEForage.winter_star_rose: self.logic.region.can_reach(SVERegion.summit) & self.logic.season.has(Season.winter), - SVEForage.bearberry: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.winter), - SVEForage.poison_mushroom: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has_any([Season.summer, Season.fall]), - SVEForage.red_baneberry: self.logic.region.can_reach(Region.secret_woods) & self.logic.season.has(Season.summer), - SVEForage.ferngill_primrose: self.logic.region.can_reach(SVERegion.summit) & self.logic.season.has(Season.spring), - SVEForage.goldenrod: self.logic.region.can_reach(SVERegion.summit) & ( - self.logic.season.has(Season.summer) | self.logic.season.has(Season.fall)), - SVESeed.shrub: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic), - SVEFruit.salal_berry: self.logic.farming.can_plant_and_grow_item((Season.spring, Season.summer)) & self.logic.has(SVESeed.shrub), - ModEdible.aegis_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 28000), - ModEdible.lightning_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 12000), - ModEdible.barbarian_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 22000), - ModEdible.gravity_elixir: self.logic.money.can_spend_at(SVERegion.galmoran_outpost, 4000), - SVESeed.ancient_fern: self.logic.region.can_reach(Region.secret_woods) & self.logic.tool.has_tool(Tool.hoe, ToolMaterial.basic), - SVEVegetable.ancient_fiber: self.logic.farming.can_plant_and_grow_item(Season.summer) & self.logic.has(SVESeed.ancient_fern), - SVEForage.conch: self.logic.region.can_reach_any((Region.beach, SVERegion.fable_reef)), - SVEForage.dewdrop_berry: self.logic.region.can_reach(SVERegion.enchanted_grove), - SVEForage.sand_dollar: self.logic.region.can_reach(SVERegion.fable_reef) | (self.logic.region.can_reach(Region.beach) & - self.logic.season.has_any([Season.summer, Season.fall])), - SVEForage.golden_ocean_flower: self.logic.region.can_reach(SVERegion.fable_reef), - SVEMeal.grampleton_orange_chicken: self.logic.money.can_spend_at(Region.saloon, 650) & self.logic.relationship.has_hearts(ModNPC.sophia, 6), - ModEdible.hero_elixir: self.logic.money.can_spend_at(SVERegion.isaac_shop, 8000), - SVEForage.four_leaf_clover: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.forest_west)) & - self.logic.season.has_any([Season.spring, Season.summer]), - SVEForage.mushroom_colony: self.logic.region.can_reach_any((Region.secret_woods, SVERegion.junimo_woods, SVERegion.forest_west)) & - self.logic.season.has(Season.fall), - SVEForage.rusty_blade: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon, - SVEForage.rafflesia: self.logic.region.can_reach(Region.secret_woods), - SVEBeverage.sports_drink: self.logic.money.can_spend_at(Region.hospital, 750), - "Stamina Capsule": self.logic.money.can_spend_at(Region.hospital, 4000), - SVEForage.thistle: self.logic.region.can_reach(SVERegion.summit), - ModLoot.void_pebble: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_great_weapon, - ModLoot.void_shard: self.logic.region.can_reach(SVERegion.crimson_badlands) & self.logic.combat.has_galaxy_weapon & - self.logic.skill.has_level(Skill.combat, 10) & self.logic.region.can_reach(Region.saloon) & self.logic.time.has_year_three - } - # @formatter:on - def get_modified_item_rules_for_sve(self, items: Dict[str, StardewRule]): return { Loot.void_essence: items[Loot.void_essence] | self.logic.region.can_reach(SVERegion.highlands_cavern) | self.logic.region.can_reach( @@ -141,7 +71,7 @@ FarmingLogicMixin]]): self.logic.combat.can_fight_at_level(Performance.great)), Ore.iridium: items[Ore.iridium] | (self.logic.tool.can_use_tool_at(Tool.pickaxe, ToolMaterial.basic, SVERegion.crimson_badlands) & self.logic.combat.can_fight_at_level(Performance.maximum)), - SVEFish.dulse_seaweed: self.logic.fishing.can_fish_at(Region.beach) & self.logic.season.has_any([Season.spring, Season.summer, Season.winter]) + } def get_modified_item_rules_for_deep_woods(self, items: Dict[str, StardewRule]): @@ -160,36 +90,6 @@ FarmingLogicMixin]]): return options_to_update - def get_archaeology_item_rules(self): - archaeology_item_rules = {} - preservation_chamber_rule = self.logic.has(ModMachine.preservation_chamber) - hardwood_preservation_chamber_rule = self.logic.has(ModMachine.hardwood_preservation_chamber) - for item in display_items: - for display_type in display_types: - if item == "Trilobite": - location_name = f"{display_type}: Trilobite Fossil" - else: - location_name = f"{display_type}: {item}" - display_item_rule = self.logic.crafting.can_craft(all_crafting_recipes_by_name[display_type]) & self.logic.has(item) - if "Wooden" in display_type: - archaeology_item_rules[location_name] = display_item_rule & preservation_chamber_rule - else: - archaeology_item_rules[location_name] = display_item_rule & hardwood_preservation_chamber_rule - archaeology_item_rules[ModTrash.rusty_scrap] = self.logic.has(ModMachine.grinder) & self.logic.has_any(*all_artifacts) - return archaeology_item_rules - - def get_distant_lands_item_rules(self): - return { - DistantLandsForageable.swamp_herb: self.logic.region.can_reach(Region.witch_swamp), - DistantLandsForageable.brown_amanita: self.logic.region.can_reach(Region.witch_swamp), - DistantLandsSeed.vile_ancient_fruit: self.logic.quest.can_complete_quest(ModQuest.WitchOrder) | self.logic.quest.can_complete_quest( - ModQuest.CorruptedCropsTask), - DistantLandsSeed.void_mint: self.logic.quest.can_complete_quest(ModQuest.WitchOrder) | self.logic.quest.can_complete_quest( - ModQuest.CorruptedCropsTask), - DistantLandsCrop.void_mint: self.logic.season.has_any_not_winter() & self.logic.has(DistantLandsSeed.void_mint), - DistantLandsCrop.vile_ancient_fruit: self.logic.season.has_any_not_winter() & self.logic.has(DistantLandsSeed.vile_ancient_fruit), - } - def get_boarding_house_item_rules(self): return { # Mob Drops from lost valley enemies @@ -251,8 +151,3 @@ FarmingLogicMixin]]): BoardingHouseRegion.lost_valley_house_2,)) & self.logic.combat.can_fight_at_level( Performance.great), } - - def has_seed_unlocked(self, seed_name: str): - if self.options.cropsanity == Cropsanity.option_disabled: - return True_() - return self.logic.received(seed_name) diff --git a/worlds/stardew_valley/mods/mod_regions.py b/worlds/stardew_valley/mods/mod_regions.py index c075bd4d10..a402ba6068 100644 --- a/worlds/stardew_valley/mods/mod_regions.py +++ b/worlds/stardew_valley/mods/mod_regions.py @@ -183,7 +183,8 @@ stardew_valley_expanded_regions = [ RegionData(SVERegion.first_slash_guild, [SVEEntrance.first_slash_guild_to_hallway], is_ginger_island=True), RegionData(SVERegion.first_slash_hallway, [SVEEntrance.first_slash_hallway_to_room], is_ginger_island=True), RegionData(SVERegion.first_slash_spare_room, is_ginger_island=True), - RegionData(SVERegion.highlands_outside, [SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave], is_ginger_island=True), + RegionData(SVERegion.highlands_outside, [SVEEntrance.highlands_to_lance, SVEEntrance.highlands_to_cave, SVEEntrance.highlands_to_pond], is_ginger_island=True), + RegionData(SVERegion.highlands_pond, is_ginger_island=True), RegionData(SVERegion.highlands_cavern, [SVEEntrance.to_dwarf_prison], is_ginger_island=True), RegionData(SVERegion.dwarf_prison, is_ginger_island=True), RegionData(SVERegion.lances_house, [SVEEntrance.lance_to_ladder], is_ginger_island=True), @@ -276,6 +277,7 @@ mandatory_sve_connections = [ ConnectionData(SVEEntrance.sprite_spring_to_cave, SVERegion.sprite_spring_cave, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.fish_shop_to_willy_bedroom, SVERegion.willy_bedroom, flag=RandomizationFlag.BUILDINGS), ConnectionData(SVEEntrance.museum_to_gunther_bedroom, SVERegion.gunther_bedroom, flag=RandomizationFlag.BUILDINGS), + ConnectionData(SVEEntrance.highlands_to_pond, SVERegion.highlands_pond), ] alecto_regions = [ diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index 7c1fdbda3c..89b1cf87c3 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -1031,6 +1031,7 @@ def set_sve_ginger_island_rules(logic: StardewLogic, multiworld: MultiWorld, pla set_entrance_rule(multiworld, player, SVEEntrance.wizard_to_fable_reef, logic.received(SVEQuestItem.fable_reef_portal)) set_entrance_rule(multiworld, player, SVEEntrance.highlands_to_cave, logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron) & logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) + set_entrance_rule(multiworld, player, SVEEntrance.highlands_to_pond, logic.tool.has_tool(Tool.axe, ToolMaterial.iron)) def set_boarding_house_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): diff --git a/worlds/stardew_valley/strings/book_names.py b/worlds/stardew_valley/strings/book_names.py index 3c32cd81b3..6c271f42ae 100644 --- a/worlds/stardew_valley/strings/book_names.py +++ b/worlds/stardew_valley/strings/book_names.py @@ -27,10 +27,6 @@ class Book: the_diamond_hunter = "The Diamond Hunter" -class ModBook: - digging_like_worms = "Digging Like Worms" - - ordered_lost_books = [] all_lost_books = set() diff --git a/worlds/stardew_valley/strings/entrance_names.py b/worlds/stardew_valley/strings/entrance_names.py index 9b651f4276..58a919f2a8 100644 --- a/worlds/stardew_valley/strings/entrance_names.py +++ b/worlds/stardew_valley/strings/entrance_names.py @@ -358,6 +358,7 @@ class SVEEntrance: sprite_spring_to_cave = "Sprite Spring to Sprite Spring Cave" fish_shop_to_willy_bedroom = "Willy's Fish Shop to Willy's Bedroom" museum_to_gunther_bedroom = "Museum to Gunther's Bedroom" + highlands_to_pond = "Highlands to Highlands Pond" class AlectoEntrance: diff --git a/worlds/stardew_valley/strings/fish_names.py b/worlds/stardew_valley/strings/fish_names.py index d94f9e2fd4..d4ee81430e 100644 --- a/worlds/stardew_valley/strings/fish_names.py +++ b/worlds/stardew_valley/strings/fish_names.py @@ -137,7 +137,6 @@ class SVEFish: void_eel = "Void Eel" water_grub = "Water Grub" sea_sponge = "Sea Sponge" - dulse_seaweed = "Dulse Seaweed" class DistantLandsFish: @@ -147,6 +146,10 @@ class DistantLandsFish: giant_horsehoe_crab = "Giant Horsehoe Crab" +class SVEWaterItem: + dulse_seaweed = "Dulse Seaweed" + + class ModTrash: rusty_scrap = "Scrap Rust" diff --git a/worlds/stardew_valley/strings/food_names.py b/worlds/stardew_valley/strings/food_names.py index 5555316f83..03784336d1 100644 --- a/worlds/stardew_valley/strings/food_names.py +++ b/worlds/stardew_valley/strings/food_names.py @@ -102,6 +102,7 @@ class SVEMeal: void_delight = "Void Delight" void_salmon_sushi = "Void Salmon Sushi" grampleton_orange_chicken = "Grampleton Orange Chicken" + stamina_capsule = "Stamina Capsule" class TrashyMeal: diff --git a/worlds/stardew_valley/strings/region_names.py b/worlds/stardew_valley/strings/region_names.py index 9cedb6b8ef..58763b6fcb 100644 --- a/worlds/stardew_valley/strings/region_names.py +++ b/worlds/stardew_valley/strings/region_names.py @@ -296,6 +296,7 @@ class SVERegion: sprite_spring_cave = "Sprite Spring Cave" willy_bedroom = "Willy's Bedroom" gunther_bedroom = "Gunther's Bedroom" + highlands_pond = "Highlands Pond" class AlectoRegion: diff --git a/worlds/stardew_valley/test/mods/TestMods.py b/worlds/stardew_valley/test/mods/TestMods.py index 5e7e9d4143..97184b1338 100644 --- a/worlds/stardew_valley/test/mods/TestMods.py +++ b/worlds/stardew_valley/test/mods/TestMods.py @@ -14,7 +14,8 @@ class TestGenerateModsOptions(WorldAssertMixin, ModAssertMixin, SVTestCase): def test_given_single_mods_when_generate_then_basic_checks(self): for mod in options.Mods.valid_keys: - with self.solo_world_sub_test(f"Mod: {mod}", {options.Mods: mod}) as (multi_world, _): + world_options = {options.Mods: mod, options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false} + with self.solo_world_sub_test(f"Mod: {mod}", world_options) as (multi_world, _): self.assert_basic_checks(multi_world) self.assert_stray_mod_items(mod, multi_world) @@ -22,8 +23,9 @@ class TestGenerateModsOptions(WorldAssertMixin, ModAssertMixin, SVTestCase): for option in options.EntranceRandomization.options: for mod in options.Mods.valid_keys: world_options = { - options.EntranceRandomization.internal_name: options.EntranceRandomization.options[option], - options.Mods: mod + options.EntranceRandomization: options.EntranceRandomization.options[option], + options.Mods: mod, + options.ExcludeGingerIsland: options.ExcludeGingerIsland.option_false } with self.solo_world_sub_test(f"entrance_randomization: {option}, Mod: {mod}", world_options) as (multi_world, _): self.assert_basic_checks(multi_world) From b019485944543e8b1cb440c572f27b6a592a0dd8 Mon Sep 17 00:00:00 2001 From: CookieCat <81494827+CookieCat45@users.noreply.github.com> Date: Thu, 25 Jul 2024 03:27:22 -0400 Subject: [PATCH 10/21] AHIT: Update Setup Guide (#3647) --- worlds/ahit/docs/setup_en.md | 61 +++++++----------------------------- 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/worlds/ahit/docs/setup_en.md b/worlds/ahit/docs/setup_en.md index 509869fc25..23b3490707 100644 --- a/worlds/ahit/docs/setup_en.md +++ b/worlds/ahit/docs/setup_en.md @@ -12,41 +12,29 @@ ## Instructions -1. Have Steam running. Open the Steam console with this link: [steam://open/console](steam://open/console) -This may not work for some browsers. If that's the case, and you're on Windows, open the Run dialog using Win+R, -paste the link into the box, and hit Enter. +1. **BACK UP YOUR SAVE FILES IN YOUR MAIN INSTALL IF YOU CARE ABOUT THEM!!!** + Go to `steamapps/common/HatinTime/HatinTimeGame/SaveData/` and copy everything inside that folder over to a safe place. + **This is important! Changing the game version CAN and WILL break your existing save files!!!** -2. In the Steam console, enter the following command: -`download_depot 253230 253232 7770543545116491859`. ***Wait for the console to say the download is finished!*** -This can take a while to finish (30+ minutes) depending on your connection speed, so please be patient. Additionally, -**try to prevent your connection from being interrupted or slowed while Steam is downloading the depot,** -or else the download may potentially become corrupted (see first FAQ issue below). +2. In your Steam library, right-click on **A Hat in Time** in the list of games and click on **Properties**. -3. Once the download finishes, go to `steamapps/content/app_253230` in Steam's program folder. +3. Click the **Betas** tab. In the **Beta Participation** dropdown, select `tcplink`. + While it downloads, you can subscribe to the [Archipelago workshop mod.]((https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601)) -4. There should be a folder named `depot_253232`. Rename it to HatinTime_AP and move it to your `steamapps/common` folder. +4. Once the game finishes downloading, start it up. + In Game Settings, make sure **Enable Developer Console** is checked. -5. In the HatinTime_AP folder, navigate to `Binaries/Win64` and create a new file: `steam_appid.txt`. -In this new text file, input the number **253230** on the first line. - - -6. Create a shortcut of `HatinTimeGame.exe` from that folder and move it to wherever you'd like. -You will use this shortcut to open the Archipelago-compatible version of A Hat in Time. - - -7. Start up the game using your new shortcut. To confirm if you are on the correct version, -go to Settings -> Game Settings. If you don't see an option labelled ***Live Game Events*** you should be running -the correct version of the game. In Game Settings, make sure ***Enable Developer Console*** is checked. +5. You should now be good to go. See below for more details on how to use the mod and connect to an Archipelago game. ## Connecting to the Archipelago server -To connect to the multiworld server, simply run the **ArchipelagoAHITClient** -(or run it from the Launcher if you have the apworld installed) and connect it to the Archipelago server. +To connect to the multiworld server, simply run the **Archipelago AHIT Client** from the Launcher +and connect it to the Archipelago server. The game will connect to the client automatically when you create a new save file. @@ -61,33 +49,8 @@ make sure ***Enable Developer Console*** is checked in Game Settings and press t ## FAQ/Common Issues -### I followed the setup, but I receive an odd error message upon starting the game or creating a save file! -If you receive an error message such as -**"Failed to find default engine .ini to retrieve My Documents subdirectory to use. Force quitting."** or -**"Failed to load map "hub_spaceship"** after booting up the game or creating a save file respectively, then the depot -download was likely corrupted. The only way to fix this is to start the entire download all over again. -Unfortunately, this appears to be an underlying issue with Steam's depot downloader. The only way to really prevent this -from happening is to ensure that your connection is not interrupted or slowed while downloading. -### The game keeps crashing on startup after the splash screen! -This issue is unfortunately very hard to fix, and the underlying cause is not known. If it does happen however, -try the following: - -- Close Steam **entirely**. -- Open the downpatched version of the game (with Steam closed) and allow it to load to the titlescreen. -- Close the game, and then open Steam again. -- After launching the game, the issue should hopefully disappear. If not, repeat the above steps until it does. - -### I followed the setup, but "Live Game Events" still shows up in the options menu! -The most common cause of this is the `steam_appid.txt` file. If you're on Windows 10, file extensions are hidden by -default (thanks Microsoft). You likely made the mistake of still naming the file `steam_appid.txt`, which, since file -extensions are hidden, would result in the file being named `steam_appid.txt.txt`, which is incorrect. -To show file extensions in Windows 10, open any folder, click the View tab at the top, and check -"File name extensions". Then you can correct the name of the file. If the name of the file is correct, -and you're still running into the issue, re-read the setup guide again in case you missed a step. -If you still can't get it to work, ask for help in the Discord thread. - -### The game is running on the older version, but it's not connecting when starting a new save! +### The game is not connecting when starting a new save! For unknown reasons, the mod will randomly disable itself in the mod menu. To fix this, go to the Mods menu (rocket icon) in-game, and re-enable the mod. From 5fb1ebdcfd4a9adaaf7d655492069649c9de0482 Mon Sep 17 00:00:00 2001 From: Tsukino <16899482+Tsukino-uwu@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:30:23 +0200 Subject: [PATCH 11/21] Docs: Add Swedish Guide for Pokemon Emerald (#3252) * Docs: Add Swedish Guide for Pokemon Emerald Swedish Translation * v2 some proof reading & clarification changes * v3 * v4 * v5 typo * v6 * Update worlds/pokemon_emerald/docs/setup_sv.md Co-authored-by: Bryce Wilson * Update worlds/pokemon_emerald/docs/setup_sv.md Co-authored-by: Bryce Wilson * v7 Tried to reduce the length of lines, this should still convey the same message/meaning * typo * v8 Removed Leading/Trailing Spaces * typo v2 * Added a couple of full stops. * lowercase typos * Update setup_sv.md * Apply suggestions from code review Co-authored-by: Bryce Wilson --------- Co-authored-by: Bryce Wilson Co-authored-by: bittersweetrin --- worlds/pokemon_emerald/__init__.py | 11 +++- worlds/pokemon_emerald/docs/setup_sv.md | 78 +++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 worlds/pokemon_emerald/docs/setup_sv.md diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index aa4f6ccf75..abdee26f57 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -52,8 +52,17 @@ class PokemonEmeraldWebWorld(WebWorld): "setup/es", ["nachocua"] ) + + setup_sv = Tutorial( + "Multivärld Installations Guide", + "En guide för att kunna spela Pokémon Emerald med Archipelago.", + "Svenska", + "setup_sv.md", + "setup/sv", + ["Tsukino"] + ) - tutorials = [setup_en, setup_es] + tutorials = [setup_en, setup_es, setup_sv] class PokemonEmeraldSettings(settings.Group): diff --git a/worlds/pokemon_emerald/docs/setup_sv.md b/worlds/pokemon_emerald/docs/setup_sv.md new file mode 100644 index 0000000000..88b1d38409 --- /dev/null +++ b/worlds/pokemon_emerald/docs/setup_sv.md @@ -0,0 +1,78 @@ +# Pokémon Emerald Installationsguide + +## Programvara som behövs + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Ett engelskt Pokémon Emerald ROM, Archipelago kan inte hjälpa dig med detta. +- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 eller senare + +### Konfigurera BizHawk + +När du har installerat BizHawk, öppna `EmuHawk.exe` och ändra följande inställningar: + +- Om du använder BizHawk 2.7 eller 2.8, gå till `Config > Customize`. På "Advanced Tab", byt Lua core från +`NLua+KopiLua` till `Lua+LuaInterface`, starta om EmuHawk efteråt. (Använder du BizHawk 2.9, kan du skippa detta steg.) +- Gå till `Config > Customize`. Markera "Run in background" inställningen för att förhindra bortkoppling från +klienten om du alt-tabbar bort från EmuHawk. +- Öppna en `.gba` fil i EmuHawk och gå till `Config > Controllers…` för att konfigurera dina inputs. +Om du inte hittar `Controllers…`, starta ett valfritt `.gba` ROM först. +- Överväg att rensa keybinds i `Config > Hotkeys…` som du inte tänkt använda. Välj en keybind och tryck på ESC +för att rensa bort den. + +## Extra programvara + +- [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest), +används tillsammans med +[PopTracker](https://github.com/black-sliver/PopTracker/releases) + +## Generera och patcha ett spel + +1. Skapa din konfigurationsfil (YAML). Du kan göra en via att använda +[Pokémon Emerald options hemsida](../../../games/Pokemon%20Emerald/player-options). +2. Följ de allmänna Archipelago instruktionerna för att +[Generera ett spel](../../Archipelago/setup/en#generating-a-game). +Detta kommer generera en fil för dig. Din patchfil kommer ha `.apemerald` som sitt filnamnstillägg. +3. Öppna `ArchipelagoLauncher.exe` +4. Välj "Open Patch" på vänstra sidan, och välj din patchfil. +5. Om detta är första gången du patchar, så kommer du behöva välja var ditt ursprungliga ROM är. +6. En patchad `.gba` fil kommer skapas på samma plats som patchfilen. +7. Första gången du öppnar en patch med BizHawk-klienten, kommer du också behöva bekräfta var `EmuHawk.exe` filen är +installerad i din BizHawk-mapp. + +Om du bara tänkt spela själv och du inte bryr dig om automatisk spårning eller ledtrådar, så kan du stanna här, stänga +av klienten, och starta ditt patchade ROM med valfri emulator. Dock, för multvärldsfunktionen eller andra +Archipelago-funktioner, fortsätt nedanför med BizHawk. + +## Anslut till en server + +Om du vanligtsvis öppnar en patchad fil så görs steg 1-5 automatiskt åt dig. Även om det är så, kom ihåg dessa steg +ifall du till exempel behöver stänga ner och starta om något medans du spelar. + +1. Pokemon Emerald använder Archipelagos BizHawk-klient. Om klienten inte startat efter att du patchat ditt spel, +så kan du bara öppna den igen från launchern. +2. Dubbelkolla att EmuHawk faktiskt startat med den patchade ROM-filen. +3. I EmuHawk, gå till `Tools > Lua Console`. Luakonsolen måste vara igång medans du spelar. +4. I Luakonsolen, Tryck på `Script > Open Script…`. +5. Leta reda på din Archipelago-mapp och i den öppna `data/lua/connector_bizhawk_generic.lua`. +6. Emulatorn och klienten kommer så småningom ansluta till varandra. I BizHawk-klienten kommer du kunna see om allt är +anslutet och att Pokemon Emerald är igenkänt. +7. För att ansluta klienten till en server, skriv in din lobbyadress och port i textfältet t.ex. +`archipelago.gg:38281` +längst upp i din klient och tryck sen på "Connect". + +Du borde nu kunna ta emot och skicka föremål. Du behöver göra dom här stegen varje gång du vill ansluta igen. Det är +helt okej att göra saker offline utan att behöva oroa sig; allt kommer att synkronisera när du ansluter till servern +igen. + +## Automatisk Spårning + +Pokémon Emerald har en fullt fungerande spårare med stöd för automatisk spårning. + +1. Ladda ner [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest) +och +[PopTracker](https://github.com/black-sliver/PopTracker/releases). +2. Placera tracker pack zip-filen i packs/ där du har PopTracker installerat. +3. Öppna PopTracker, och välj Pokemon Emerald. +4. För att automatiskt spåra, tryck på "AP" symbolen längst upp. +5. Skriv in Archipelago-serverns uppgifter (Samma som du använde för att ansluta med klienten), "Slot"-namn samt +lösenord. From 79843803cf3a2547390f6e139be9c229a77d370b Mon Sep 17 00:00:00 2001 From: qwint Date: Thu, 25 Jul 2024 16:01:22 -0500 Subject: [PATCH 12/21] Docs: Add header to FAQ doc referencing other relevant docs (#3692) * Add header to FAQ doc referencing other relevant docs * Update docs/apworld_dev_faq.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/apworld_dev_faq.md Co-authored-by: Scipio Wright --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Scipio Wright --- docs/apworld_dev_faq.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/apworld_dev_faq.md b/docs/apworld_dev_faq.md index 059c33844f..71e2e4152e 100644 --- a/docs/apworld_dev_faq.md +++ b/docs/apworld_dev_faq.md @@ -1,6 +1,8 @@ # APWorld Dev FAQ This document is meant as a reference tool to show solutions to common problems when developing an apworld. +It is not intended to answer every question about Archipelago and it assumes you have read the other docs, +including [Contributing](contributing.md), [Adding Games](), and [World API](). --- From b6e5223aa27bd77217897bcad41645b6645a6969 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:02:25 -0400 Subject: [PATCH 13/21] Docs: Expanding on the answers in the FAQ (#3690) * Expand on some existing answers * Oops * Sphere "one" * Removing while * Update docs/apworld_dev_faq.md Co-authored-by: Scipio Wright --------- Co-authored-by: Scipio Wright --- docs/apworld_dev_faq.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/apworld_dev_faq.md b/docs/apworld_dev_faq.md index 71e2e4152e..8d9429afa3 100644 --- a/docs/apworld_dev_faq.md +++ b/docs/apworld_dev_faq.md @@ -8,12 +8,18 @@ including [Contributing](contributing.md), [Adding Games](), an ### My game has a restrictive start that leads to fill errors -Hint to the Generator that an item needs to be in sphere one with local_early_items +Hint to the Generator that an item needs to be in sphere one with local_early_items. Here, `1` represents the number of "Sword" items to attempt to place in sphere one. ```py early_item_name = "Sword" self.multiworld.local_early_items[self.player][early_item_name] = 1 ``` +Some alternative ways to try to fix this problem are: +* Add more locations to sphere one of your world, potentially only when there would be a restrictive start +* Pre-place items yourself, such as during `create_items` +* Put items into the player's starting inventory using `push_precollected` +* Raise an exception, such as an `OptionError` during `generate_early`, to disallow options that would lead to a restrictive start + --- ### I have multiple settings that change the item/location pool counts and need to balance them out @@ -27,8 +33,13 @@ Note: to use self.create_filler(), self.get_filler_item_name() should be defined total_locations = len(self.multiworld.get_unfilled_locations(self.player)) item_pool = self.create_non_filler_items() -while len(item_pool) < total_locations: +for _ in range(total_locations - len(item_pool)): item_pool.append(self.create_filler()) self.multiworld.itempool += item_pool ``` + +A faster alternative to the `for` loop would be to use a [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions): +```py +item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))] +``` From d030a698a6824ec960fdcba40383d38991f82812 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 25 Jul 2024 17:09:37 -0400 Subject: [PATCH 14/21] Lingo: Changed minimum progression requirement (#3672) --- worlds/lingo/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index 3b67617873..a1b8b7c1d4 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -9,7 +9,7 @@ from worlds.AutoWorld import WebWorld, World from .datatypes import Room, RoomEntrance from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP -from .options import LingoOptions, lingo_option_groups +from .options import LingoOptions, lingo_option_groups, SunwarpAccess, VictoryCondition from .player_logic import LingoPlayerLogic from .regions import create_regions @@ -54,14 +54,17 @@ class LingoWorld(World): player_logic: LingoPlayerLogic def generate_early(self): - if not (self.options.shuffle_doors or self.options.shuffle_colors or self.options.shuffle_sunwarps): + if not (self.options.shuffle_doors or self.options.shuffle_colors or + (self.options.sunwarp_access >= SunwarpAccess.option_unlock and + self.options.victory_condition == VictoryCondition.option_pilgrimage)): if self.multiworld.players == 1: - warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression" - f" items. Please turn on Door Shuffle, Color Shuffle, or Sunwarp Shuffle if that doesn't seem" - f" right.") + warning(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on Door" + f" Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage victory condition" + f" if that doesn't seem right.") else: - raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any" - f" progression items. Please turn on Door Shuffle, Color Shuffle or Sunwarp Shuffle.") + raise OptionError(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on" + f" Door Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage" + f" victory condition.") self.player_logic = LingoPlayerLogic(self) From cc2216164489f89d78ac1e53aaa71b9dce04ac28 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Fri, 26 Jul 2024 04:53:11 -0400 Subject: [PATCH 15/21] Lingo: Add panels mode door shuffle (#3163) * Created panels mode door shuffle * Added some panel door item names * Remove RUNT TURN panel door Not really useful. * Fix logic with First SIX related stuff * Add group_doors to slot data * Fix LEVEL 2 behavior with panels mode * Fixed unit tests * Fixed duplicate IDs from merge * Just regenerated new IDs * Fixed duplication of color and door group items * Removed unnecessary unit test option * Fix The Seeker being achievable without entrance door * Fix The Observant being achievable without locked panels * Added some more panel doors * Added Progressive Suits Area * Lingo: Fix Basement access with THE MASTER * Added indirect conditions for MASTER-blocked entrances * Fixed Incomparable achievement access * Fix STAIRS panel logic * Fix merge error with good items * Is this clearer? * DREAD and TURN LEARN * Allow a weird edge case for reduced locations Panels mode door shuffle + grouped doors + color shuffle + pilgrimage enabled is exactly the right number of items for reduced locations. Removing color shuffle also allows for disabling pilgrimage, adding sunwarp locking, or both, with a couple of locations left over. * Prevent small sphere one on panels mode * Added shuffle_doors aliases for old options * Fixed a unit test * Updated datafile * Tweaked requirements for reduced locations * Added player name to OptionError messages * Update generated.dat --- worlds/lingo/__init__.py | 3 +- worlds/lingo/data/LL1.yaml | 694 +++++++++++++++++++++-- worlds/lingo/data/generated.dat | Bin 136563 -> 148903 bytes worlds/lingo/data/ids.yaml | 142 +++++ worlds/lingo/datatypes.py | 11 + worlds/lingo/items.py | 17 +- worlds/lingo/options.py | 29 +- worlds/lingo/player_logic.py | 121 +++- worlds/lingo/rules.py | 10 +- worlds/lingo/static_logic.py | 32 +- worlds/lingo/test/TestDoors.py | 56 +- worlds/lingo/test/TestOptions.py | 17 +- worlds/lingo/test/TestOrangeTower.py | 2 +- worlds/lingo/test/TestPanelsanity.py | 2 +- worlds/lingo/test/TestPilgrimage.py | 8 +- worlds/lingo/test/TestProgressive.py | 7 +- worlds/lingo/test/TestSunwarps.py | 21 +- worlds/lingo/utils/assign_ids.rb | 40 ++ worlds/lingo/utils/pickle_static_data.py | 124 +++- worlds/lingo/utils/validate_config.rb | 88 ++- 20 files changed, 1274 insertions(+), 150 deletions(-) diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index a1b8b7c1d4..9853be73fa 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -170,7 +170,8 @@ class LingoWorld(World): slot_options = [ "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", "enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks", - "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps" + "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps", + "group_doors" ] slot_data = { diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 3035446ef7..1c9f4e551d 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -1,6 +1,13 @@ --- # This file is an associative array where the keys are region names. Rooms - # have four properties: entrances, panels, doors, and paintings. + # have a number of properties: + # - entrances + # - panels + # - doors + # - panel_doors + # - paintings + # - progression + # - sunwarps # # entrances is an array of regions from which this room can be accessed. The # key of each entry is the room that can access this one. The value is a list @@ -13,7 +20,7 @@ # room that the door is in. The room name may be omitted if the door is # located in the current room. # - # panels is an array of panels in the room. The key of the array is an + # panels is a named array of panels in the room. The key of the array is an # arbitrary name for the panel. Panels can have the following fields: # - id: The internal ID of the panel in the LINGO map # - required_room: In addition to having access to this room, the player must @@ -45,7 +52,7 @@ # - hunt: If True, the tracker will show this panel even when it is # not a check. Used for hunts like the Number Hunt. # - # doors is an array of doors associated with this room. When door + # doors is a named array of doors associated with this room. When door # randomization is enabled, each of these is an item. The key is a name that # will be displayed as part of the item's name. Doors can have the following # fields: @@ -78,6 +85,18 @@ # - event: Denotes that the door is event only. This is similar to # setting both skip_location and skip_item. # + # panel_doors is a named array of "panel doors" associated with this room. + # When panel door shuffle is enabled, each of these becomes an item, and those + # items block access to the listed panels. The key is a name for internal + # reference only. Panel doors can have the following fields: + # - panels: Required. This is the set of panels that are blocked by this + # panel door. + # - item_name: Overrides the name of the item generated for this panel + # door. If not specified, the item name will be generated from + # the room name and the name(s) of the panel(s). + # - panel_group: When region grouping is enabled, all panel doors with the + # same group will be covered by a single item. + # # paintings is an array of paintings in the room. This is used for painting # shuffling. # - id: The internal painting ID from the LINGO map. @@ -105,6 +124,14 @@ # fine in door shuffle mode. # - move: Denotes that the painting is able to move. # + # progression is a named array of items that define an ordered set of items. + # progression items do not have any true connection to the rooms that they + # are defined in, but it is best to place them in a thematically appropriate + # room. The key for a progression entry is the name of the item that will be + # created. A progression entry is a dictionary with one or both of a "doors" + # key and a "panel_doors" key. These fields should be lists of doors or + # panel doors that will be contained in this progressive item. + # # sunwarps is an array of sunwarps in the room. This is used for sunwarp # shuffling. # - dots: The number of dots on this sunwarp. @@ -193,6 +220,10 @@ panel: RACECAR (Black) - room: The Tenacious panel: SOLOS (Black) + panel_doors: + HIDDEN: + panels: + - HIDDEN paintings: - id: arrows_painting exit_only: True @@ -303,6 +334,10 @@ panel: SOLOS (Black) - room: Hub Room panel: RAT + panel_doors: + OPEN: + panels: + - OPEN paintings: - id: owl_painting orientation: north @@ -317,7 +352,13 @@ panels: Achievement: id: Countdown Panels/Panel_seeker_seeker - required_room: Hidden Room + # The Seeker uniquely has the property that 1) it can be entered (through the Pilgrim Room) without opening the + # front door in panels mode door shuffle, and 2) the front door panel is part of the CDP. This necessitates this + # required_panel clause, because the entrance panel needs to be solvable for the achievement even if an + # alternate entrance to the room is used. + required_panel: + room: Hidden Room + panel: OPEN tag: forbid check: True achievement: The Seeker @@ -537,6 +578,23 @@ item_group: Achievement Room Entrances panels: - OPEN + panel_doors: + ORDER: + panels: + - ORDER + SLAUGHTER: + panel_group: Tenacious Entrance Panels + panels: + - SLAUGHTER + TRACE: + panels: + - TRACE + RAT: + panels: + - RAT + OPEN: + panels: + - OPEN paintings: - id: maze_painting orientation: west @@ -608,12 +666,13 @@ item_name: "6 Sunwarp" progression: Progressive Pilgrimage: - - 1 Sunwarp - - 2 Sunwarp - - 3 Sunwarp - - 4 Sunwarp - - 5 Sunwarp - - 6 Sunwarp + doors: + - 1 Sunwarp + - 2 Sunwarp + - 3 Sunwarp + - 4 Sunwarp + - 5 Sunwarp + - 6 Sunwarp Pilgrim Antechamber: # The entrances to this room are special. When pilgrimage is enabled, we use a special access rule to determine # whether a pilgrimage can succeed. When pilgrimage is disabled, the sun painting will be added to the pool. @@ -881,6 +940,24 @@ panel: READS + RUST - room: Ending Area panel: THE END + panel_doors: + DECAY: + panel_group: Tenacious Entrance Panels + panels: + - DECAY + NOPE: + panels: + - NOPE + WE ROT: + panels: + - WE ROT + WORDS SWORD: + panels: + - WORDS + - SWORD + BEND HI: + panels: + - BEND HI paintings: - id: eye_painting disable: True @@ -895,6 +972,14 @@ direction: exit entrance_indicator_pos: [ -17, 2.5, -41.01 ] orientation: north + progression: + Progressive Suits Area: + panel_doors: + - WORDS SWORD + - room: Lost Area + panel_door: LOST + - room: Amen Name Area + panel_door: AMEN NAME Lost Area: entrances: Outside The Agreeable: @@ -920,6 +1005,11 @@ panels: - LOST (1) - LOST (2) + panel_doors: + LOST: + panels: + - LOST (1) + - LOST (2) Amen Name Area: entrances: Crossroads: @@ -953,6 +1043,11 @@ panels: - AMEN - NAME + panel_doors: + AMEN NAME: + panels: + - AMEN + - NAME Suits Area: entrances: Amen Name Area: @@ -1056,6 +1151,13 @@ - LEVEL (White) - RACECAR (White) - SOLOS (White) + panel_doors: + Black Palindromes: + item_name: The Tenacious - Black Palindromes (Panels) + panels: + - LEVEL (Black) + - RACECAR (Black) + - SOLOS (Black) Near Far Area: entrances: Hub Room: True @@ -1081,6 +1183,21 @@ panels: - NEAR - FAR + panel_doors: + NEAR FAR: + item_name: Symmetry Room - NEAR, FAR (Panels) + panel_group: Symmetry Room Panels + panels: + - NEAR + - FAR + progression: + Progressive Symmetry Room: + panel_doors: + - NEAR FAR + - room: Warts Straw Area + panel_door: WARTS STRAW + - room: Leaf Feel Area + panel_door: LEAF FEEL Warts Straw Area: entrances: Near Far Area: @@ -1108,6 +1225,13 @@ panels: - WARTS - STRAW + panel_doors: + WARTS STRAW: + item_name: Symmetry Room - WARTS, STRAW (Panels) + panel_group: Symmetry Room Panels + panels: + - WARTS + - STRAW Leaf Feel Area: entrances: Warts Straw Area: @@ -1135,6 +1259,13 @@ panels: - LEAF - FEEL + panel_doors: + LEAF FEEL: + item_name: Symmetry Room - LEAF, FEEL (Panels) + panel_group: Symmetry Room Panels + panels: + - LEAF + - FEEL Outside The Agreeable: entrances: Crossroads: @@ -1243,6 +1374,20 @@ panels: - room: Color Hunt panel: PURPLE + panel_doors: + MASSACRED: + panel_group: Tenacious Entrance Panels + panels: + - MASSACRED + BLACK: + panels: + - BLACK + CLOSE: + panels: + - CLOSE + RIGHT: + panels: + - RIGHT paintings: - id: eyes_yellow_painting orientation: east @@ -1294,6 +1439,14 @@ - WINTER - DIAMONDS - FIRE + panel_doors: + Lookout: + item_name: Compass Room Panels + panels: + - NORTH + - WINTER + - DIAMONDS + - FIRE paintings: - id: pencil_painting7 orientation: north @@ -1510,6 +1663,10 @@ - HIDE (3) - room: Outside The Agreeable panel: HIDE + panel_doors: + DOWN: + panels: + - DOWN The Perceptive: entrances: Starting Room: @@ -1531,6 +1688,10 @@ check: True exclude_reduce: True tag: botwhite + panel_doors: + GAZE: + panels: + - GAZE paintings: - id: garden_painting_tower orientation: north @@ -1572,9 +1733,10 @@ - EAT progression: Progressive Fearless: - - Second Floor - - room: The Fearless (Second Floor) - door: Third Floor + doors: + - Second Floor + - room: The Fearless (Second Floor) + door: Third Floor The Fearless (Second Floor): entrances: The Fearless (First Floor): @@ -1669,6 +1831,10 @@ tag: forbid required_door: door: Stairs + required_panel: + - panel: FOUR (1) + - panel: FOUR (2) + - panel: SIX achievement: The Observant FOUR (1): id: Look Room/Panel_four_back @@ -1782,6 +1948,16 @@ door_group: Observant Doors panels: - SIX + panel_doors: + BACKSIDE: + item_name: The Observant - Backside Entrance Panels + panel_group: Backside Entrance Panels + panels: + - FOUR (1) + - FOUR (2) + STAIRS: + panels: + - SIX The Incomparable: entrances: The Observant: @@ -1798,9 +1974,12 @@ check: True tag: forbid required_room: - - Elements Area - - Courtyard - Eight Room + required_panel: + - room: Courtyard + panel: I + - room: Elements Area + panel: A achievement: The Incomparable A (One): id: Strand Room/Panel_blank_a @@ -1865,6 +2044,15 @@ panel: I - room: Elements Area panel: A + panel_doors: + Giant Sevens: + item_name: Giant Seven Panels + panels: + - I (Seven) + - room: Courtyard + panel: I + - room: Elements Area + panel: A paintings: - id: crown_painting orientation: east @@ -1972,14 +2160,31 @@ panel: DRAWL + RUNS - room: Owl Hallway panel: READS + RUST + panel_doors: + Access: + item_name: Orange Tower Panels + panels: + - room: Orange Tower First Floor + panel: DADS + ALE + - room: Outside The Undeterred + panel: ART + ART + - room: Orange Tower Third Floor + panel: DEER + WREN + - room: Orange Tower Fourth Floor + panel: LEARNS + UNSEW + - room: Orange Tower Fifth Floor + panel: DRAWL + RUNS + - room: Owl Hallway + panel: READS + RUST progression: Progressive Orange Tower: - - Second Floor - - Third Floor - - Fourth Floor - - Fifth Floor - - Sixth Floor - - Seventh Floor + doors: + - Second Floor + - Third Floor + - Fourth Floor + - Fifth Floor + - Sixth Floor + - Seventh Floor Orange Tower First Floor: entrances: Hub Room: @@ -2022,6 +2227,10 @@ - SALT - room: Directional Gallery panel: PEPPER + panel_doors: + SECRET: + panels: + - SECRET sunwarps: - dots: 4 direction: enter @@ -2174,6 +2383,10 @@ id: Shuffle Room Area Doors/Door_hotcrust_shortcuts panels: - HOT CRUSTS + panel_doors: + HOT CRUSTS: + panels: + - HOT CRUSTS sunwarps: - dots: 5 direction: enter @@ -2288,6 +2501,12 @@ panels: - SIZE (Small) - SIZE (Big) + panel_doors: + SIZE: + item_name: Orange Tower Fifth Floor - SIZE Panels + panels: + - SIZE (Small) + - SIZE (Big) paintings: - id: hi_solved_painting3 orientation: south @@ -2631,6 +2850,15 @@ - SECOND - THIRD - FOURTH + panel_doors: + FIRST SECOND THIRD FOURTH: + item_name: Courtyard - Ordinal Panels + panel_group: Backside Entrance Panels + panels: + - FIRST + - SECOND + - THIRD + - FOURTH The Colorful (White): entrances: Courtyard: True @@ -2648,6 +2876,12 @@ location_name: The Colorful - White panels: - BEGIN + panel_doors: + BEGIN: + item_name: The Colorful - BEGIN (Panel) + panel_group: Colorful Panels + panels: + - BEGIN The Colorful (Black): entrances: The Colorful (White): @@ -2668,6 +2902,12 @@ door_group: Colorful Doors panels: - FOUND + panel_doors: + FOUND: + item_name: The Colorful - FOUND (Panel) + panel_group: Colorful Panels + panels: + - FOUND The Colorful (Red): entrances: The Colorful (Black): @@ -2688,6 +2928,12 @@ door_group: Colorful Doors panels: - LOAF + panel_doors: + LOAF: + item_name: The Colorful - LOAF (Panel) + panel_group: Colorful Panels + panels: + - LOAF The Colorful (Yellow): entrances: The Colorful (Red): @@ -2708,6 +2954,12 @@ door_group: Colorful Doors panels: - CREAM + panel_doors: + CREAM: + item_name: The Colorful - CREAM (Panel) + panel_group: Colorful Panels + panels: + - CREAM The Colorful (Blue): entrances: The Colorful (Yellow): @@ -2728,6 +2980,12 @@ door_group: Colorful Doors panels: - SUN + panel_doors: + SUN: + item_name: The Colorful - SUN (Panel) + panel_group: Colorful Panels + panels: + - SUN The Colorful (Purple): entrances: The Colorful (Blue): @@ -2748,6 +3006,12 @@ door_group: Colorful Doors panels: - SPOON + panel_doors: + SPOON: + item_name: The Colorful - SPOON (Panel) + panel_group: Colorful Panels + panels: + - SPOON The Colorful (Orange): entrances: The Colorful (Purple): @@ -2768,6 +3032,12 @@ door_group: Colorful Doors panels: - LETTERS + panel_doors: + LETTERS: + item_name: The Colorful - LETTERS (Panel) + panel_group: Colorful Panels + panels: + - LETTERS The Colorful (Green): entrances: The Colorful (Orange): @@ -2788,6 +3058,12 @@ door_group: Colorful Doors panels: - WALLS + panel_doors: + WALLS: + item_name: The Colorful - WALLS (Panel) + panel_group: Colorful Panels + panels: + - WALLS The Colorful (Brown): entrances: The Colorful (Green): @@ -2808,6 +3084,12 @@ door_group: Colorful Doors panels: - IRON + panel_doors: + IRON: + item_name: The Colorful - IRON (Panel) + panel_group: Colorful Panels + panels: + - IRON The Colorful (Gray): entrances: The Colorful (Brown): @@ -2828,6 +3110,12 @@ door_group: Colorful Doors panels: - OBSTACLE + panel_doors: + OBSTACLE: + item_name: The Colorful - OBSTACLE (Panel) + panel_group: Colorful Panels + panels: + - OBSTACLE The Colorful: entrances: The Colorful (Gray): @@ -2866,26 +3154,48 @@ orientation: north progression: Progressive Colorful: - - room: The Colorful (White) - door: Progress Door - - room: The Colorful (Black) - door: Progress Door - - room: The Colorful (Red) - door: Progress Door - - room: The Colorful (Yellow) - door: Progress Door - - room: The Colorful (Blue) - door: Progress Door - - room: The Colorful (Purple) - door: Progress Door - - room: The Colorful (Orange) - door: Progress Door - - room: The Colorful (Green) - door: Progress Door - - room: The Colorful (Brown) - door: Progress Door - - room: The Colorful (Gray) - door: Progress Door + doors: + - room: The Colorful (White) + door: Progress Door + - room: The Colorful (Black) + door: Progress Door + - room: The Colorful (Red) + door: Progress Door + - room: The Colorful (Yellow) + door: Progress Door + - room: The Colorful (Blue) + door: Progress Door + - room: The Colorful (Purple) + door: Progress Door + - room: The Colorful (Orange) + door: Progress Door + - room: The Colorful (Green) + door: Progress Door + - room: The Colorful (Brown) + door: Progress Door + - room: The Colorful (Gray) + door: Progress Door + panel_doors: + - room: The Colorful (White) + panel_door: BEGIN + - room: The Colorful (Black) + panel_door: FOUND + - room: The Colorful (Red) + panel_door: LOAF + - room: The Colorful (Yellow) + panel_door: CREAM + - room: The Colorful (Blue) + panel_door: SUN + - room: The Colorful (Purple) + panel_door: SPOON + - room: The Colorful (Orange) + panel_door: LETTERS + - room: The Colorful (Green) + panel_door: WALLS + - room: The Colorful (Brown) + panel_door: IRON + - room: The Colorful (Gray) + panel_door: OBSTACLE Welcome Back Area: entrances: Starting Room: @@ -2958,6 +3268,10 @@ door_group: Hedge Maze Doors panels: - STRAYS + panel_doors: + STRAYS: + panels: + - STRAYS paintings: - id: arrows_painting_8 orientation: south @@ -3155,6 +3469,13 @@ panel: I - room: Elements Area panel: A + panel_doors: + UNCOVER: + panels: + - UNCOVER + OXEN: + panels: + - OXEN paintings: - id: clock_painting_5 orientation: east @@ -3524,6 +3845,13 @@ - RISE (Sunrise) - ZEN - SON + panel_doors: + UNOPEN: + panels: + - UNOPEN + BEGIN: + panels: + - BEGIN paintings: - id: pencil_painting2 orientation: west @@ -3819,6 +4147,34 @@ item_group: Achievement Room Entrances panels: - ZERO + panel_doors: + ZERO: + panels: + - ZERO + PEN: + panels: + - PEN + TWO: + item_name: Two Panels + panels: + - TWO (1) + - TWO (2) + THREE: + item_name: Three Panels + panels: + - THREE (1) + - THREE (2) + - THREE (3) + FOUR: + item_name: Four Panels + panels: + - FOUR + - room: Hub Room + panel: FOUR + - room: Dead End Area + panel: FOUR + - room: The Traveled + panel: FOUR paintings: - id: maze_painting_3 enter_only: True @@ -3994,6 +4350,10 @@ panel: FIVE (1) - room: Directional Gallery panel: FIVE (2) + First Six: + event: True + panels: + - SIX Sevens: id: - Count Up Room Area Doors/Door_seven_hider @@ -4102,12 +4462,109 @@ panel: NINE - room: Elements Area panel: NINE + panel_doors: + FIVE: + item_name: Five Panels + panels: + - FIVE + - room: Outside The Agreeable + panel: FIVE (1) + - room: Outside The Agreeable + panel: FIVE (2) + - room: Directional Gallery + panel: FIVE (1) + - room: Directional Gallery + panel: FIVE (2) + SIX: + item_name: Six Panels + panels: + - SIX + - room: Outside The Bold + panel: SIX + - room: Directional Gallery + panel: SIX (1) + - room: Directional Gallery + panel: SIX (2) + - room: The Bearer (East) + panel: SIX + - room: The Bearer (South) + panel: SIX + SEVEN: + item_name: Seven Panels + panels: + - SEVEN + - room: Directional Gallery + panel: SEVEN + - room: Knight Night Exit + panel: SEVEN (1) + - room: Knight Night Exit + panel: SEVEN (2) + - room: Knight Night Exit + panel: SEVEN (3) + - room: Outside The Initiated + panel: SEVEN (1) + - room: Outside The Initiated + panel: SEVEN (2) + EIGHT: + item_name: Eight Panels + panels: + - EIGHT + - room: Directional Gallery + panel: EIGHT + - room: The Eyes They See + panel: EIGHT + - room: Dead End Area + panel: EIGHT + - room: Crossroads + panel: EIGHT + - room: Hot Crusts Area + panel: EIGHT + - room: Art Gallery + panel: EIGHT + - room: Outside The Initiated + panel: EIGHT + NINE: + item_name: Nine Panels + panels: + - NINE + - room: Directional Gallery + panel: NINE + - room: Amen Name Area + panel: NINE + - room: Yellow Backside Area + panel: NINE + - room: Outside The Initiated + panel: NINE + - room: Outside The Bold + panel: NINE + - room: Rhyme Room (Cross) + panel: NINE + - room: Orange Tower Fifth Floor + panel: NINE + - room: Elements Area + panel: NINE paintings: - id: smile_painting_5 enter_only: True orientation: east required_door: door: Eights + progression: + Progressive Number Hunt: + panel_doors: + - room: Outside The Undeterred + panel_door: TWO + - room: Outside The Undeterred + panel_door: THREE + - room: Outside The Undeterred + panel_door: FOUR + - FIVE + - SIX + - SEVEN + - EIGHT + - NINE + - room: Outside The Undeterred + panel_door: ZERO Directional Gallery: entrances: Outside The Agreeable: @@ -4195,7 +4652,7 @@ tag: midorange required_door: room: Number Hunt - door: Sixes + door: First Six PARANOID: id: Backside Room/Panel_paranoid_paranoid tag: midwhite @@ -4203,7 +4660,7 @@ exclude_reduce: True required_door: room: Number Hunt - door: Sixes + door: First Six YELLOW: id: Color Arrow Room/Panel_yellow_afar tag: midwhite @@ -4266,6 +4723,11 @@ panels: - room: Color Hunt panel: YELLOW + panel_doors: + TURN LEARN: + panels: + - TURN + - LEARN paintings: - id: smile_painting_7 orientation: south @@ -4277,7 +4739,7 @@ move: True required_door: room: Number Hunt - door: Sixes + door: First Six - id: boxes_painting orientation: south - id: cherry_painting @@ -4344,6 +4806,34 @@ id: Rock Room Doors/Door_hint panels: - EXIT + panel_doors: + EXIT: + panels: + - EXIT + RED: + panel_group: Color Hunt Panels + panels: + - RED + BLUE: + panel_group: Color Hunt Panels + panels: + - BLUE + YELLOW: + panel_group: Color Hunt Panels + panels: + - YELLOW + ORANGE: + panel_group: Color Hunt Panels + panels: + - ORANGE + PURPLE: + panel_group: Color Hunt Panels + panels: + - PURPLE + GREEN: + panel_group: Color Hunt Panels + panels: + - GREEN paintings: - id: arrows_painting_7 orientation: east @@ -4481,6 +4971,14 @@ event: True panels: - HEART + panel_doors: + FARTHER: + panel_group: Backside Entrance Panels + panels: + - FARTHER + MIDDLE: + panels: + - MIDDLE The Bearer (East): entrances: Cross Tower (East): True @@ -5333,6 +5831,11 @@ item_name: Knight Night Room - Exit panels: - TRUSTED + panel_doors: + TRUSTED: + item_name: Knight Night Room - TRUSTED (Panel) + panels: + - TRUSTED Knight Night Exit: entrances: Knight Night (Outer Ring): @@ -6017,6 +6520,10 @@ item_group: Achievement Room Entrances panels: - SHRINK + panel_doors: + SHRINK: + panels: + - SHRINK The Wondrous (Doorknob): entrances: Outside The Wondrous: @@ -6228,18 +6735,36 @@ - KEEP - BAILEY - TOWER + panel_doors: + CASTLE: + item_name: Hallway Room - First Room Panels + panel_group: Hallway Room Panels + panels: + - WALL + - KEEP + - BAILEY + - TOWER paintings: - id: panda_painting orientation: south progression: Progressive Hallway Room: - - Exit - - room: Hallway Room (2) - door: Exit - - room: Hallway Room (3) - door: Exit - - room: Hallway Room (4) - door: Exit + doors: + - Exit + - room: Hallway Room (2) + door: Exit + - room: Hallway Room (3) + door: Exit + - room: Hallway Room (4) + door: Exit + panel_doors: + - CASTLE + - room: Hallway Room (2) + panel_door: COUNTERCLOCKWISE + - room: Hallway Room (3) + panel_door: TRANSFORMATION + - room: Hallway Room (4) + panel_door: WHEELBARROW Hallway Room (2): entrances: Hallway Room (1): @@ -6278,6 +6803,15 @@ - CLOCK - ER - COUNT + panel_doors: + COUNTERCLOCKWISE: + item_name: Hallway Room - Second Room Panels + panel_group: Hallway Room Panels + panels: + - WISE + - CLOCK + - ER + - COUNT Hallway Room (3): entrances: Hallway Room (2): @@ -6316,6 +6850,15 @@ - FORM - A - SHUN + panel_doors: + TRANSFORMATION: + item_name: Hallway Room - Third Room Panels + panel_group: Hallway Room Panels + panels: + - TRANCE + - FORM + - A + - SHUN Hallway Room (4): entrances: Hallway Room (3): @@ -6338,6 +6881,12 @@ panels: - WHEEL include_reduce: True + panel_doors: + WHEELBARROW: + item_name: Hallway Room - WHEEL + panel_group: Hallway Room Panels + panels: + - WHEEL Elements Area: entrances: Roof: True @@ -6412,6 +6961,10 @@ panels: - room: The Wanderer panel: Achievement + panel_doors: + WANDERLUST: + panels: + - WANDERLUST The Wanderer: entrances: Outside The Wanderer: @@ -6553,6 +7106,10 @@ item_group: Achievement Room Entrances panels: - ORDER + panel_doors: + ORDER: + panels: + - ORDER paintings: - id: smile_painting_3 orientation: west @@ -6566,10 +7123,11 @@ orientation: south progression: Progressive Art Gallery: - - Second Floor - - Third Floor - - Fourth Floor - - Fifth Floor + doors: + - Second Floor + - Third Floor + - Fourth Floor + - Fifth Floor Art Gallery (Second Floor): entrances: Art Gallery: @@ -7281,8 +7839,8 @@ id: Panel Room/Panel_broomed_bedroom colors: yellow tag: midyellow - required_door: - door: Excavation + required_panel: + panel: WALL (1) LAYS: id: Panel Room/Panel_lays_maze colors: purple @@ -7309,13 +7867,24 @@ Excavation: event: True panels: - - WALL (1) + - STAIRS Cellar Exit: id: - Tower Room Area Doors/Door_panel_basement - Tower Room Area Doors/Door_panel_basement2 panels: - BASE + panel_doors: + STAIRS: + panel_group: Room Room Panels + panels: + - STAIRS + Colors: + panel_group: Room Room Panels + panels: + - BROOMED + - LAYS + - BASE Cellar: entrances: Room Room: @@ -7354,6 +7923,11 @@ panels: - KITTEN - CAT + panel_doors: + KITTEN CAT: + panels: + - KITTEN + - CAT paintings: - id: arrows_painting_2 orientation: east @@ -7608,6 +8182,10 @@ item_group: Achievement Room Entrances panels: - OPEN + panel_doors: + OPEN: + panels: + - OPEN The Scientific: entrances: Outside The Scientific: diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat index 4a751b25ec5f143b6b055fd2043a6543754ef5b1..d221b8168d9164576b3f1c5b2fc57c604da5f100 100644 GIT binary patch delta 33771 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