From cfe357eb7197a127f1a5dc62ccc9eaa87e6c2668 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sat, 25 Nov 2023 15:07:02 -0600 Subject: [PATCH 01/56] The Messenger, LADX: use collect and remove as intended (#2093) Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> --- worlds/ladx/__init__.py | 31 ++++++++++++----------------- worlds/ladx/test/testShop.py | 38 ++++++++++++++++++++++++++++++++++++ worlds/messenger/__init__.py | 19 ++++++++++-------- 3 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 worlds/ladx/test/testShop.py diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index eaaea5be2f67..181cc053222d 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -1,32 +1,29 @@ import binascii -import bsdiff4 import os import pkgutil -import settings -import typing import tempfile +import typing +import bsdiff4 +import settings from BaseClasses import Entrance, Item, ItemClassification, Location, Tutorial from Fill import fill_restrictive from worlds.AutoWorld import WebWorld, World - from .Common import * -from .Items import (DungeonItemData, DungeonItemType, LinksAwakeningItem, TradeItemData, - ladxr_item_to_la_item_name, links_awakening_items, - links_awakening_items_by_name, ItemName) +from .Items import (DungeonItemData, DungeonItemType, ItemName, LinksAwakeningItem, TradeItemData, + ladxr_item_to_la_item_name, links_awakening_items, links_awakening_items_by_name) from .LADXR import generator from .LADXR.itempool import ItemPool as LADXRItemPool +from .LADXR.locations.constants import CHEST_ITEMS +from .LADXR.locations.instrument import Instrument from .LADXR.logic import Logic as LAXDRLogic from .LADXR.main import get_parser from .LADXR.settings import Settings as LADXRSettings from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup -from .LADXR.locations.instrument import Instrument -from .LADXR.locations.constants import CHEST_ITEMS from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion, create_regions_from_ladxr, get_locations_to_id) -from .Options import links_awakening_options, DungeonItemShuffle - +from .Options import DungeonItemShuffle, links_awakening_options from .Rom import LADXDeltaPatch DEVELOPER_MODE = False @@ -511,16 +508,12 @@ def modify_multidata(self, multidata: dict): def collect(self, state, item: Item) -> bool: change = super().collect(state, item) - if change: - rupees = self.rupees.get(item.name, 0) - state.prog_items[item.player]["RUPEES"] += rupees - + if change and item.name in self.rupees: + state.prog_items[self.player]["RUPEES"] += self.rupees[item.name] return change def remove(self, state, item: Item) -> bool: change = super().remove(state, item) - if change: - rupees = self.rupees.get(item.name, 0) - state.prog_items[item.player]["RUPEES"] -= rupees - + if change and item.name in self.rupees: + state.prog_items[self.player]["RUPEES"] -= self.rupees[item.name] return change diff --git a/worlds/ladx/test/testShop.py b/worlds/ladx/test/testShop.py new file mode 100644 index 000000000000..91d504d521b4 --- /dev/null +++ b/worlds/ladx/test/testShop.py @@ -0,0 +1,38 @@ +from typing import Optional + +from Fill import distribute_planned +from test.general import setup_solo_multiworld +from worlds.AutoWorld import call_all +from . import LADXTestBase +from .. import LinksAwakeningWorld + + +class PlandoTest(LADXTestBase): + options = { + "plando_items": [{ + "items": { + "Progressive Sword": 2, + }, + "locations": [ + "Shop 200 Item (Mabe Village)", + "Shop 980 Item (Mabe Village)", + ], + }], + } + + def world_setup(self, seed: Optional[int] = None) -> None: + self.multiworld = setup_solo_multiworld( + LinksAwakeningWorld, + ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic") + ) + self.multiworld.plando_items[1] = self.options["plando_items"] + distribute_planned(self.multiworld) + call_all(self.multiworld, "pre_fill") + + def test_planned(self): + """Tests plandoing swords in the shop.""" + location_names = ["Shop 200 Item (Mabe Village)", "Shop 980 Item (Mabe Village)"] + locations = [self.multiworld.get_location(loc, 1) for loc in location_names] + for loc in locations: + self.assertEqual("Progressive Sword", loc.item.name) + self.assertFalse(loc.can_reach(self.multiworld.state)) diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py index f12687361b70..d569dd754278 100644 --- a/worlds/messenger/__init__.py +++ b/worlds/messenger/__init__.py @@ -176,11 +176,14 @@ def create_item(self, name: str) -> MessengerItem: self.total_shards += count return MessengerItem(name, self.player, item_id, override_prog, count) - def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]: - if item.advancement and "Time Shard" in item.name: - shard_count = int(item.name.strip("Time Shard ()")) - if remove: - shard_count = -shard_count - state.prog_items[self.player]["Shards"] += shard_count - - return super().collect_item(state, item, remove) + def collect(self, state: "CollectionState", item: "Item") -> bool: + change = super().collect(state, item) + if change and "Time Shard" in item.name: + state.prog_items[self.player]["Shards"] += int(item.name.strip("Time Shard ()")) + return change + + def remove(self, state: "CollectionState", item: "Item") -> bool: + change = super().remove(state, item) + if change and "Time Shard" in item.name: + state.prog_items[self.player]["Shards"] -= int(item.name.strip("Time Shard ()")) + return change From 7a4620925957b2da909d9510358d6773efab3bf3 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:12:38 -0500 Subject: [PATCH 02/56] SA2B: Add AP 0.4.4 Game Chao Names (#2510) --- worlds/sa2b/AestheticData.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/worlds/sa2b/AestheticData.py b/worlds/sa2b/AestheticData.py index f3699e81e0c4..077f35fc01b0 100644 --- a/worlds/sa2b/AestheticData.py +++ b/worlds/sa2b/AestheticData.py @@ -146,6 +146,10 @@ "Rin", "Doomguy", "Guide", + "May", + "Hubert", + "Corvus", + "Nigel", ] totally_real_item_names = [ From eec35ab1c3c4b6d51d04b12c812ed6abe8e84f09 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Sat, 25 Nov 2023 20:13:08 -0800 Subject: [PATCH 03/56] Pokemon Emerald: Fix tracker flags being reset in menus (#2511) --- worlds/pokemon_emerald/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/pokemon_emerald/client.py b/worlds/pokemon_emerald/client.py index 5420b15fbe95..d8b4b8d5878f 100644 --- a/worlds/pokemon_emerald/client.py +++ b/worlds/pokemon_emerald/client.py @@ -254,7 +254,7 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: "key": f"pokemon_emerald_events_{ctx.team}_{ctx.slot}", "default": 0, "want_reply": False, - "operations": [{"operation": "replace", "value": event_bitfield}] + "operations": [{"operation": "or", "value": event_bitfield}] }]) self.local_set_events = local_set_events @@ -269,7 +269,7 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None: "key": f"pokemon_emerald_keys_{ctx.team}_{ctx.slot}", "default": 0, "want_reply": False, - "operations": [{"operation": "replace", "value": key_bitfield}] + "operations": [{"operation": "or", "value": key_bitfield}] }]) self.local_found_key_items = local_found_key_items except bizhawk.RequestFailedError: From 65f47be511ace6d6f34495df58ad46d02c450f9d Mon Sep 17 00:00:00 2001 From: Justus Lind Date: Sun, 26 Nov 2023 14:13:59 +1000 Subject: [PATCH 04/56] Muse Dash: Presets and Song Updates (#2512) --- worlds/musedash/MuseDashData.txt | 8 +++++++- worlds/musedash/Presets.py | 31 +++++++++++++++++++++++++++++++ worlds/musedash/__init__.py | 2 ++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 worlds/musedash/Presets.py diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt index 5b3ef40e5421..54a0124474c6 100644 --- a/worlds/musedash/MuseDashData.txt +++ b/worlds/musedash/MuseDashData.txt @@ -495,4 +495,10 @@ Gullinkambi|67-1|Happy Otaku Pack Vol.18|True|4|7|10| RakiRaki Rebuilders!!!|67-2|Happy Otaku Pack Vol.18|True|5|7|10| Laniakea|67-3|Happy Otaku Pack Vol.18|False|5|8|10| OTTAMA GAZER|67-4|Happy Otaku Pack Vol.18|True|5|8|10| -Sleep Tight feat.Macoto|67-5|Happy Otaku Pack Vol.18|True|3|5|8| \ No newline at end of file +Sleep Tight feat.Macoto|67-5|Happy Otaku Pack Vol.18|True|3|5|8| +New York Back Raise|68-0|Gambler's Tricks|True|6|8|10| +slic.hertz|68-1|Gambler's Tricks|True|5|7|9| +Fuzzy-Navel|68-2|Gambler's Tricks|True|6|8|10|11 +Swing Edge|68-3|Gambler's Tricks|True|4|8|10| +Twisted Escape|68-4|Gambler's Tricks|True|5|8|10|11 +Swing Sweet Twee Dance|68-5|Gambler's Tricks|False|4|7|10| \ No newline at end of file diff --git a/worlds/musedash/Presets.py b/worlds/musedash/Presets.py new file mode 100644 index 000000000000..64591118021e --- /dev/null +++ b/worlds/musedash/Presets.py @@ -0,0 +1,31 @@ +from typing import Any, Dict + +MuseDashPresets: Dict[str, Dict[str, Any]] = { + # An option to support Short Sync games. 40 songs. + "No DLC - Short": { + "allow_just_as_planned_dlc_songs": False, + "starting_song_count": 5, + "additional_song_count": 34, + "additional_item_percentage": 80, + "music_sheet_count_percentage": 20, + "music_sheet_win_count_percentage": 90, + }, + # An option to support Short Sync games but adds variety. 40 songs. + "DLC - Short": { + "allow_just_as_planned_dlc_songs": True, + "starting_song_count": 5, + "additional_song_count": 34, + "additional_item_percentage": 80, + "music_sheet_count_percentage": 20, + "music_sheet_win_count_percentage": 90, + }, + # An option to support Longer Sync/Async games. 100 songs. + "DLC - Long": { + "allow_just_as_planned_dlc_songs": True, + "starting_song_count": 8, + "additional_song_count": 91, + "additional_item_percentage": 80, + "music_sheet_count_percentage": 20, + "music_sheet_win_count_percentage": 90, + }, +} diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py index 9a0e473494ad..a68fd2853def 100644 --- a/worlds/musedash/__init__.py +++ b/worlds/musedash/__init__.py @@ -8,6 +8,7 @@ from .Items import MuseDashSongItem, MuseDashFixedItem from .Locations import MuseDashLocation from .MuseDashCollection import MuseDashCollections +from .Presets import MuseDashPresets class MuseDashWebWorld(WebWorld): @@ -33,6 +34,7 @@ class MuseDashWebWorld(WebWorld): ) tutorials = [setup_en, setup_es] + options_presets = MuseDashPresets class MuseDashWorld(World): From f54f8622bb6edd38324a0eadc42b2daa7261f415 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 26 Nov 2023 11:17:59 -0500 Subject: [PATCH 05/56] Final Fantasy Mystic Quest: Implement new game (#1909) FFMQR by @wildham0 Uses an API created by wildham for Map Shuffle, Crest Shuffle and Battlefield Reward Shuffle, using a similar method of obtaining data from an external website to Super Metroid's Varia Preset option. Generates a .apmq file which the user must bring to the FFMQR website https://www.ffmqrando.net/Archipelago to patch their rom. It is not an actual patch file but contains item placement and options data for the FFMQR website to generate a patched rom with for AP. Some of the AP options may seem unusual, using Choice instead of Range where it may seem more appropriate, but these are options that are passed to FFMQR and I can only be as flexible as it is. @wildham0 deserves the bulk of the credit for not only creating FFMQR in the first place but all the ASM work on the rom needed to make this possible, work on FFMQR to allow patching with the .apmq files, and creating the API that meant I did not have to recreate his map shuffle from scratch. --- README.md | 1 + WebHostLib/downloads.py | 2 + WebHostLib/templates/macros.html | 3 + docs/CODEOWNERS | 3 + worlds/ffmq/Client.py | 119 + worlds/ffmq/Items.py | 297 ++ worlds/ffmq/LICENSE | 22 + worlds/ffmq/Options.py | 258 ++ worlds/ffmq/Output.py | 113 + worlds/ffmq/Regions.py | 251 + worlds/ffmq/__init__.py | 217 + worlds/ffmq/data/entrances.yaml | 2425 ++++++++++ worlds/ffmq/data/rooms.yaml | 4026 +++++++++++++++++ worlds/ffmq/data/settings.yaml | 140 + .../docs/en_Final Fantasy Mystic Quest.md | 33 + worlds/ffmq/docs/setup_en.md | 162 + 16 files changed, 8072 insertions(+) create mode 100644 worlds/ffmq/Client.py create mode 100644 worlds/ffmq/Items.py create mode 100644 worlds/ffmq/LICENSE create mode 100644 worlds/ffmq/Options.py create mode 100644 worlds/ffmq/Output.py create mode 100644 worlds/ffmq/Regions.py create mode 100644 worlds/ffmq/__init__.py create mode 100644 worlds/ffmq/data/entrances.yaml create mode 100644 worlds/ffmq/data/rooms.yaml create mode 100644 worlds/ffmq/data/settings.yaml create mode 100644 worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md create mode 100644 worlds/ffmq/docs/setup_en.md diff --git a/README.md b/README.md index 3508dd16095c..a1e03293d587 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Currently, the following games are supported: * Shivers * Heretic * Landstalker: The Treasures of King Nole +* Final Fantasy Mystic Quest For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/WebHostLib/downloads.py b/WebHostLib/downloads.py index 5cf503be1b2b..a09ca7017181 100644 --- a/WebHostLib/downloads.py +++ b/WebHostLib/downloads.py @@ -90,6 +90,8 @@ def download_slot_file(room_id, player_id: int): fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}.json" elif slot_data.game == "Kingdom Hearts 2": fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.zip" + elif slot_data.game == "Final Fantasy Mystic Quest": + fname = f"AP+{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.apmq" else: return "Game download not supported." return send_file(io.BytesIO(slot_data.data), as_attachment=True, download_name=fname) diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html index 746399da74a6..0722ee317466 100644 --- a/WebHostLib/templates/macros.html +++ b/WebHostLib/templates/macros.html @@ -50,6 +50,9 @@ {% elif patch.game == "Dark Souls III" %} Download JSON File... + {% elif patch.game == "Final Fantasy Mystic Quest" %} + + Download APMQ File... {% else %} No file to download for this game. {% endif %} diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 0764fa927464..e221371b2417 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -55,6 +55,9 @@ # Final Fantasy /worlds/ff1/ @jtoyoda +# Final Fantasy Mystic Quest +/worlds/ffmq/ @Alchav @wildham0 + # Heretic /worlds/heretic/ @Daivuk diff --git a/worlds/ffmq/Client.py b/worlds/ffmq/Client.py new file mode 100644 index 000000000000..c53f275017af --- /dev/null +++ b/worlds/ffmq/Client.py @@ -0,0 +1,119 @@ + +from NetUtils import ClientStatus, color +from worlds.AutoSNIClient import SNIClient +from .Regions import offset +import logging + +snes_logger = logging.getLogger("SNES") + +ROM_NAME = (0x7FC0, 0x7FD4 + 1 - 0x7FC0) + +READ_DATA_START = 0xF50EA8 +READ_DATA_END = 0xF50FE7 + 1 + +GAME_FLAGS = (0xF50EA8, 64) +COMPLETED_GAME = (0xF50F22, 1) +BATTLEFIELD_DATA = (0xF50FD4, 20) + +RECEIVED_DATA = (0xE01FF0, 3) + +ITEM_CODE_START = 0x420000 + +IN_GAME_FLAG = (4 * 8) + 2 + +NPC_CHECKS = { + 4325676: ((6 * 8) + 4, False), # Old Man Level Forest + 4325677: ((3 * 8) + 6, True), # Kaeli Level Forest + 4325678: ((25 * 8) + 1, True), # Tristam + 4325680: ((26 * 8) + 0, True), # Aquaria Vendor Girl + 4325681: ((29 * 8) + 2, True), # Phoebe Wintry Cave + 4325682: ((25 * 8) + 6, False), # Mysterious Man (Life Temple) + 4325683: ((29 * 8) + 3, True), # Reuben Mine + 4325684: ((29 * 8) + 7, True), # Spencer + 4325685: ((29 * 8) + 6, False), # Venus Chest + 4325686: ((29 * 8) + 1, True), # Fireburg Tristam + 4325687: ((26 * 8) + 1, True), # Fireburg Vendor Girl + 4325688: ((14 * 8) + 4, True), # MegaGrenade Dude + 4325689: ((29 * 8) + 5, False), # Tristam's Chest + 4325690: ((29 * 8) + 4, True), # Arion + 4325691: ((29 * 8) + 0, True), # Windia Kaeli + 4325692: ((26 * 8) + 2, True), # Windia Vendor Girl + +} + + +def get_flag(data, flag): + byte = int(flag / 8) + bit = int(0x80 / (2 ** (flag % 8))) + return (data[byte] & bit) > 0 + + +class FFMQClient(SNIClient): + game = "Final Fantasy Mystic Quest" + + async def validate_rom(self, ctx): + from SNIClient import snes_read + rom_name = await snes_read(ctx, *ROM_NAME) + if rom_name is None: + return False + if rom_name[:2] != b"MQ": + return False + + ctx.rom = rom_name + ctx.game = self.game + ctx.items_handling = 0b001 + return True + + async def game_watcher(self, ctx): + from SNIClient import snes_buffered_write, snes_flush_writes, snes_read + + check_1 = await snes_read(ctx, 0xF53749, 1) + received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1]) + data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START) + check_2 = await snes_read(ctx, 0xF53749, 1) + if check_1 == b'\x00' or check_2 == b'\x00': + return + + def get_range(data_range): + return data[data_range[0] - READ_DATA_START:data_range[0] + data_range[1] - READ_DATA_START] + completed_game = get_range(COMPLETED_GAME) + battlefield_data = get_range(BATTLEFIELD_DATA) + game_flags = get_range(GAME_FLAGS) + + if game_flags is None: + return + if not get_flag(game_flags, IN_GAME_FLAG): + return + + if not ctx.finished_game: + if completed_game[0] & 0x80 and game_flags[30] & 0x18: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + + old_locations_checked = ctx.locations_checked.copy() + + for container in range(256): + if get_flag(game_flags, (0x20 * 8) + container): + ctx.locations_checked.add(offset["Chest"] + container) + + for location, data in NPC_CHECKS.items(): + if get_flag(game_flags, data[0]) is data[1]: + ctx.locations_checked.add(location) + + for battlefield in range(20): + if battlefield_data[battlefield] == 0: + ctx.locations_checked.add(offset["BattlefieldItem"] + battlefield + 1) + + if old_locations_checked != ctx.locations_checked: + await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": ctx.locations_checked}]) + + if received[0] == 0: + received_index = int.from_bytes(received[1:], "big") + if received_index < len(ctx.items_received): + item = ctx.items_received[received_index] + received_index += 1 + code = (item.item - ITEM_CODE_START) + 1 + if code > 256: + code -= 256 + snes_buffered_write(ctx, RECEIVED_DATA[0], bytes([code, *received_index.to_bytes(2, "big")])) + await snes_flush_writes(ctx) diff --git a/worlds/ffmq/Items.py b/worlds/ffmq/Items.py new file mode 100644 index 000000000000..7660bd5d52f3 --- /dev/null +++ b/worlds/ffmq/Items.py @@ -0,0 +1,297 @@ +from BaseClasses import ItemClassification, Item + +fillers = {"Cure Potion": 61, "Heal Potion": 52, "Refresher": 17, "Seed": 2, "Bomb Refill": 19, + "Projectile Refill": 50} + + +class ItemData: + def __init__(self, item_id, classification, groups=(), data_name=None): + self.groups = groups + self.classification = classification + self.id = None + if item_id is not None: + self.id = item_id + 0x420000 + self.data_name = data_name + + +item_table = { + "Elixir": ItemData(0, ItemClassification.progression, ["Key Items"]), + "Tree Wither": ItemData(1, ItemClassification.progression, ["Key Items"]), + "Wakewater": ItemData(2, ItemClassification.progression, ["Key Items"]), + "Venus Key": ItemData(3, ItemClassification.progression, ["Key Items"]), + "Multi Key": ItemData(4, ItemClassification.progression, ["Key Items"]), + "Mask": ItemData(5, ItemClassification.progression, ["Key Items"]), + "Magic Mirror": ItemData(6, ItemClassification.progression, ["Key Items"]), + "Thunder Rock": ItemData(7, ItemClassification.progression, ["Key Items"]), + "Captain's Cap": ItemData(8, ItemClassification.progression_skip_balancing, ["Key Items"]), + "Libra Crest": ItemData(9, ItemClassification.progression, ["Key Items"]), + "Gemini Crest": ItemData(10, ItemClassification.progression, ["Key Items"]), + "Mobius Crest": ItemData(11, ItemClassification.progression, ["Key Items"]), + "Sand Coin": ItemData(12, ItemClassification.progression, ["Key Items", "Coins"]), + "River Coin": ItemData(13, ItemClassification.progression, ["Key Items", "Coins"]), + "Sun Coin": ItemData(14, ItemClassification.progression, ["Key Items", "Coins"]), + "Sky Coin": ItemData(15, ItemClassification.progression_skip_balancing, ["Key Items", "Coins"]), + "Sky Fragment": ItemData(15 + 256, ItemClassification.progression_skip_balancing, ["Key Items"]), + "Cure Potion": ItemData(16, ItemClassification.filler, ["Consumables"]), + "Heal Potion": ItemData(17, ItemClassification.filler, ["Consumables"]), + "Seed": ItemData(18, ItemClassification.filler, ["Consumables"]), + "Refresher": ItemData(19, ItemClassification.filler, ["Consumables"]), + "Exit Book": ItemData(20, ItemClassification.useful, ["Spells"]), + "Cure Book": ItemData(21, ItemClassification.useful, ["Spells"]), + "Heal Book": ItemData(22, ItemClassification.useful, ["Spells"]), + "Life Book": ItemData(23, ItemClassification.useful, ["Spells"]), + "Quake Book": ItemData(24, ItemClassification.useful, ["Spells"]), + "Blizzard Book": ItemData(25, ItemClassification.useful, ["Spells"]), + "Fire Book": ItemData(26, ItemClassification.useful, ["Spells"]), + "Aero Book": ItemData(27, ItemClassification.useful, ["Spells"]), + "Thunder Seal": ItemData(28, ItemClassification.useful, ["Spells"]), + "White Seal": ItemData(29, ItemClassification.useful, ["Spells"]), + "Meteor Seal": ItemData(30, ItemClassification.useful, ["Spells"]), + "Flare Seal": ItemData(31, ItemClassification.useful, ["Spells"]), + "Progressive Sword": ItemData(32 + 256, ItemClassification.progression, ["Weapons", "Swords"]), + "Steel Sword": ItemData(32, ItemClassification.progression, ["Weapons", "Swords"]), + "Knight Sword": ItemData(33, ItemClassification.progression_skip_balancing, ["Weapons", "Swords"]), + "Excalibur": ItemData(34, ItemClassification.progression_skip_balancing, ["Weapons", "Swords"]), + "Progressive Axe": ItemData(35 + 256, ItemClassification.progression, ["Weapons", "Axes"]), + "Axe": ItemData(35, ItemClassification.progression, ["Weapons", "Axes"]), + "Battle Axe": ItemData(36, ItemClassification.progression_skip_balancing, ["Weapons", "Axes"]), + "Giant's Axe": ItemData(37, ItemClassification.progression_skip_balancing, ["Weapons", "Axes"]), + "Progressive Claw": ItemData(38 + 256, ItemClassification.progression, ["Weapons", "Axes"]), + "Cat Claw": ItemData(38, ItemClassification.progression, ["Weapons", "Claws"]), + "Charm Claw": ItemData(39, ItemClassification.progression_skip_balancing, ["Weapons", "Claws"]), + "Dragon Claw": ItemData(40, ItemClassification.progression, ["Weapons", "Claws"]), + "Progressive Bomb": ItemData(41 + 256, ItemClassification.progression, ["Weapons", "Bombs"]), + "Bomb": ItemData(41, ItemClassification.progression, ["Weapons", "Bombs"]), + "Jumbo Bomb": ItemData(42, ItemClassification.progression_skip_balancing, ["Weapons", "Bombs"]), + "Mega Grenade": ItemData(43, ItemClassification.progression, ["Weapons", "Bombs"]), + # Ally-only equipment does nothing when received, no reason to put them in the datapackage + #"Morning Star": ItemData(44, ItemClassification.progression, ["Weapons"]), + #"Bow Of Grace": ItemData(45, ItemClassification.progression, ["Weapons"]), + #"Ninja Star": ItemData(46, ItemClassification.progression, ["Weapons"]), + + "Progressive Helm": ItemData(47 + 256, ItemClassification.useful, ["Helms"]), + "Steel Helm": ItemData(47, ItemClassification.useful, ["Helms"]), + "Moon Helm": ItemData(48, ItemClassification.useful, ["Helms"]), + "Apollo Helm": ItemData(49, ItemClassification.useful, ["Helms"]), + "Progressive Armor": ItemData(50 + 256, ItemClassification.useful, ["Armors"]), + "Steel Armor": ItemData(50, ItemClassification.useful, ["Armors"]), + "Noble Armor": ItemData(51, ItemClassification.useful, ["Armors"]), + "Gaia's Armor": ItemData(52, ItemClassification.useful, ["Armors"]), + #"Replica Armor": ItemData(53, ItemClassification.progression, ["Armors"]), + #"Mystic Robes": ItemData(54, ItemClassification.progression, ["Armors"]), + #"Flame Armor": ItemData(55, ItemClassification.progression, ["Armors"]), + #"Black Robe": ItemData(56, ItemClassification.progression, ["Armors"]), + "Progressive Shield": ItemData(57 + 256, ItemClassification.useful, ["Shields"]), + "Steel Shield": ItemData(57, ItemClassification.useful, ["Shields"]), + "Venus Shield": ItemData(58, ItemClassification.useful, ["Shields"]), + "Aegis Shield": ItemData(59, ItemClassification.useful, ["Shields"]), + #"Ether Shield": ItemData(60, ItemClassification.progression, ["Shields"]), + "Progressive Accessory": ItemData(61 + 256, ItemClassification.useful, ["Accessories"]), + "Charm": ItemData(61, ItemClassification.useful, ["Accessories"]), + "Magic Ring": ItemData(62, ItemClassification.useful, ["Accessories"]), + "Cupid Locket": ItemData(63, ItemClassification.useful, ["Accessories"]), + + # these are understood by FFMQR and I could place these if I want, but it's easier to just let FFMQR + # place them. I want an option to make shuffle battlefield rewards NOT color-code the battlefields, + # and then I would make the non-item reward battlefields into AP checks and these would be put into those as + # the item for AP. But there is no such option right now. + # "54 XP": ItemData(96, ItemClassification.filler, data_name="Xp54"), + # "99 XP": ItemData(97, ItemClassification.filler, data_name="Xp99"), + # "540 XP": ItemData(98, ItemClassification.filler, data_name="Xp540"), + # "744 XP": ItemData(99, ItemClassification.filler, data_name="Xp744"), + # "816 XP": ItemData(100, ItemClassification.filler, data_name="Xp816"), + # "1068 XP": ItemData(101, ItemClassification.filler, data_name="Xp1068"), + # "1200 XP": ItemData(102, ItemClassification.filler, data_name="Xp1200"), + # "2700 XP": ItemData(103, ItemClassification.filler, data_name="Xp2700"), + # "2808 XP": ItemData(104, ItemClassification.filler, data_name="Xp2808"), + # "150 Gp": ItemData(105, ItemClassification.filler, data_name="Gp150"), + # "300 Gp": ItemData(106, ItemClassification.filler, data_name="Gp300"), + # "600 Gp": ItemData(107, ItemClassification.filler, data_name="Gp600"), + # "900 Gp": ItemData(108, ItemClassification.filler, data_name="Gp900"), + # "1200 Gp": ItemData(109, ItemClassification.filler, data_name="Gp1200"), + + + "Bomb Refill": ItemData(221, ItemClassification.filler, ["Refills"]), + "Projectile Refill": ItemData(222, ItemClassification.filler, ["Refills"]), + #"None": ItemData(255, ItemClassification.progression, []), + + "Kaeli 1": ItemData(None, ItemClassification.progression), + "Kaeli 2": ItemData(None, ItemClassification.progression), + "Tristam": ItemData(None, ItemClassification.progression), + "Phoebe 1": ItemData(None, ItemClassification.progression), + "Reuben 1": ItemData(None, ItemClassification.progression), + "Reuben Dad Saved": ItemData(None, ItemClassification.progression), + "Otto": ItemData(None, ItemClassification.progression), + "Captain Mac": ItemData(None, ItemClassification.progression), + "Ship Steering Wheel": ItemData(None, ItemClassification.progression), + "Minotaur": ItemData(None, ItemClassification.progression), + "Flamerus Rex": ItemData(None, ItemClassification.progression), + "Phanquid": ItemData(None, ItemClassification.progression), + "Freezer Crab": ItemData(None, ItemClassification.progression), + "Ice Golem": ItemData(None, ItemClassification.progression), + "Jinn": ItemData(None, ItemClassification.progression), + "Medusa": ItemData(None, ItemClassification.progression), + "Dualhead Hydra": ItemData(None, ItemClassification.progression), + "Gidrah": ItemData(None, ItemClassification.progression), + "Dullahan": ItemData(None, ItemClassification.progression), + "Pazuzu": ItemData(None, ItemClassification.progression), + "Aquaria Plaza": ItemData(None, ItemClassification.progression), + "Summer Aquaria": ItemData(None, ItemClassification.progression), + "Reuben Mine": ItemData(None, ItemClassification.progression), + "Alive Forest": ItemData(None, ItemClassification.progression), + "Rainbow Bridge": ItemData(None, ItemClassification.progression), + "Collapse Spencer's Cave": ItemData(None, ItemClassification.progression), + "Ship Liberated": ItemData(None, ItemClassification.progression), + "Ship Loaned": ItemData(None, ItemClassification.progression), + "Ship Dock Access": ItemData(None, ItemClassification.progression), + "Stone Golem": ItemData(None, ItemClassification.progression), + "Twinhead Wyvern": ItemData(None, ItemClassification.progression), + "Zuh": ItemData(None, ItemClassification.progression), + + "Libra Temple Crest Tile": ItemData(None, ItemClassification.progression), + "Life Temple Crest Tile": ItemData(None, ItemClassification.progression), + "Aquaria Vendor Crest Tile": ItemData(None, ItemClassification.progression), + "Fireburg Vendor Crest Tile": ItemData(None, ItemClassification.progression), + "Fireburg Grenademan Crest Tile": ItemData(None, ItemClassification.progression), + "Sealed Temple Crest Tile": ItemData(None, ItemClassification.progression), + "Wintry Temple Crest Tile": ItemData(None, ItemClassification.progression), + "Kaidge Temple Crest Tile": ItemData(None, ItemClassification.progression), + "Light Temple Crest Tile": ItemData(None, ItemClassification.progression), + "Windia Kids Crest Tile": ItemData(None, ItemClassification.progression), + "Windia Dock Crest Tile": ItemData(None, ItemClassification.progression), + "Ship Dock Crest Tile": ItemData(None, ItemClassification.progression), + "Alive Forest Libra Crest Tile": ItemData(None, ItemClassification.progression), + "Alive Forest Gemini Crest Tile": ItemData(None, ItemClassification.progression), + "Alive Forest Mobius Crest Tile": ItemData(None, ItemClassification.progression), + "Wood House Libra Crest Tile": ItemData(None, ItemClassification.progression), + "Wood House Gemini Crest Tile": ItemData(None, ItemClassification.progression), + "Wood House Mobius Crest Tile": ItemData(None, ItemClassification.progression), + "Barrel Pushed": ItemData(None, ItemClassification.progression), + "Long Spine Bombed": ItemData(None, ItemClassification.progression), + "Short Spine Bombed": ItemData(None, ItemClassification.progression), + "Skull 1 Bombed": ItemData(None, ItemClassification.progression), + "Skull 2 Bombed": ItemData(None, ItemClassification.progression), + "Ice Pyramid 1F Statue": ItemData(None, ItemClassification.progression), + "Ice Pyramid 3F Statue": ItemData(None, ItemClassification.progression), + "Ice Pyramid 4F Statue": ItemData(None, ItemClassification.progression), + "Ice Pyramid 5F Statue": ItemData(None, ItemClassification.progression), + "Spencer Cave Libra Block Bombed": ItemData(None, ItemClassification.progression), + "Lava Dome Plate": ItemData(None, ItemClassification.progression), + "Pazuzu 2F Lock": ItemData(None, ItemClassification.progression), + "Pazuzu 4F Lock": ItemData(None, ItemClassification.progression), + "Pazuzu 6F Lock": ItemData(None, ItemClassification.progression), + "Pazuzu 1F": ItemData(None, ItemClassification.progression), + "Pazuzu 2F": ItemData(None, ItemClassification.progression), + "Pazuzu 3F": ItemData(None, ItemClassification.progression), + "Pazuzu 4F": ItemData(None, ItemClassification.progression), + "Pazuzu 5F": ItemData(None, ItemClassification.progression), + "Pazuzu 6F": ItemData(None, ItemClassification.progression), + "Dark King": ItemData(None, ItemClassification.progression), + #"Barred": ItemData(None, ItemClassification.progression), + +} + +prog_map = { + "Swords": "Progressive Sword", + "Axes": "Progressive Axe", + "Claws": "Progressive Claw", + "Bombs": "Progressive Bomb", + "Shields": "Progressive Shield", + "Armors": "Progressive Armor", + "Helms": "Progressive Helm", + "Accessories": "Progressive Accessory", +} + + +def yaml_item(text): + if text == "CaptainCap": + return "Captain's Cap" + elif text == "WakeWater": + return "Wakewater" + return "".join( + [(" " + c if (c.isupper() or c.isnumeric()) and not (text[i - 1].isnumeric() and c == "F") else c) for + i, c in enumerate(text)]).strip() + + +item_groups = {} +for item, data in item_table.items(): + for group in data.groups: + item_groups[group] = item_groups.get(group, []) + [item] + + +def create_items(self) -> None: + items = [] + starting_weapon = self.multiworld.starting_weapon[self.player].current_key.title().replace("_", " ") + if self.multiworld.progressive_gear[self.player]: + for item_group in prog_map: + if starting_weapon in self.item_name_groups[item_group]: + starting_weapon = prog_map[item_group] + break + self.multiworld.push_precollected(self.create_item(starting_weapon)) + self.multiworld.push_precollected(self.create_item("Steel Armor")) + if self.multiworld.sky_coin_mode[self.player] == "start_with": + self.multiworld.push_precollected(self.create_item("Sky Coin")) + + precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]} + + def add_item(item_name): + if item_name in ["Steel Armor", "Sky Fragment"] or "Progressive" in item_name: + return + if item_name.lower().replace(" ", "_") == self.multiworld.starting_weapon[self.player].current_key: + return + if self.multiworld.progressive_gear[self.player]: + for item_group in prog_map: + if item_name in self.item_name_groups[item_group]: + item_name = prog_map[item_group] + break + if item_name == "Sky Coin": + if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + for _ in range(40): + items.append(self.create_item("Sky Fragment")) + return + elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": + items.append(self.create_filler()) + return + if item_name in precollected_item_names: + items.append(self.create_filler()) + return + i = self.create_item(item_name) + if self.multiworld.logic[self.player] != "friendly" and item_name in ("Magic Mirror", "Mask"): + i.classification = ItemClassification.useful + if (self.multiworld.logic[self.player] == "expert" and self.multiworld.map_shuffle[self.player] == "none" and + item_name == "Exit Book"): + i.classification = ItemClassification.progression + items.append(i) + + for item_group in ("Key Items", "Spells", "Armors", "Helms", "Shields", "Accessories", "Weapons"): + for item in self.item_name_groups[item_group]: + add_item(item) + + if self.multiworld.brown_boxes[self.player] == "include": + filler_items = [] + for item, count in fillers.items(): + filler_items += [self.create_item(item) for _ in range(count)] + if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + self.multiworld.random.shuffle(filler_items) + filler_items = filler_items[39:] + items += filler_items + + self.multiworld.itempool += items + + if len(self.multiworld.player_ids) > 1: + early_choices = ["Sand Coin", "River Coin"] + early_item = self.multiworld.random.choice(early_choices) + self.multiworld.early_items[self.player][early_item] = 1 + + +class FFMQItem(Item): + game = "Final Fantasy Mystic Quest" + type = None + + def __init__(self, name, player: int = None): + item_data = item_table[name] + super(FFMQItem, self).__init__( + name, + item_data.classification, + item_data.id, player + ) \ No newline at end of file diff --git a/worlds/ffmq/LICENSE b/worlds/ffmq/LICENSE new file mode 100644 index 000000000000..46ad1c007466 --- /dev/null +++ b/worlds/ffmq/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 Alex "Alchav" Avery +Copyright (c) 2023 wildham + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/worlds/ffmq/Options.py b/worlds/ffmq/Options.py new file mode 100644 index 000000000000..2746bb197743 --- /dev/null +++ b/worlds/ffmq/Options.py @@ -0,0 +1,258 @@ +from Options import Choice, FreeText, Toggle + + +class Logic(Choice): + """Placement logic sets the rules that will be applied when placing items. Friendly: Required Items to clear a + dungeon will never be placed in that dungeon to avoid the need to revisit it. Also, the Magic Mirror and the Mask + will always be available before Ice Pyramid and Volcano, respectively. Note: If Dungeons are shuffled, Friendly + logic will only ensure the availability of the Mirror and the Mask. Standard: Items are randomly placed and logic + merely verifies that they're all accessible. As for Region access, only the Coins are considered. Expert: Same as + Standard, but Items Placement logic also includes other routes than Coins: the Crests Teleporters, the + Fireburg-Aquaria Lava bridge and the Sealed Temple Exit trick.""" + option_friendly = 0 + option_standard = 1 + option_expert = 2 + default = 1 + display_name = "Logic" + + +class BrownBoxes(Choice): + """Include the 201 brown box locations from the original game. Brown Boxes are all the boxes that contained a + consumable in the original game. If shuffle is chosen, the consumables contained will be shuffled but the brown + boxes will not be Archipelago location checks.""" + option_exclude = 0 + option_include = 1 + option_shuffle = 2 + default = 1 + display_name = "Brown Boxes" + + +class SkyCoinMode(Choice): + """Configure how the Sky Coin is acquired. With standard, the Sky Coin will be placed randomly. With Start With, the + Sky Coin will be in your inventory at the start of the game. With Save The Crystals, the Sky Coin will be acquired + once you save all 4 crystals. With Shattered Sky Coin, the Sky Coin is split in 40 fragments; you can enter Doom + Castle once the required amount is found. Shattered Sky Coin will force brown box locations to be included.""" + option_standard = 0 + option_start_with = 1 + option_save_the_crystals = 2 + option_shattered_sky_coin = 3 + default = 0 + display_name = "Sky Coin Mode" + + +class ShatteredSkyCoinQuantity(Choice): + """Configure the number of the 40 Sky Coin Fragments required to enter the Doom Castle. Only has an effect if + Sky Coin Mode is set to shattered. Low: 16. Mid: 24. High: 32. Random Narrow: random between 16 and 32. + Random Wide: random between 10 and 38.""" + option_low_16 = 0 + option_mid_24 = 1 + option_high_32 = 2 + option_random_narrow = 3 + option_random_wide = 4 + default = 1 + display_name = "Shattered Sky Coin" + + +class StartingWeapon(Choice): + """Choose your starting weapon.""" + display_name = "Starting Weapon" + option_steel_sword = 0 + option_axe = 1 + option_cat_claw = 2 + option_bomb = 3 + default = "random" + + +class ProgressiveGear(Toggle): + """Pieces of gear are always acquired from weakest to strongest in a set.""" + display_name = "Progressive Gear" + + +class EnemiesDensity(Choice): + """Set how many of the original enemies are on each map.""" + display_name = "Enemies Density" + option_all = 0 + option_three_quarter = 1 + option_half = 2 + option_quarter = 3 + option_none = 4 + + +class EnemyScaling(Choice): + """Superclass for enemy scaling options.""" + option_quarter = 0 + option_half = 1 + option_three_quarter = 2 + option_normal = 3 + option_one_and_quarter = 4 + option_one_and_half = 5 + option_double = 6 + option_double_and_half = 7 + option_triple = 8 + + +class EnemiesScalingLower(EnemyScaling): + """Randomly adjust enemies stats by the selected range percentage. Include mini-bosses' weaker clones.""" + display_name = "Enemies Scaling Lower" + default = 0 + + +class EnemiesScalingUpper(EnemyScaling): + """Randomly adjust enemies stats by the selected range percentage. Include mini-bosses' weaker clones.""" + display_name = "Enemies Scaling Upper" + default = 4 + + +class BossesScalingLower(EnemyScaling): + """Randomly adjust bosses stats by the selected range percentage. Include Mini-Bosses, Bosses, Bosses' refights and + the Dark King.""" + display_name = "Bosses Scaling Lower" + default = 0 + + +class BossesScalingUpper(EnemyScaling): + """Randomly adjust bosses stats by the selected range percentage. Include Mini-Bosses, Bosses, Bosses' refights and + the Dark King.""" + display_name = "Bosses Scaling Upper" + default = 4 + + +class EnemizerAttacks(Choice): + """Shuffles enemy attacks. Standard: No shuffle. Safe: Randomize every attack but leave out self-destruct and Dark + King attacks. Chaos: Randomize and include self-destruct and Dark King attacks. Self Destruct: Every enemy + self-destructs. Simple Shuffle: Instead of randomizing, shuffle one monster's attacks to another. Dark King is left + vanilla.""" + display_name = "Enemizer Attacks" + option_normal = 0 + option_safe = 1 + option_chaos = 2 + option_self_destruct = 3 + option_simple_shuffle = 4 + default = 0 + + +class ShuffleEnemiesPositions(Toggle): + """Instead of their original position in a given map, enemies are randomly placed.""" + display_name = "Shuffle Enemies' Positions" + default = 1 + + +class ProgressiveFormations(Choice): + """Enemies' formations are selected by regions, with the weakest formations always selected in Foresta and the + strongest in Windia. Disabled: Standard formations are used. Regions Strict: Formations will come exclusively + from the current region, whatever the map is. Regions Keep Type: Formations will keep the original formation type + and match with the nearest power level.""" + display_name = "Progressive Formations" + option_disabled = 0 + option_regions_strict = 1 + option_regions_keep_type = 2 + + +class DoomCastle(Choice): + """Configure how you reach the Dark King. With Standard, you need to defeat all four bosses and their floors to + reach the Dark King. With Boss Rush, only the bosses are blocking your way in the corridor to the Dark King's room. + With Dark King Only, the way to the Dark King is free of any obstacle.""" + display_name = "Doom Castle" + option_standard = 0 + option_boss_rush = 1 + option_dark_king_only = 2 + + +class DoomCastleShortcut(Toggle): + """Create a shortcut granting access from the start to Doom Castle at Focus Tower's entrance. + Also modify the Desert floor, so it can be navigated without the Mega Grenades and the Dragon Claw.""" + display_name = "Doom Castle Shortcut" + + +class TweakFrustratingDungeons(Toggle): + """Make some small changes to a few of the most annoying dungeons. Ice Pyramid: Add 3 shortcuts on the 1st floor. + Giant Tree: Add shortcuts on the 1st and 4th floors and curtail mushrooms population. + Pazuzu's Tower: Staircases are devoid of enemies (regardless of Enemies Density settings).""" + display_name = "Tweak Frustrating Dungeons" + + +class MapShuffle(Choice): + """None: No shuffle. Overworld: Only shuffle the Overworld locations. Dungeons: Only shuffle the dungeons' floors + amongst themselves. Temples and Towns aren't included. Overworld And Dungeons: Shuffle the Overworld and dungeons + at the same time. Everything: Shuffle the Overworld, dungeons, temples and towns all amongst each others. + When dungeons are shuffled, defeating Pazuzu won't teleport you to the 7th floor, you have to get there normally to + save the Crystal and get Pazuzu's Chest.""" + display_name = "Map Shuffle" + option_none = 0 + option_overworld = 1 + option_dungeons = 2 + option_overworld_and_dungeons = 3 + option_everything = 4 + default = 0 + + +class CrestShuffle(Toggle): + """Shuffle the Crest tiles amongst themselves.""" + display_name = "Crest Shuffle" + + +class MapShuffleSeed(FreeText): + """If this is a number, it will be used as a set seed number for Map, Crest, and Battlefield Reward shuffles. + If this is "random" the seed will be chosen randomly. If it is any other text, it will be used as a seed group name. + All players using the same seed group name will get the same shuffle results, as long as their Map Shuffle, + Crest Shuffle, and Shuffle Battlefield Rewards settings are the same.""" + display_name = "Map Shuffle Seed" + default = "random" + + +class LevelingCurve(Choice): + """Adjust the level gain rate.""" + display_name = "Leveling Curve" + option_half = 0 + option_normal = 1 + option_one_and_half = 2 + option_double = 3 + option_double_and_half = 4 + option_triple = 5 + option_quadruple = 6 + default = 4 + + +class ShuffleBattlefieldRewards(Toggle): + """Shuffle the type of reward (Item, XP, GP) given by battlefields and color code them by reward type. + Blue: Give an item. Grey: Give XP. Green: Give GP.""" + display_name = "Shuffle Battlefield Rewards" + + +class BattlefieldsBattlesQuantities(Choice): + """Adjust the number of battles that need to be fought to get a battlefield's reward.""" + display_name = "Battlefields Battles Quantity" + option_ten = 0 + option_seven = 1 + option_five = 2 + option_three = 3 + option_one = 4 + option_random_one_through_five = 5 + option_random_one_through_ten = 6 + + +option_definitions = { + "logic": Logic, + "brown_boxes": BrownBoxes, + "sky_coin_mode": SkyCoinMode, + "shattered_sky_coin_quantity": ShatteredSkyCoinQuantity, + "starting_weapon": StartingWeapon, + "progressive_gear": ProgressiveGear, + "enemies_density": EnemiesDensity, + "enemies_scaling_lower": EnemiesScalingLower, + "enemies_scaling_upper": EnemiesScalingUpper, + "bosses_scaling_lower": BossesScalingLower, + "bosses_scaling_upper": BossesScalingUpper, + "enemizer_attacks": EnemizerAttacks, + "shuffle_enemies_position": ShuffleEnemiesPositions, + "progressive_formations": ProgressiveFormations, + "doom_castle_mode": DoomCastle, + "doom_castle_shortcut": DoomCastleShortcut, + "tweak_frustrating_dungeons": TweakFrustratingDungeons, + "map_shuffle": MapShuffle, + "crest_shuffle": CrestShuffle, + "shuffle_battlefield_rewards": ShuffleBattlefieldRewards, + "map_shuffle_seed": MapShuffleSeed, + "leveling_curve": LevelingCurve, + "battlefields_battles_quantities": BattlefieldsBattlesQuantities, +} diff --git a/worlds/ffmq/Output.py b/worlds/ffmq/Output.py new file mode 100644 index 000000000000..c4c4605c8512 --- /dev/null +++ b/worlds/ffmq/Output.py @@ -0,0 +1,113 @@ +import yaml +import os +import zipfile +from copy import deepcopy +from .Regions import object_id_table +from Main import __version__ +from worlds.Files import APContainer +import pkgutil + +settings_template = yaml.load(pkgutil.get_data(__name__, "data/settings.yaml"), yaml.Loader) + + +def generate_output(self, output_directory): + def output_item_name(item): + if item.player == self.player: + if item.code > 0x420000 + 256: + item_name = self.item_id_to_name[item.code - 256] + else: + item_name = item.name + item_name = "".join(item_name.split("'")) + item_name = "".join(item_name.split(" ")) + else: + if item.advancement or item.useful or (item.trap and + self.multiworld.per_slot_randoms[self.player].randint(0, 1)): + item_name = "APItem" + else: + item_name = "APItemFiller" + return item_name + + item_placement = [] + for location in self.multiworld.get_locations(self.player): + if location.type != "Trigger": + item_placement.append({"object_id": object_id_table[location.name], "type": location.type, "content": + output_item_name(location.item), "player": self.multiworld.player_name[location.item.player], + "item_name": location.item.name}) + + def cc(option): + return option.current_key.title().replace("_", "").replace("OverworldAndDungeons", "OverworldDungeons") + + def tf(option): + return True if option else False + + options = deepcopy(settings_template) + options["name"] = self.multiworld.player_name[self.player] + + option_writes = { + "enemies_density": cc(self.multiworld.enemies_density[self.player]), + "chests_shuffle": "Include", + "shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle", + "npcs_shuffle": "Include", + "battlefields_shuffle": "Include", + "logic_options": cc(self.multiworld.logic[self.player]), + "shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]), + "enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]), + "enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]), + "bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]), + "bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]), + "enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]), + "leveling_curve": cc(self.multiworld.leveling_curve[self.player]), + "battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if + self.multiworld.battlefields_battles_quantities[self.player].value < 5 else + "RandomLow" if + self.multiworld.battlefields_battles_quantities[self.player].value == 5 else + "RandomHigh", + "shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]), + "random_starting_weapon": True, + "progressive_gear": tf(self.multiworld.progressive_gear[self.player]), + "tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]), + "doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]), + "doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]), + "sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]), + "sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]), + "enable_spoilers": False, + "progressive_formations": cc(self.multiworld.progressive_formations[self.player]), + "map_shuffling": cc(self.multiworld.map_shuffle[self.player]), + "crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]), + } + for option, data in option_writes.items(): + options["Final Fantasy Mystic Quest"][option][data] = 1 + + rom_name = f'MQ{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21] + self.rom_name = bytearray(rom_name, + 'utf8') + self.rom_name_available_event.set() + + setup = {"version": "1.4", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed": + hex(self.multiworld.per_slot_randoms[self.player].randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} + + starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]] + if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + starting_items.append("SkyCoin") + + file_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.apmq") + + APMQ = APMQFile(file_path, player=self.player, player_name=self.multiworld.player_name[self.player]) + with zipfile.ZipFile(file_path, mode="w", compression=zipfile.ZIP_DEFLATED, + compresslevel=9) as zf: + zf.writestr("itemplacement.yaml", yaml.dump(item_placement)) + zf.writestr("flagset.yaml", yaml.dump(options)) + zf.writestr("startingitems.yaml", yaml.dump(starting_items)) + zf.writestr("setup.yaml", yaml.dump(setup)) + zf.writestr("rooms.yaml", yaml.dump(self.rooms)) + + APMQ.write_contents(zf) + + +class APMQFile(APContainer): + game = "Final Fantasy Mystic Quest" + + def get_manifest(self): + manifest = super().get_manifest() + manifest["patch_file_ending"] = ".apmq" + return manifest \ No newline at end of file diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py new file mode 100644 index 000000000000..aac8289a3600 --- /dev/null +++ b/worlds/ffmq/Regions.py @@ -0,0 +1,251 @@ +from BaseClasses import Region, MultiWorld, Entrance, Location, LocationProgressType, ItemClassification +from worlds.generic.Rules import add_rule +from .Items import item_groups, yaml_item +import pkgutil +import yaml + +rooms = yaml.load(pkgutil.get_data(__name__, "data/rooms.yaml"), yaml.Loader) +entrance_names = {entrance["id"]: entrance["name"] for entrance in yaml.load(pkgutil.get_data(__name__, "data/entrances.yaml"), yaml.Loader)} + +object_id_table = {} +object_type_table = {} +offset = {"Chest": 0x420000, "Box": 0x420000, "NPC": 0x420000 + 300, "BattlefieldItem": 0x420000 + 350} +for room in rooms: + for object in room["game_objects"]: + if "Hero Chest" in object["name"] or object["type"] == "Trigger": + continue + if object["type"] in ("BattlefieldItem", "BattlefieldXp", "BattlefieldGp"): + object_type_table[object["name"]] = "BattlefieldItem" + elif object["type"] in ("Chest", "NPC", "Box"): + object_type_table[object["name"]] = object["type"] + object_id_table[object["name"]] = object["object_id"] + +location_table = {loc_name: offset[object_type_table[loc_name]] + obj_id for loc_name, obj_id in + object_id_table.items()} + +weapons = ("Claw", "Bomb", "Sword", "Axe") +crest_warps = [51, 52, 53, 76, 96, 108, 158, 171, 175, 191, 275, 276, 277, 308, 334, 336, 396, 397] + + +def process_rules(spot, access): + for weapon in weapons: + if weapon in access: + add_rule(spot, lambda state, w=weapon: state.has_any(item_groups[w + "s"], spot.player)) + access = [yaml_item(rule) for rule in access if rule not in weapons] + add_rule(spot, lambda state: state.has_all(access, spot.player)) + + +def create_region(world: MultiWorld, player: int, name: str, room_id=None, locations=None, links=None): + if links is None: + links = [] + ret = Region(name, player, world) + if locations: + for location in locations: + location.parent_region = ret + ret.locations.append(location) + ret.links = links + ret.id = room_id + return ret + + +def get_entrance_to(entrance_to): + for room in rooms: + if room["id"] == entrance_to["target_room"]: + for link in room["links"]: + if link["target_room"] == entrance_to["room"]: + return link + else: + raise Exception(f"Did not find entrance {entrance_to}") + + +def create_regions(self): + + menu_region = create_region(self.multiworld, self.player, "Menu") + self.multiworld.regions.append(menu_region) + + for room in self.rooms: + self.multiworld.regions.append(create_region(self.multiworld, self.player, room["name"], room["id"], + [FFMQLocation(self.player, object["name"], location_table[object["name"]] if object["name"] in + location_table else None, object["type"], object["access"], + self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for + object in room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in + ("BattlefieldGp", "BattlefieldXp") and (object["type"] != "Box" or + self.multiworld.brown_boxes[self.player] == "include")], room["links"])) + + dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player) + dark_king = FFMQLocation(self.player, "Dark King", None, "Trigger", []) + dark_king.parent_region = dark_king_room + dark_king.place_locked_item(self.create_item("Dark King")) + dark_king_room.locations.append(dark_king) + + connection = Entrance(self.player, f"Enter Overworld", menu_region) + connection.connect(self.multiworld.get_region("Overworld", self.player)) + menu_region.exits.append(connection) + + for region in self.multiworld.get_regions(self.player): + for link in region.links: + for connect_room in self.multiworld.get_regions(self.player): + if connect_room.id == link["target_room"]: + connection = Entrance(self.player, entrance_names[link["entrance"]] if "entrance" in link and + link["entrance"] != -1 else f"{region.name} to {connect_room.name}", region) + if "entrance" in link and link["entrance"] != -1: + spoiler = False + if link["entrance"] in crest_warps: + if self.multiworld.crest_shuffle[self.player]: + spoiler = True + elif self.multiworld.map_shuffle[self.player] == "everything": + spoiler = True + elif "Subregion" in region.name and self.multiworld.map_shuffle[self.player] not in ("dungeons", + "none"): + spoiler = True + elif "Subregion" not in region.name and self.multiworld.map_shuffle[self.player] not in ("none", + "overworld"): + spoiler = True + + if spoiler: + self.multiworld.spoiler.set_entrance(entrance_names[link["entrance"]], connect_room.name, + 'both', self.player) + if link["access"]: + process_rules(connection, link["access"]) + region.exits.append(connection) + connection.connect(connect_room) + break + +non_dead_end_crest_rooms = [ + 'Libra Temple', 'Aquaria Gemini Room', "GrenadeMan's Mobius Room", 'Fireburg Gemini Room', + 'Sealed Temple', 'Alive Forest', 'Kaidge Temple Upper Ledge', + 'Windia Kid House Basement', 'Windia Old People House Basement' +] + +non_dead_end_crest_warps = [ + 'Libra Temple - Libra Tile Script', 'Aquaria Gemini Room - Gemini Script', + 'GrenadeMan Mobius Room - Mobius Teleporter Script', 'Fireburg Gemini Room - Gemini Teleporter Script', + 'Sealed Temple - Gemini Tile Script', 'Alive Forest - Libra Teleporter Script', + 'Alive Forest - Gemini Teleporter Script', 'Alive Forest - Mobius Teleporter Script', + 'Kaidge Temple - Mobius Teleporter Script', 'Windia Kid House Basement - Mobius Teleporter', + 'Windia Old People House Basement - Mobius Teleporter Script', +] + + +vendor_locations = ["Aquaria - Vendor", "Fireburg - Vendor", "Windia - Vendor"] + + +def set_rules(self) -> None: + self.multiworld.completion_condition[self.player] = lambda state: state.has("Dark King", self.player) + + def hard_boss_logic(state): + return state.has_all(["River Coin", "Sand Coin"], self.player) + + add_rule(self.multiworld.get_location("Pazuzu 1F", self.player), hard_boss_logic) + add_rule(self.multiworld.get_location("Gidrah", self.player), hard_boss_logic) + add_rule(self.multiworld.get_location("Dullahan", self.player), hard_boss_logic) + + if self.multiworld.map_shuffle[self.player]: + for boss in ("Freezer Crab", "Ice Golem", "Jinn", "Medusa", "Dualhead Hydra"): + loc = self.multiworld.get_location(boss, self.player) + checked_regions = {loc.parent_region} + + def check_foresta(region): + if region.name == "Subregion Foresta": + add_rule(loc, hard_boss_logic) + return True + elif "Subregion" in region.name: + return True + for entrance in region.entrances: + if entrance.parent_region not in checked_regions: + checked_regions.add(entrance.parent_region) + if check_foresta(entrance.parent_region): + return True + check_foresta(loc.parent_region) + + if self.multiworld.logic[self.player] == "friendly": + process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player), + ["MagicMirror"]) + process_rules(self.multiworld.get_entrance("Overworld - Volcano", self.player), + ["Mask"]) + if self.multiworld.map_shuffle[self.player] in ("none", "overworld"): + process_rules(self.multiworld.get_entrance("Overworld - Bone Dungeon", self.player), + ["Bomb"]) + process_rules(self.multiworld.get_entrance("Overworld - Wintry Cave", self.player), + ["Bomb", "Claw"]) + process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player), + ["Bomb", "Claw"]) + process_rules(self.multiworld.get_entrance("Overworld - Mine", self.player), + ["MegaGrenade", "Claw", "Reuben1"]) + process_rules(self.multiworld.get_entrance("Overworld - Lava Dome", self.player), + ["MegaGrenade"]) + process_rules(self.multiworld.get_entrance("Overworld - Giant Tree", self.player), + ["DragonClaw", "Axe"]) + process_rules(self.multiworld.get_entrance("Overworld - Mount Gale", self.player), + ["DragonClaw"]) + process_rules(self.multiworld.get_entrance("Overworld - Pazuzu Tower", self.player), + ["DragonClaw", "Bomb"]) + process_rules(self.multiworld.get_entrance("Overworld - Mac Ship", self.player), + ["DragonClaw", "CaptainCap"]) + process_rules(self.multiworld.get_entrance("Overworld - Mac Ship Doom", self.player), + ["DragonClaw", "CaptainCap"]) + + if self.multiworld.logic[self.player] == "expert": + if self.multiworld.map_shuffle[self.player] == "none" and not self.multiworld.crest_shuffle[self.player]: + inner_room = self.multiworld.get_region("Wintry Temple Inner Room", self.player) + connection = Entrance(self.player, "Sealed Temple Exit Trick", inner_room) + connection.connect(self.multiworld.get_region("Wintry Temple Outer Room", self.player)) + connection.access_rule = lambda state: state.has("Exit Book", self.player) + inner_room.exits.append(connection) + else: + for crest_warp in non_dead_end_crest_warps: + entrance = self.multiworld.get_entrance(crest_warp, self.player) + if entrance.connected_region.name in non_dead_end_crest_rooms: + entrance.access_rule = lambda state: False + + if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + logic_coins = [16, 24, 32, 32, 38][self.multiworld.shattered_sky_coin_quantity[self.player].value] + self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ + lambda state: state.has("Sky Fragment", self.player, logic_coins) + elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": + self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ + lambda state: state.has_all(["Flamerus Rex", "Dualhead Hydra", "Ice Golem", "Pazuzu"], self.player) + elif self.multiworld.sky_coin_mode[self.player] in ("standard", "start_with"): + self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ + lambda state: state.has("Sky Coin", self.player) + + +def stage_set_rules(multiworld): + # If there's no enemies, there's no repeatable income sources + no_enemies_players = [player for player in multiworld.get_game_players("Final Fantasy Mystic Quest") + if multiworld.enemies_density[player] == "none"] + if (len([item for item in multiworld.itempool if item.classification in (ItemClassification.filler, + ItemClassification.trap)]) > len([player for player in no_enemies_players if + multiworld.accessibility[player] == "minimal"]) * 3): + for player in no_enemies_players: + for location in vendor_locations: + if multiworld.accessibility[player] == "locations": + print("exclude") + multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED + else: + print("unreachable") + multiworld.get_location(location, player).access_rule = lambda state: False + else: + # There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing + # advancement items so that useful items can be placed. + print("no advancement") + for player in no_enemies_players: + for location in vendor_locations: + multiworld.get_location(location, player).item_rule = lambda item: not item.advancement + + + + +class FFMQLocation(Location): + game = "Final Fantasy Mystic Quest" + + def __init__(self, player, name, address, loc_type, access=None, event=None): + super(FFMQLocation, self).__init__( + player, name, + address + ) + self.type = loc_type + if access: + process_rules(self, access) + if event: + self.place_locked_item(event) diff --git a/worlds/ffmq/__init__.py b/worlds/ffmq/__init__.py new file mode 100644 index 000000000000..b6f19a77fb53 --- /dev/null +++ b/worlds/ffmq/__init__.py @@ -0,0 +1,217 @@ +import Utils +import settings +import base64 +import threading +import requests +import yaml +from worlds.AutoWorld import World, WebWorld +from BaseClasses import Tutorial +from .Regions import create_regions, location_table, set_rules, stage_set_rules, rooms, non_dead_end_crest_rooms,\ + non_dead_end_crest_warps +from .Items import item_table, item_groups, create_items, FFMQItem, fillers +from .Output import generate_output +from .Options import option_definitions +from .Client import FFMQClient + + +# removed until lists are supported +# class FFMQSettings(settings.Group): +# class APIUrls(list): +# """A list of API URLs to get map shuffle, crest shuffle, and battlefield reward shuffle data from.""" +# api_urls: APIUrls = [ +# "https://api.ffmqrando.net/", +# "http://ffmqr.jalchavware.com:5271/" +# ] + + +class FFMQWebWorld(WebWorld): + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to playing Final Fantasy Mystic Quest with Archipelago.", + "English", + "setup_en.md", + "setup/en", + ["Alchav"] + )] + + +class FFMQWorld(World): + """Final Fantasy: Mystic Quest is a simple, humorous RPG for the Super Nintendo. You travel across four continents, + linked in the middle of the world by the Focus Tower, which has been locked by four magical coins. Make your way to + the bottom of the Focus Tower, then straight up through the top!""" + # -Giga Otomia + + game = "Final Fantasy Mystic Quest" + + item_name_to_id = {name: data.id for name, data in item_table.items() if data.id is not None} + location_name_to_id = location_table + option_definitions = option_definitions + + topology_present = True + + item_name_groups = item_groups + + generate_output = generate_output + create_items = create_items + create_regions = create_regions + set_rules = set_rules + stage_set_rules = stage_set_rules + + data_version = 1 + + web = FFMQWebWorld() + # settings: FFMQSettings + + def __init__(self, world, player: int): + self.rom_name_available_event = threading.Event() + self.rom_name = None + self.rooms = None + super().__init__(world, player) + + def generate_early(self): + if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": + self.multiworld.brown_boxes[self.player].value = 1 + if self.multiworld.enemies_scaling_lower[self.player].value > \ + self.multiworld.enemies_scaling_upper[self.player].value: + (self.multiworld.enemies_scaling_lower[self.player].value, + self.multiworld.enemies_scaling_upper[self.player].value) =\ + (self.multiworld.enemies_scaling_upper[self.player].value, + self.multiworld.enemies_scaling_lower[self.player].value) + if self.multiworld.bosses_scaling_lower[self.player].value > \ + self.multiworld.bosses_scaling_upper[self.player].value: + (self.multiworld.bosses_scaling_lower[self.player].value, + self.multiworld.bosses_scaling_upper[self.player].value) =\ + (self.multiworld.bosses_scaling_upper[self.player].value, + self.multiworld.bosses_scaling_lower[self.player].value) + + @classmethod + def stage_generate_early(cls, multiworld): + + # api_urls = Utils.get_options()["ffmq_options"].get("api_urls", None) + api_urls = [ + "https://api.ffmqrando.net/", + "http://ffmqr.jalchavware.com:5271/" + ] + + rooms_data = {} + + for world in multiworld.get_game_worlds("Final Fantasy Mystic Quest"): + if (world.multiworld.map_shuffle[world.player] or world.multiworld.crest_shuffle[world.player] or + world.multiworld.crest_shuffle[world.player]): + if world.multiworld.map_shuffle_seed[world.player].value.isdigit(): + multiworld.random.seed(int(world.multiworld.map_shuffle_seed[world.player].value)) + elif world.multiworld.map_shuffle_seed[world.player].value != "random": + multiworld.random.seed(int(hash(world.multiworld.map_shuffle_seed[world.player].value)) + + int(world.multiworld.seed)) + + seed = hex(multiworld.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper() + map_shuffle = multiworld.map_shuffle[world.player].value + crest_shuffle = multiworld.crest_shuffle[world.player].current_key + battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key + + query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}" + + if query in rooms_data: + world.rooms = rooms_data[query] + continue + + if not api_urls: + raise Exception("No FFMQR API URLs specified in host.yaml") + + errors = [] + for api_url in api_urls.copy(): + try: + response = requests.get(f"{api_url}GenerateRooms?{query}") + except (ConnectionError, requests.exceptions.HTTPError, requests.exceptions.ConnectionError, + requests.exceptions.RequestException) as err: + api_urls.remove(api_url) + errors.append([api_url, err]) + else: + if response.ok: + world.rooms = rooms_data[query] = yaml.load(response.text, yaml.Loader) + break + else: + api_urls.remove(api_url) + errors.append([api_url, response]) + else: + error_text = f"Failed to fetch map shuffle data for FFMQ player {world.player}" + for error in errors: + error_text += f"\n{error[0]} - got error {error[1].status_code} {error[1].reason} {error[1].text}" + raise Exception(error_text) + api_urls.append(api_urls.pop(0)) + else: + world.rooms = rooms + + def create_item(self, name: str): + return FFMQItem(name, self.player) + + def collect_item(self, state, item, remove=False): + if "Progressive" in item.name: + i = item.code - 256 + if state.has(self.item_id_to_name[i], self.player): + if state.has(self.item_id_to_name[i+1], self.player): + return self.item_id_to_name[i+2] + return self.item_id_to_name[i+1] + return self.item_id_to_name[i] + return item.name if item.advancement else None + + def modify_multidata(self, multidata): + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + payload = multidata["connect_names"][self.multiworld.player_name[self.player]] + multidata["connect_names"][new_name] = payload + + def get_filler_item_name(self): + r = self.multiworld.random.randint(0, 201) + for item, count in fillers.items(): + r -= count + r -= fillers[item] + if r <= 0: + return item + + def extend_hint_information(self, hint_data): + hint_data[self.player] = {} + if self.multiworld.map_shuffle[self.player]: + single_location_regions = ["Subregion Volcano Battlefield", "Subregion Mac's Ship", "Subregion Doom Castle"] + for subregion in ["Subregion Foresta", "Subregion Aquaria", "Subregion Frozen Fields", "Subregion Fireburg", + "Subregion Volcano Battlefield", "Subregion Windia", "Subregion Mac's Ship", + "Subregion Doom Castle"]: + region = self.multiworld.get_region(subregion, self.player) + for location in region.locations: + if location.address and self.multiworld.map_shuffle[self.player] != "dungeons": + hint_data[self.player][location.address] = (subregion.split("Subregion ")[-1] + + (" Region" if subregion not in + single_location_regions else "")) + for overworld_spot in region.exits: + if ("Subregion" in overworld_spot.connected_region.name or + overworld_spot.name == "Overworld - Mac Ship Doom" or "Focus Tower" in overworld_spot.name + or "Doom Castle" in overworld_spot.name or overworld_spot.name == "Overworld - Giant Tree"): + continue + exits = list(overworld_spot.connected_region.exits) + [overworld_spot] + checked_regions = set() + while exits: + exit_check = exits.pop() + if (exit_check.connected_region not in checked_regions and "Subregion" not in + exit_check.connected_region.name): + checked_regions.add(exit_check.connected_region) + exits.extend(exit_check.connected_region.exits) + for location in exit_check.connected_region.locations: + if location.address: + hint = [] + if self.multiworld.map_shuffle[self.player] != "dungeons": + hint.append((subregion.split("Subregion ")[-1] + (" Region" if subregion not + in single_location_regions else ""))) + if self.multiworld.map_shuffle[self.player] != "overworld" and subregion not in \ + ("Subregion Mac's Ship", "Subregion Doom Castle"): + hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu", + "Pazuzu's")) + hint = " - ".join(hint) + if location.address in hint_data[self.player]: + hint_data[self.player][location.address] += f"/{hint}" + else: + hint_data[self.player][location.address] = hint + diff --git a/worlds/ffmq/data/entrances.yaml b/worlds/ffmq/data/entrances.yaml new file mode 100644 index 000000000000..15bcd02bf623 --- /dev/null +++ b/worlds/ffmq/data/entrances.yaml @@ -0,0 +1,2425 @@ +- name: Doom Castle - Sand Floor - To Sky Door - Sand Floor + id: 0 + area: 7 + coordinates: [24, 19] + teleporter: [0, 0] +- name: Doom Castle - Sand Floor - Main Entrance - Sand Floor + id: 1 + area: 7 + coordinates: [19, 43] + teleporter: [1, 6] +- name: Doom Castle - Aero Room - Aero Room Entrance + id: 2 + area: 7 + coordinates: [27, 39] + teleporter: [1, 0] +- name: Focus Tower B1 - Main Loop - South Entrance + id: 3 + area: 8 + coordinates: [43, 60] + teleporter: [2, 6] +- name: Focus Tower B1 - Main Loop - To Focus Tower 1F - Main Hall + id: 4 + area: 8 + coordinates: [37, 41] + teleporter: [4, 0] +- name: Focus Tower B1 - Aero Corridor - To Focus Tower 1F - Sun Coin Room + id: 5 + area: 8 + coordinates: [59, 35] + teleporter: [5, 0] +- name: Focus Tower B1 - Aero Corridor - To Sand Floor - Aero Chest + id: 6 + area: 8 + coordinates: [57, 59] + teleporter: [8, 0] +- name: Focus Tower B1 - Inner Loop - To Focus Tower 1F - Sky Door + id: 7 + area: 8 + coordinates: [51, 49] + teleporter: [6, 0] +- name: Focus Tower B1 - Inner Loop - To Doom Castle Sand Floor + id: 8 + area: 8 + coordinates: [51, 45] + teleporter: [7, 0] +- name: Focus Tower 1F - Focus Tower West Entrance + id: 9 + area: 9 + coordinates: [25, 29] + teleporter: [3, 6] +- name: Focus Tower 1F - To Focus Tower 2F - From SandCoin + id: 10 + area: 9 + coordinates: [16, 4] + teleporter: [10, 0] +- name: Focus Tower 1F - To Focus Tower B1 - Main Hall + id: 11 + area: 9 + coordinates: [4, 23] + teleporter: [11, 0] +- name: Focus Tower 1F - To Focus Tower B1 - To Aero Chest + id: 12 + area: 9 + coordinates: [26, 17] + teleporter: [12, 0] +- name: Focus Tower 1F - Sky Door + id: 13 + area: 9 + coordinates: [16, 24] + teleporter: [13, 0] +- name: Focus Tower 1F - To Focus Tower 2F - From RiverCoin + id: 14 + area: 9 + coordinates: [16, 10] + teleporter: [14, 0] +- name: Focus Tower 1F - To Focus Tower B1 - From Sky Door + id: 15 + area: 9 + coordinates: [16, 29] + teleporter: [15, 0] +- name: Focus Tower 2F - Sand Coin Passage - North Entrance + id: 16 + area: 10 + coordinates: [49, 30] + teleporter: [4, 6] +- name: Focus Tower 2F - Sand Coin Passage - To Focus Tower 1F - To SandCoin + id: 17 + area: 10 + coordinates: [47, 33] + teleporter: [17, 0] +- name: Focus Tower 2F - River Coin Passage - To Focus Tower 1F - To RiverCoin + id: 18 + area: 10 + coordinates: [47, 41] + teleporter: [18, 0] +- name: Focus Tower 2F - River Coin Passage - To Focus Tower 3F - Lower Floor + id: 19 + area: 10 + coordinates: [38, 40] + teleporter: [20, 0] +- name: Focus Tower 2F - Venus Chest Room - To Focus Tower 3F - Upper Floor + id: 20 + area: 10 + coordinates: [56, 40] + teleporter: [19, 0] +- name: Focus Tower 2F - Venus Chest Room - Pillar Script + id: 21 + area: 10 + coordinates: [48, 53] + teleporter: [13, 8] +- name: Focus Tower 3F - Lower Floor - To Fireburg Entrance + id: 22 + area: 11 + coordinates: [11, 39] + teleporter: [6, 6] +- name: Focus Tower 3F - Lower Floor - To Focus Tower 2F - Jump on Pillar + id: 23 + area: 11 + coordinates: [6, 47] + teleporter: [24, 0] +- name: Focus Tower 3F - Upper Floor - To Aquaria Entrance + id: 24 + area: 11 + coordinates: [21, 38] + teleporter: [5, 6] +- name: Focus Tower 3F - Upper Floor - To Focus Tower 2F - Venus Chest Room + id: 25 + area: 11 + coordinates: [24, 47] + teleporter: [23, 0] +- name: Level Forest - Boulder Script + id: 26 + area: 14 + coordinates: [52, 15] + teleporter: [0, 8] +- name: Level Forest - Rotten Tree Script + id: 27 + area: 14 + coordinates: [47, 6] + teleporter: [2, 8] +- name: Level Forest - Exit Level Forest 1 + id: 28 + area: 14 + coordinates: [46, 25] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 2 + id: 29 + area: 14 + coordinates: [46, 26] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 3 + id: 30 + area: 14 + coordinates: [47, 25] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 4 + id: 31 + area: 14 + coordinates: [47, 26] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 5 + id: 32 + area: 14 + coordinates: [60, 14] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 6 + id: 33 + area: 14 + coordinates: [61, 14] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 7 + id: 34 + area: 14 + coordinates: [46, 4] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 8 + id: 35 + area: 14 + coordinates: [46, 3] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest 9 + id: 36 + area: 14 + coordinates: [47, 4] + teleporter: [25, 0] +- name: Level Forest - Exit Level Forest A + id: 37 + area: 14 + coordinates: [47, 3] + teleporter: [25, 0] +- name: Foresta - Exit Foresta 1 + id: 38 + area: 15 + coordinates: [10, 25] + teleporter: [31, 0] +- name: Foresta - Exit Foresta 2 + id: 39 + area: 15 + coordinates: [10, 26] + teleporter: [31, 0] +- name: Foresta - Exit Foresta 3 + id: 40 + area: 15 + coordinates: [11, 25] + teleporter: [31, 0] +- name: Foresta - Exit Foresta 4 + id: 41 + area: 15 + coordinates: [11, 26] + teleporter: [31, 0] +- name: Foresta - Old Man House - Front Door + id: 42 + area: 15 + coordinates: [25, 17] + teleporter: [32, 4] +- name: Foresta - Old Man House - Back Door + id: 43 + area: 15 + coordinates: [25, 14] + teleporter: [33, 0] +- name: Foresta - Kaeli's House + id: 44 + area: 15 + coordinates: [7, 21] + teleporter: [0, 5] +- name: Foresta - Rest House + id: 45 + area: 15 + coordinates: [23, 23] + teleporter: [1, 5] +- name: Kaeli's House - Kaeli's House Entrance + id: 46 + area: 16 + coordinates: [11, 20] + teleporter: [86, 3] +- name: Foresta Houses - Old Man's House - Old Man Front Exit + id: 47 + area: 17 + coordinates: [35, 44] + teleporter: [34, 0] +- name: Foresta Houses - Old Man's House - Old Man Back Exit + id: 48 + area: 17 + coordinates: [35, 27] + teleporter: [35, 0] +- name: Foresta - Old Man House - Barrel Tile Script # New, use the focus tower column's script + id: 483 + area: 17 + coordinates: [0x23, 0x1E] + teleporter: [0x0D, 8] +- name: Foresta Houses - Rest House - Bed Script + id: 49 + area: 17 + coordinates: [30, 6] + teleporter: [1, 8] +- name: Foresta Houses - Rest House - Rest House Exit + id: 50 + area: 17 + coordinates: [35, 20] + teleporter: [87, 3] +- name: Foresta Houses - Libra House - Libra House Script + id: 51 + area: 17 + coordinates: [8, 49] + teleporter: [67, 8] +- name: Foresta Houses - Gemini House - Gemini House Script + id: 52 + area: 17 + coordinates: [26, 55] + teleporter: [68, 8] +- name: Foresta Houses - Mobius House - Mobius House Script + id: 53 + area: 17 + coordinates: [14, 33] + teleporter: [69, 8] +- name: Sand Temple - Sand Temple Entrance + id: 54 + area: 18 + coordinates: [56, 27] + teleporter: [36, 0] +- name: Bone Dungeon 1F - Bone Dungeon Entrance + id: 55 + area: 19 + coordinates: [13, 60] + teleporter: [37, 0] +- name: Bone Dungeon 1F - To Bone Dungeon B1 + id: 56 + area: 19 + coordinates: [13, 39] + teleporter: [2, 2] +- name: Bone Dungeon B1 - Waterway - Exit Waterway + id: 57 + area: 20 + coordinates: [27, 39] + teleporter: [3, 2] +- name: Bone Dungeon B1 - Waterway - Tristam's Script + id: 58 + area: 20 + coordinates: [27, 45] + teleporter: [3, 8] +- name: Bone Dungeon B1 - Waterway - To Bone Dungeon 1F + id: 59 + area: 20 + coordinates: [54, 61] + teleporter: [88, 3] +- name: Bone Dungeon B1 - Checker Room - Exit Checker Room + id: 60 + area: 20 + coordinates: [23, 40] + teleporter: [4, 2] +- name: Bone Dungeon B1 - Checker Room - To Waterway + id: 61 + area: 20 + coordinates: [39, 49] + teleporter: [89, 3] +- name: Bone Dungeon B1 - Hidden Room - To B2 - Exploding Skull Room + id: 62 + area: 20 + coordinates: [5, 33] + teleporter: [91, 3] +- name: Bonne Dungeon B2 - Exploding Skull Room - To Hidden Passage + id: 63 + area: 21 + coordinates: [19, 13] + teleporter: [5, 2] +- name: Bonne Dungeon B2 - Exploding Skull Room - To Two Skulls Room + id: 64 + area: 21 + coordinates: [29, 15] + teleporter: [6, 2] +- name: Bonne Dungeon B2 - Exploding Skull Room - To Checker Room + id: 65 + area: 21 + coordinates: [8, 25] + teleporter: [90, 3] +- name: Bonne Dungeon B2 - Box Room - To B2 - Two Skulls Room + id: 66 + area: 21 + coordinates: [59, 12] + teleporter: [93, 3] +- name: Bonne Dungeon B2 - Quake Room - To B2 - Two Skulls Room + id: 67 + area: 21 + coordinates: [59, 28] + teleporter: [94, 3] +- name: Bonne Dungeon B2 - Two Skulls Room - To Box Room + id: 68 + area: 21 + coordinates: [53, 7] + teleporter: [7, 2] +- name: Bonne Dungeon B2 - Two Skulls Room - To Quake Room + id: 69 + area: 21 + coordinates: [41, 3] + teleporter: [8, 2] +- name: Bonne Dungeon B2 - Two Skulls Room - To Boss Room + id: 70 + area: 21 + coordinates: [47, 57] + teleporter: [9, 2] +- name: Bonne Dungeon B2 - Two Skulls Room - To B2 - Exploding Skull Room + id: 71 + area: 21 + coordinates: [54, 23] + teleporter: [92, 3] +- name: Bone Dungeon B2 - Boss Room - Flamerus Rex Script + id: 72 + area: 22 + coordinates: [29, 19] + teleporter: [4, 8] +- name: Bone Dungeon B2 - Boss Room - Tristam Leave Script + id: 73 + area: 22 + coordinates: [29, 23] + teleporter: [75, 8] +- name: Bone Dungeon B2 - Boss Room - To B2 - Two Skulls Room + id: 74 + area: 22 + coordinates: [30, 27] + teleporter: [95, 3] +- name: Libra Temple - Entrance + id: 75 + area: 23 + coordinates: [10, 15] + teleporter: [13, 6] +- name: Libra Temple - Libra Tile Script + id: 76 + area: 23 + coordinates: [9, 8] + teleporter: [59, 8] +- name: Aquaria Winter - Winter Entrance 1 + id: 77 + area: 24 + coordinates: [25, 25] + teleporter: [8, 6] +- name: Aquaria Winter - Winter Entrance 2 + id: 78 + area: 24 + coordinates: [25, 26] + teleporter: [8, 6] +- name: Aquaria Winter - Winter Entrance 3 + id: 79 + area: 24 + coordinates: [26, 25] + teleporter: [8, 6] +- name: Aquaria Winter - Winter Entrance 4 + id: 80 + area: 24 + coordinates: [26, 26] + teleporter: [8, 6] +- name: Aquaria Winter - Winter Phoebe's House Entrance Script #Modified to not be a script + id: 81 + area: 24 + coordinates: [8, 19] + teleporter: [10, 5] # original value [5, 8] +- name: Aquaria Winter - Winter Vendor House Entrance + id: 82 + area: 24 + coordinates: [8, 5] + teleporter: [44, 4] +- name: Aquaria Winter - Winter INN Entrance + id: 83 + area: 24 + coordinates: [26, 17] + teleporter: [11, 5] +- name: Aquaria Summer - Summer Entrance 1 + id: 84 + area: 25 + coordinates: [57, 25] + teleporter: [8, 6] +- name: Aquaria Summer - Summer Entrance 2 + id: 85 + area: 25 + coordinates: [57, 26] + teleporter: [8, 6] +- name: Aquaria Summer - Summer Entrance 3 + id: 86 + area: 25 + coordinates: [58, 25] + teleporter: [8, 6] +- name: Aquaria Summer - Summer Entrance 4 + id: 87 + area: 25 + coordinates: [58, 26] + teleporter: [8, 6] +- name: Aquaria Summer - Summer Phoebe's House Entrance + id: 88 + area: 25 + coordinates: [40, 19] + teleporter: [10, 5] +- name: Aquaria Summer - Spencer's Place Entrance Top + id: 89 + area: 25 + coordinates: [40, 16] + teleporter: [42, 0] +- name: Aquaria Summer - Spencer's Place Entrance Side + id: 90 + area: 25 + coordinates: [41, 18] + teleporter: [43, 0] +- name: Aquaria Summer - Summer Vendor House Entrance + id: 91 + area: 25 + coordinates: [40, 5] + teleporter: [44, 4] +- name: Aquaria Summer - Summer INN Entrance + id: 92 + area: 25 + coordinates: [58, 17] + teleporter: [11, 5] +- name: Phoebe's House - Entrance # Change to a script, same as vendor house + id: 93 + area: 26 + coordinates: [29, 14] + teleporter: [5, 8] # Original Value [11,3] +- name: Aquaria Vendor House - Vendor House Entrance's Script + id: 94 + area: 27 + coordinates: [7, 10] + teleporter: [40, 8] +- name: Aquaria Vendor House - Vendor House Stairs + id: 95 + area: 27 + coordinates: [1, 4] + teleporter: [47, 0] +- name: Aquaria Gemini Room - Gemini Script + id: 96 + area: 27 + coordinates: [2, 40] + teleporter: [72, 8] +- name: Aquaria Gemini Room - Gemini Room Stairs + id: 97 + area: 27 + coordinates: [4, 39] + teleporter: [48, 0] +- name: Aquaria INN - Aquaria INN entrance # Change to a script, same as vendor house + id: 98 + area: 27 + coordinates: [51, 46] + teleporter: [75, 8] # Original value [48,3] +- name: Wintry Cave 1F - Main Entrance + id: 99 + area: 28 + coordinates: [50, 58] + teleporter: [49, 0] +- name: Wintry Cave 1F - To 3F Top + id: 100 + area: 28 + coordinates: [40, 25] + teleporter: [14, 2] +- name: Wintry Cave 1F - To 2F + id: 101 + area: 28 + coordinates: [10, 43] + teleporter: [15, 2] +- name: Wintry Cave 1F - Phoebe's Script + id: 102 + area: 28 + coordinates: [44, 37] + teleporter: [6, 8] +- name: Wintry Cave 2F - To 3F Bottom + id: 103 + area: 29 + coordinates: [58, 5] + teleporter: [50, 0] +- name: Wintry Cave 2F - To 1F + id: 104 + area: 29 + coordinates: [38, 18] + teleporter: [97, 3] +- name: Wintry Cave 3F Top - Exit from 3F Top + id: 105 + area: 30 + coordinates: [24, 6] + teleporter: [96, 3] +- name: Wintry Cave 3F Bottom - Exit to 2F + id: 106 + area: 31 + coordinates: [4, 29] + teleporter: [51, 0] +- name: Life Temple - Entrance + id: 107 + area: 32 + coordinates: [9, 60] + teleporter: [14, 6] +- name: Life Temple - Libra Tile Script + id: 108 + area: 32 + coordinates: [3, 55] + teleporter: [60, 8] +- name: Life Temple - Mysterious Man Script + id: 109 + area: 32 + coordinates: [9, 44] + teleporter: [78, 8] +- name: Fall Basin - Back Exit Script + id: 110 + area: 33 + coordinates: [17, 5] + teleporter: [9, 0] # Remove script [42, 8] for overworld teleport (but not main exit) +- name: Fall Basin - Main Exit + id: 111 + area: 33 + coordinates: [15, 26] + teleporter: [53, 0] +- name: Fall Basin - Phoebe's Script + id: 112 + area: 33 + coordinates: [17, 6] + teleporter: [9, 8] +- name: Ice Pyramid B1 Taunt Room - To Climbing Wall Room + id: 113 + area: 34 + coordinates: [43, 6] + teleporter: [55, 0] +- name: Ice Pyramid 1F Maze - Main Entrance 1 + id: 114 + area: 35 + coordinates: [18, 36] + teleporter: [56, 0] +- name: Ice Pyramid 1F Maze - Main Entrance 2 + id: 115 + area: 35 + coordinates: [19, 36] + teleporter: [56, 0] +- name: Ice Pyramid 1F Maze - West Stairs To 2F South Tiled Room + id: 116 + area: 35 + coordinates: [3, 27] + teleporter: [57, 0] +- name: Ice Pyramid 1F Maze - West Center Stairs to 2F West Room + id: 117 + area: 35 + coordinates: [11, 15] + teleporter: [58, 0] +- name: Ice Pyramid 1F Maze - East Center Stairs to 2F Center Room + id: 118 + area: 35 + coordinates: [25, 16] + teleporter: [59, 0] +- name: Ice Pyramid 1F Maze - Upper Stairs to 2F Small North Room + id: 119 + area: 35 + coordinates: [31, 1] + teleporter: [60, 0] +- name: Ice Pyramid 1F Maze - East Stairs to 2F North Corridor + id: 120 + area: 35 + coordinates: [34, 9] + teleporter: [61, 0] +- name: Ice Pyramid 1F Maze - Statue's Script + id: 121 + area: 35 + coordinates: [21, 32] + teleporter: [77, 8] +- name: Ice Pyramid 2F South Tiled Room - To 1F + id: 122 + area: 36 + coordinates: [4, 26] + teleporter: [62, 0] +- name: Ice Pyramid 2F South Tiled Room - To 3F Two Boxes Room + id: 123 + area: 36 + coordinates: [22, 17] + teleporter: [67, 0] +- name: Ice Pyramid 2F West Room - To 1F + id: 124 + area: 36 + coordinates: [9, 10] + teleporter: [63, 0] +- name: Ice Pyramid 2F Center Room - To 1F + id: 125 + area: 36 + coordinates: [22, 14] + teleporter: [64, 0] +- name: Ice Pyramid 2F Small North Room - To 1F + id: 126 + area: 36 + coordinates: [26, 4] + teleporter: [65, 0] +- name: Ice Pyramid 2F North Corridor - To 1F + id: 127 + area: 36 + coordinates: [32, 8] + teleporter: [66, 0] +- name: Ice Pyramid 2F North Corridor - To 3F Main Loop + id: 128 + area: 36 + coordinates: [12, 7] + teleporter: [68, 0] +- name: Ice Pyramid 3F Two Boxes Room - To 2F South Tiled Room + id: 129 + area: 37 + coordinates: [24, 54] + teleporter: [69, 0] +- name: Ice Pyramid 3F Main Loop - To 2F Corridor + id: 130 + area: 37 + coordinates: [16, 45] + teleporter: [70, 0] +- name: Ice Pyramid 3F Main Loop - To 4F + id: 131 + area: 37 + coordinates: [19, 43] + teleporter: [71, 0] +- name: Ice Pyramid 4F Treasure Room - To 3F Main Loop + id: 132 + area: 38 + coordinates: [52, 5] + teleporter: [72, 0] +- name: Ice Pyramid 4F Treasure Room - To 5F Leap of Faith Room + id: 133 + area: 38 + coordinates: [62, 19] + teleporter: [73, 0] +- name: Ice Pyramid 5F Leap of Faith Room - To 4F Treasure Room + id: 134 + area: 39 + coordinates: [54, 63] + teleporter: [74, 0] +- name: Ice Pyramid 5F Leap of Faith Room - Bombed Ice Plate + id: 135 + area: 39 + coordinates: [47, 54] + teleporter: [77, 8] +- name: Ice Pyramid 5F Stairs to Ice Golem - To Ice Golem Room + id: 136 + area: 39 + coordinates: [39, 43] + teleporter: [75, 0] +- name: Ice Pyramid 5F Stairs to Ice Golem - To Climbing Wall Room + id: 137 + area: 39 + coordinates: [39, 60] + teleporter: [76, 0] +- name: Ice Pyramid - Duplicate Ice Golem Room # not used? + id: 138 + area: 40 + coordinates: [44, 43] + teleporter: [77, 0] +- name: Ice Pyramid Climbing Wall Room - To Taunt Room + id: 139 + area: 41 + coordinates: [4, 59] + teleporter: [78, 0] +- name: Ice Pyramid Climbing Wall Room - To 5F Stairs + id: 140 + area: 41 + coordinates: [4, 45] + teleporter: [79, 0] +- name: Ice Pyramid Ice Golem Room - To 5F Stairs + id: 141 + area: 42 + coordinates: [44, 43] + teleporter: [80, 0] +- name: Ice Pyramid Ice Golem Room - Ice Golem Script + id: 142 + area: 42 + coordinates: [53, 32] + teleporter: [10, 8] +- name: Spencer Waterfall - To Spencer Cave + id: 143 + area: 43 + coordinates: [48, 57] + teleporter: [81, 0] +- name: Spencer Waterfall - Upper Exit to Aquaria 1 + id: 144 + area: 43 + coordinates: [40, 5] + teleporter: [82, 0] +- name: Spencer Waterfall - Upper Exit to Aquaria 2 + id: 145 + area: 43 + coordinates: [40, 6] + teleporter: [82, 0] +- name: Spencer Waterfall - Upper Exit to Aquaria 3 + id: 146 + area: 43 + coordinates: [41, 5] + teleporter: [82, 0] +- name: Spencer Waterfall - Upper Exit to Aquaria 4 + id: 147 + area: 43 + coordinates: [41, 6] + teleporter: [82, 0] +- name: Spencer Waterfall - Right Exit to Aquaria 1 + id: 148 + area: 43 + coordinates: [46, 8] + teleporter: [83, 0] +- name: Spencer Waterfall - Right Exit to Aquaria 2 + id: 149 + area: 43 + coordinates: [47, 8] + teleporter: [83, 0] +- name: Spencer Cave Normal Main - To Waterfall + id: 150 + area: 44 + coordinates: [14, 39] + teleporter: [85, 0] +- name: Spencer Cave Normal From Overworld - Exit to Overworld + id: 151 + area: 44 + coordinates: [15, 57] + teleporter: [7, 6] +- name: Spencer Cave Unplug - Exit to Overworld + id: 152 + area: 45 + coordinates: [40, 29] + teleporter: [7, 6] +- name: Spencer Cave Unplug - Libra Teleporter Start Script + id: 153 + area: 45 + coordinates: [28, 21] + teleporter: [33, 8] +- name: Spencer Cave Unplug - Libra Teleporter End Script + id: 154 + area: 45 + coordinates: [46, 4] + teleporter: [34, 8] +- name: Spencer Cave Unplug - Mobius Teleporter Chest Script + id: 155 + area: 45 + coordinates: [21, 9] + teleporter: [35, 8] +- name: Spencer Cave Unplug - Mobius Teleporter Start Script + id: 156 + area: 45 + coordinates: [29, 28] + teleporter: [36, 8] +- name: Wintry Temple Outer Room - Main Entrance + id: 157 + area: 46 + coordinates: [8, 31] + teleporter: [15, 6] +- name: Wintry Temple Inner Room - Gemini Tile to Sealed temple + id: 158 + area: 46 + coordinates: [9, 24] + teleporter: [62, 8] +- name: Fireburg - To Overworld + id: 159 + area: 47 + coordinates: [4, 13] + teleporter: [9, 6] +- name: Fireburg - To Overworld + id: 160 + area: 47 + coordinates: [5, 13] + teleporter: [9, 6] +- name: Fireburg - To Overworld + id: 161 + area: 47 + coordinates: [28, 15] + teleporter: [9, 6] +- name: Fireburg - To Overworld + id: 162 + area: 47 + coordinates: [27, 15] + teleporter: [9, 6] +- name: Fireburg - Vendor House + id: 163 + area: 47 + coordinates: [10, 24] + teleporter: [91, 0] +- name: Fireburg - Reuben House + id: 164 + area: 47 + coordinates: [14, 6] + teleporter: [16, 2] +- name: Fireburg - Hotel + id: 165 + area: 47 + coordinates: [20, 8] + teleporter: [17, 2] +- name: Fireburg - GrenadeMan House Script + id: 166 + area: 47 + coordinates: [12, 18] + teleporter: [11, 8] +- name: Reuben House - Main Entrance + id: 167 + area: 48 + coordinates: [33, 46] + teleporter: [98, 3] +- name: GrenadeMan House - Entrance Script + id: 168 + area: 49 + coordinates: [55, 60] + teleporter: [9, 8] +- name: GrenadeMan House - To Mobius Crest Room + id: 169 + area: 49 + coordinates: [57, 52] + teleporter: [93, 0] +- name: GrenadeMan Mobius Room - Stairs to House + id: 170 + area: 49 + coordinates: [39, 26] + teleporter: [94, 0] +- name: GrenadeMan Mobius Room - Mobius Teleporter Script + id: 171 + area: 49 + coordinates: [39, 23] + teleporter: [54, 8] +- name: Fireburg Vendor House - Entrance Script # No use to be a script + id: 172 + area: 49 + coordinates: [7, 10] + teleporter: [95, 0] # Original value [39, 8] +- name: Fireburg Vendor House - Stairs to Gemini Room + id: 173 + area: 49 + coordinates: [1, 4] + teleporter: [96, 0] +- name: Fireburg Gemini Room - Stairs to Vendor House + id: 174 + area: 49 + coordinates: [4, 39] + teleporter: [97, 0] +- name: Fireburg Gemini Room - Gemini Teleporter Script + id: 175 + area: 49 + coordinates: [2, 40] + teleporter: [45, 8] +- name: Fireburg Hotel Lobby - Stairs to beds + id: 176 + area: 49 + coordinates: [4, 50] + teleporter: [213, 0] +- name: Fireburg Hotel Lobby - Entrance + id: 177 + area: 49 + coordinates: [17, 56] + teleporter: [99, 3] +- name: Fireburg Hotel Beds - Stairs to Hotel Lobby + id: 178 + area: 49 + coordinates: [45, 59] + teleporter: [214, 0] +- name: Mine Exterior - Main Entrance + id: 179 + area: 50 + coordinates: [5, 28] + teleporter: [98, 0] +- name: Mine Exterior - To Cliff + id: 180 + area: 50 + coordinates: [58, 29] + teleporter: [99, 0] +- name: Mine Exterior - To Parallel Room + id: 181 + area: 50 + coordinates: [8, 7] + teleporter: [20, 2] +- name: Mine Exterior - To Crescent Room + id: 182 + area: 50 + coordinates: [26, 15] + teleporter: [21, 2] +- name: Mine Exterior - To Climbing Room + id: 183 + area: 50 + coordinates: [21, 35] + teleporter: [22, 2] +- name: Mine Exterior - Jinn Fight Script + id: 184 + area: 50 + coordinates: [58, 31] + teleporter: [74, 8] +- name: Mine Parallel Room - To Mine Exterior + id: 185 + area: 51 + coordinates: [7, 60] + teleporter: [100, 3] +- name: Mine Crescent Room - To Mine Exterior + id: 186 + area: 51 + coordinates: [22, 61] + teleporter: [101, 3] +- name: Mine Climbing Room - To Mine Exterior + id: 187 + area: 51 + coordinates: [56, 21] + teleporter: [102, 3] +- name: Mine Cliff - Entrance + id: 188 + area: 52 + coordinates: [9, 5] + teleporter: [100, 0] +- name: Mine Cliff - Reuben Grenade Script + id: 189 + area: 52 + coordinates: [15, 7] + teleporter: [12, 8] +- name: Sealed Temple - To Overworld + id: 190 + area: 53 + coordinates: [58, 43] + teleporter: [16, 6] +- name: Sealed Temple - Gemini Tile Script + id: 191 + area: 53 + coordinates: [56, 38] + teleporter: [63, 8] +- name: Volcano Base - Main Entrance 1 + id: 192 + area: 54 + coordinates: [23, 25] + teleporter: [103, 0] +- name: Volcano Base - Main Entrance 2 + id: 193 + area: 54 + coordinates: [23, 26] + teleporter: [103, 0] +- name: Volcano Base - Main Entrance 3 + id: 194 + area: 54 + coordinates: [24, 25] + teleporter: [103, 0] +- name: Volcano Base - Main Entrance 4 + id: 195 + area: 54 + coordinates: [24, 26] + teleporter: [103, 0] +- name: Volcano Base - Left Stairs Script + id: 196 + area: 54 + coordinates: [20, 5] + teleporter: [31, 8] +- name: Volcano Base - Right Stairs Script + id: 197 + area: 54 + coordinates: [32, 5] + teleporter: [30, 8] +- name: Volcano Top Right - Top Exit + id: 198 + area: 55 + coordinates: [44, 8] + teleporter: [9, 0] # Original value [103, 0] changed to volcano escape so floor shuffling doesn't pick it up +- name: Volcano Top Left - To Right-Left Path Script + id: 199 + area: 55 + coordinates: [40, 24] + teleporter: [26, 8] +- name: Volcano Top Right - To Left-Right Path Script + id: 200 + area: 55 + coordinates: [52, 24] + teleporter: [79, 8] # Original Value [26, 8] +- name: Volcano Right Path - To Volcano Base Script + id: 201 + area: 56 + coordinates: [48, 42] + teleporter: [15, 8] # Original Value [27, 8] +- name: Volcano Left Path - To Volcano Cross Left-Right + id: 202 + area: 56 + coordinates: [40, 31] + teleporter: [25, 2] +- name: Volcano Left Path - To Volcano Cross Right-Left + id: 203 + area: 56 + coordinates: [52, 29] + teleporter: [26, 2] +- name: Volcano Left Path - To Volcano Base Script + id: 204 + area: 56 + coordinates: [36, 42] + teleporter: [27, 8] +- name: Volcano Cross Left-Right - To Volcano Left Path + id: 205 + area: 56 + coordinates: [10, 42] + teleporter: [103, 3] +- name: Volcano Cross Left-Right - To Volcano Top Right Script + id: 206 + area: 56 + coordinates: [16, 24] + teleporter: [29, 8] +- name: Volcano Cross Right-Left - To Volcano Top Left Script + id: 207 + area: 56 + coordinates: [8, 22] + teleporter: [28, 8] +- name: Volcano Cross Right-Left - To Volcano Left Path + id: 208 + area: 56 + coordinates: [16, 42] + teleporter: [104, 3] +- name: Lava Dome Inner Ring Main Loop - Main Entrance 1 + id: 209 + area: 57 + coordinates: [32, 5] + teleporter: [104, 0] +- name: Lava Dome Inner Ring Main Loop - Main Entrance 2 + id: 210 + area: 57 + coordinates: [33, 5] + teleporter: [104, 0] +- name: Lava Dome Inner Ring Main Loop - To Three Steps Room + id: 211 + area: 57 + coordinates: [14, 5] + teleporter: [105, 0] +- name: Lava Dome Inner Ring Main Loop - To Life Chest Room Lower + id: 212 + area: 57 + coordinates: [40, 17] + teleporter: [106, 0] +- name: Lava Dome Inner Ring Main Loop - To Big Jump Room Left + id: 213 + area: 57 + coordinates: [8, 11] + teleporter: [108, 0] +- name: Lava Dome Inner Ring Main Loop - To Split Corridor Room + id: 214 + area: 57 + coordinates: [11, 19] + teleporter: [111, 0] +- name: Lava Dome Inner Ring Center Ledge - To Life Chest Room Higher + id: 215 + area: 57 + coordinates: [32, 11] + teleporter: [107, 0] +- name: Lava Dome Inner Ring Plate Ledge - To Plate Corridor + id: 216 + area: 57 + coordinates: [12, 23] + teleporter: [109, 0] +- name: Lava Dome Inner Ring Plate Ledge - Plate Script + id: 217 + area: 57 + coordinates: [5, 23] + teleporter: [47, 8] +- name: Lava Dome Inner Ring Upper Ledges - To Pointless Room + id: 218 + area: 57 + coordinates: [0, 9] + teleporter: [110, 0] +- name: Lava Dome Inner Ring Upper Ledges - To Lower Moon Helm Room + id: 219 + area: 57 + coordinates: [0, 15] + teleporter: [112, 0] +- name: Lava Dome Inner Ring Upper Ledges - To Up-Down Corridor + id: 220 + area: 57 + coordinates: [54, 5] + teleporter: [113, 0] +- name: Lava Dome Inner Ring Big Door Ledge - To Jumping Maze II + id: 221 + area: 57 + coordinates: [54, 21] + teleporter: [114, 0] +- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 1 + id: 222 + area: 57 + coordinates: [62, 20] + teleporter: [29, 2] +- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 2 + id: 223 + area: 57 + coordinates: [63, 20] + teleporter: [29, 2] +- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 3 + id: 224 + area: 57 + coordinates: [62, 21] + teleporter: [29, 2] +- name: Lava Dome Inner Ring Big Door Ledge - Hydra Gate 4 + id: 225 + area: 57 + coordinates: [63, 21] + teleporter: [29, 2] +- name: Lava Dome Inner Ring Tiny Bottom Ledge - To Four Boxes Corridor + id: 226 + area: 57 + coordinates: [50, 25] + teleporter: [115, 0] +- name: Lava Dome Jump Maze II - Lower Right Entrance + id: 227 + area: 58 + coordinates: [55, 28] + teleporter: [116, 0] +- name: Lava Dome Jump Maze II - Upper Entrance + id: 228 + area: 58 + coordinates: [35, 3] + teleporter: [119, 0] +- name: Lava Dome Jump Maze II - Lower Left Entrance + id: 229 + area: 58 + coordinates: [34, 27] + teleporter: [120, 0] +- name: Lava Dome Up-Down Corridor - Upper Entrance + id: 230 + area: 58 + coordinates: [29, 8] + teleporter: [117, 0] +- name: Lava Dome Up-Down Corridor - Lower Entrance + id: 231 + area: 58 + coordinates: [28, 25] + teleporter: [118, 0] +- name: Lava Dome Jump Maze I - South Entrance + id: 232 + area: 59 + coordinates: [20, 27] + teleporter: [121, 0] +- name: Lava Dome Jump Maze I - North Entrance + id: 233 + area: 59 + coordinates: [7, 3] + teleporter: [122, 0] +- name: Lava Dome Pointless Room - Entrance + id: 234 + area: 60 + coordinates: [2, 7] + teleporter: [123, 0] +- name: Lava Dome Lower Moon Helm Room - Left Entrance + id: 235 + area: 60 + coordinates: [2, 19] + teleporter: [124, 0] +- name: Lava Dome Lower Moon Helm Room - Right Entrance + id: 236 + area: 60 + coordinates: [11, 21] + teleporter: [125, 0] +- name: Lava Dome Moon Helm Room - Entrance + id: 237 + area: 60 + coordinates: [15, 23] + teleporter: [126, 0] +- name: Lava Dome Three Jumps Room - To Main Loop + id: 238 + area: 61 + coordinates: [58, 15] + teleporter: [127, 0] +- name: Lava Dome Life Chest Room - Lower South Entrance + id: 239 + area: 61 + coordinates: [38, 27] + teleporter: [128, 0] +- name: Lava Dome Life Chest Room - Upper South Entrance + id: 240 + area: 61 + coordinates: [28, 23] + teleporter: [129, 0] +- name: Lava Dome Big Jump Room - Left Entrance + id: 241 + area: 62 + coordinates: [42, 51] + teleporter: [133, 0] +- name: Lava Dome Big Jump Room - North Entrance + id: 242 + area: 62 + coordinates: [30, 29] + teleporter: [131, 0] +- name: Lava Dome Big Jump Room - Lower Right Stairs + id: 243 + area: 62 + coordinates: [61, 59] + teleporter: [132, 0] +- name: Lava Dome Split Corridor - Upper Stairs + id: 244 + area: 62 + coordinates: [30, 43] + teleporter: [130, 0] +- name: Lava Dome Split Corridor - Lower Stairs + id: 245 + area: 62 + coordinates: [36, 61] + teleporter: [134, 0] +- name: Lava Dome Plate Corridor - Right Entrance + id: 246 + area: 63 + coordinates: [19, 29] + teleporter: [135, 0] +- name: Lava Dome Plate Corridor - Left Entrance + id: 247 + area: 63 + coordinates: [60, 21] + teleporter: [137, 0] +- name: Lava Dome Four Boxes Stairs - Upper Entrance + id: 248 + area: 63 + coordinates: [22, 3] + teleporter: [136, 0] +- name: Lava Dome Four Boxes Stairs - Lower Entrance + id: 249 + area: 63 + coordinates: [22, 17] + teleporter: [16, 0] +- name: Lava Dome Hydra Room - South Entrance + id: 250 + area: 64 + coordinates: [14, 59] + teleporter: [105, 3] +- name: Lava Dome Hydra Room - North Exit + id: 251 + area: 64 + coordinates: [25, 31] + teleporter: [138, 0] +- name: Lava Dome Hydra Room - Hydra Script + id: 252 + area: 64 + coordinates: [14, 36] + teleporter: [14, 8] +- name: Lava Dome Escape Corridor - South Entrance + id: 253 + area: 65 + coordinates: [22, 17] + teleporter: [139, 0] +- name: Lava Dome Escape Corridor - North Entrance + id: 254 + area: 65 + coordinates: [22, 3] + teleporter: [9, 0] +- name: Rope Bridge - West Entrance 1 + id: 255 + area: 66 + coordinates: [3, 10] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 2 + id: 256 + area: 66 + coordinates: [3, 11] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 3 + id: 257 + area: 66 + coordinates: [3, 12] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 4 + id: 258 + area: 66 + coordinates: [3, 13] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 5 + id: 259 + area: 66 + coordinates: [4, 10] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 6 + id: 260 + area: 66 + coordinates: [4, 11] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 7 + id: 261 + area: 66 + coordinates: [4, 12] + teleporter: [140, 0] +- name: Rope Bridge - West Entrance 8 + id: 262 + area: 66 + coordinates: [4, 13] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 1 + id: 263 + area: 66 + coordinates: [59, 10] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 2 + id: 264 + area: 66 + coordinates: [59, 11] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 3 + id: 265 + area: 66 + coordinates: [59, 12] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 4 + id: 266 + area: 66 + coordinates: [59, 13] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 5 + id: 267 + area: 66 + coordinates: [60, 10] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 6 + id: 268 + area: 66 + coordinates: [60, 11] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 7 + id: 269 + area: 66 + coordinates: [60, 12] + teleporter: [140, 0] +- name: Rope Bridge - East Entrance 8 + id: 270 + area: 66 + coordinates: [60, 13] + teleporter: [140, 0] +- name: Rope Bridge - Reuben Fall Script + id: 271 + area: 66 + coordinates: [13, 12] + teleporter: [15, 8] +- name: Alive Forest - West Entrance 1 + id: 272 + area: 67 + coordinates: [8, 13] + teleporter: [142, 0] +- name: Alive Forest - West Entrance 2 + id: 273 + area: 67 + coordinates: [9, 13] + teleporter: [142, 0] +- name: Alive Forest - Giant Tree Entrance + id: 274 + area: 67 + coordinates: [42, 42] + teleporter: [143, 0] +- name: Alive Forest - Libra Teleporter Script + id: 275 + area: 67 + coordinates: [8, 52] + teleporter: [64, 8] +- name: Alive Forest - Gemini Teleporter Script + id: 276 + area: 67 + coordinates: [57, 49] + teleporter: [65, 8] +- name: Alive Forest - Mobius Teleporter Script + id: 277 + area: 67 + coordinates: [24, 10] + teleporter: [66, 8] +- name: Giant Tree 1F - Entrance Script 1 + id: 278 + area: 68 + coordinates: [18, 31] + teleporter: [56, 1] # The script is restored if no map shuffling [49, 8] +- name: Giant Tree 1F - Entrance Script 2 + id: 279 + area: 68 + coordinates: [19, 31] + teleporter: [56, 1] # Same [49, 8] +- name: Giant Tree 1F - North Entrance To 2F + id: 280 + area: 68 + coordinates: [16, 1] + teleporter: [144, 0] +- name: Giant Tree 2F Main Lobby - North Entrance to 1F + id: 281 + area: 69 + coordinates: [44, 33] + teleporter: [145, 0] +- name: Giant Tree 2F Main Lobby - Central Entrance to 3F + id: 282 + area: 69 + coordinates: [42, 47] + teleporter: [146, 0] +- name: Giant Tree 2F Main Lobby - West Entrance to Mushroom Room + id: 283 + area: 69 + coordinates: [58, 49] + teleporter: [149, 0] +- name: Giant Tree 2F West Ledge - To 3F Northwest Ledge + id: 284 + area: 69 + coordinates: [34, 37] + teleporter: [147, 0] +- name: Giant Tree 2F Fall From Vine Script + id: 482 + area: 69 + coordinates: [0x2E, 0x33] + teleporter: [76, 8] +- name: Giant Tree Meteor Chest Room - To 2F Mushroom Room + id: 285 + area: 69 + coordinates: [58, 44] + teleporter: [148, 0] +- name: Giant Tree 2F Mushroom Room - Entrance + id: 286 + area: 70 + coordinates: [55, 18] + teleporter: [150, 0] +- name: Giant Tree 2F Mushroom Room - North Face to Meteor + id: 287 + area: 70 + coordinates: [56, 7] + teleporter: [151, 0] +- name: Giant Tree 3F Central Room - Central Entrance to 2F + id: 288 + area: 71 + coordinates: [46, 53] + teleporter: [152, 0] +- name: Giant Tree 3F Central Room - East Entrance to Worm Room + id: 289 + area: 71 + coordinates: [58, 39] + teleporter: [153, 0] +- name: Giant Tree 3F Lower Corridor - Entrance from Worm Room + id: 290 + area: 71 + coordinates: [45, 39] + teleporter: [154, 0] +- name: Giant Tree 3F West Platform - Lower Entrance + id: 291 + area: 71 + coordinates: [33, 43] + teleporter: [155, 0] +- name: Giant Tree 3F West Platform - Top Entrance + id: 292 + area: 71 + coordinates: [52, 25] + teleporter: [156, 0] +- name: Giant Tree Worm Room - East Entrance + id: 293 + area: 72 + coordinates: [20, 58] + teleporter: [157, 0] +- name: Giant Tree Worm Room - West Entrance + id: 294 + area: 72 + coordinates: [6, 56] + teleporter: [158, 0] +- name: Giant Tree 4F Lower Floor - Entrance + id: 295 + area: 73 + coordinates: [20, 7] + teleporter: [159, 0] +- name: Giant Tree 4F Lower Floor - Lower West Mouth + id: 296 + area: 73 + coordinates: [8, 23] + teleporter: [160, 0] +- name: Giant Tree 4F Lower Floor - Lower Central Mouth + id: 297 + area: 73 + coordinates: [14, 25] + teleporter: [161, 0] +- name: Giant Tree 4F Lower Floor - Lower East Mouth + id: 298 + area: 73 + coordinates: [20, 25] + teleporter: [162, 0] +- name: Giant Tree 4F Upper Floor - Upper West Mouth + id: 299 + area: 73 + coordinates: [8, 19] + teleporter: [163, 0] +- name: Giant Tree 4F Upper Floor - Upper Central Mouth + id: 300 + area: 73 + coordinates: [12, 17] + teleporter: [164, 0] +- name: Giant Tree 4F Slime Room - Exit + id: 301 + area: 74 + coordinates: [47, 10] + teleporter: [165, 0] +- name: Giant Tree 4F Slime Room - West Entrance + id: 302 + area: 74 + coordinates: [45, 24] + teleporter: [166, 0] +- name: Giant Tree 4F Slime Room - Central Entrance + id: 303 + area: 74 + coordinates: [50, 24] + teleporter: [167, 0] +- name: Giant Tree 4F Slime Room - East Entrance + id: 304 + area: 74 + coordinates: [57, 28] + teleporter: [168, 0] +- name: Giant Tree 5F - Entrance + id: 305 + area: 75 + coordinates: [14, 51] + teleporter: [169, 0] +- name: Giant Tree 5F - Giant Tree Face # Unused + id: 306 + area: 75 + coordinates: [14, 37] + teleporter: [170, 0] +- name: Kaidge Temple - Entrance + id: 307 + area: 77 + coordinates: [44, 63] + teleporter: [18, 6] +- name: Kaidge Temple - Mobius Teleporter Script + id: 308 + area: 77 + coordinates: [35, 57] + teleporter: [71, 8] +- name: Windhole Temple - Entrance + id: 309 + area: 78 + coordinates: [10, 29] + teleporter: [173, 0] +- name: Mount Gale - Entrance 1 + id: 310 + area: 79 + coordinates: [1, 45] + teleporter: [174, 0] +- name: Mount Gale - Entrance 2 + id: 311 + area: 79 + coordinates: [2, 45] + teleporter: [174, 0] +- name: Windia - Main Entrance 1 + id: 312 + area: 80 + coordinates: [12, 40] + teleporter: [10, 6] +- name: Windia - Main Entrance 2 + id: 313 + area: 80 + coordinates: [13, 40] + teleporter: [10, 6] +- name: Windia - Main Entrance 3 + id: 314 + area: 80 + coordinates: [14, 40] + teleporter: [10, 6] +- name: Windia - Main Entrance 4 + id: 315 + area: 80 + coordinates: [15, 40] + teleporter: [10, 6] +- name: Windia - Main Entrance 5 + id: 316 + area: 80 + coordinates: [12, 41] + teleporter: [10, 6] +- name: Windia - Main Entrance 6 + id: 317 + area: 80 + coordinates: [13, 41] + teleporter: [10, 6] +- name: Windia - Main Entrance 7 + id: 318 + area: 80 + coordinates: [14, 41] + teleporter: [10, 6] +- name: Windia - Main Entrance 8 + id: 319 + area: 80 + coordinates: [15, 41] + teleporter: [10, 6] +- name: Windia - Otto's House + id: 320 + area: 80 + coordinates: [21, 39] + teleporter: [30, 5] +- name: Windia - INN's Script # Change to teleporter + id: 321 + area: 80 + coordinates: [18, 34] + teleporter: [31, 2] # Original value [79, 8] +- name: Windia - Vendor House + id: 322 + area: 80 + coordinates: [8, 36] + teleporter: [32, 5] +- name: Windia - Kid House + id: 323 + area: 80 + coordinates: [7, 23] + teleporter: [176, 4] +- name: Windia - Old People House + id: 324 + area: 80 + coordinates: [19, 21] + teleporter: [177, 4] +- name: Windia - Rainbow Bridge Script + id: 325 + area: 80 + coordinates: [21, 9] + teleporter: [10, 6] # Change to entrance, usually a script [41, 8] +- name: Otto's House - Attic Stairs + id: 326 + area: 81 + coordinates: [2, 19] + teleporter: [33, 2] +- name: Otto's House - Entrance + id: 327 + area: 81 + coordinates: [9, 30] + teleporter: [106, 3] +- name: Otto's Attic - Stairs + id: 328 + area: 81 + coordinates: [26, 23] + teleporter: [107, 3] +- name: Windia Kid House - Entrance Script # Change to teleporter + id: 329 + area: 82 + coordinates: [7, 10] + teleporter: [178, 0] # Original value [38, 8] +- name: Windia Kid House - Basement Stairs + id: 330 + area: 82 + coordinates: [1, 4] + teleporter: [180, 0] +- name: Windia Old People House - Entrance + id: 331 + area: 82 + coordinates: [55, 12] + teleporter: [179, 0] +- name: Windia Old People House - Basement Stairs + id: 332 + area: 82 + coordinates: [60, 5] + teleporter: [181, 0] +- name: Windia Kid House Basement - Stairs + id: 333 + area: 82 + coordinates: [43, 8] + teleporter: [182, 0] +- name: Windia Kid House Basement - Mobius Teleporter + id: 334 + area: 82 + coordinates: [41, 9] + teleporter: [44, 8] +- name: Windia Old People House Basement - Stairs + id: 335 + area: 82 + coordinates: [39, 26] + teleporter: [183, 0] +- name: Windia Old People House Basement - Mobius Teleporter Script + id: 336 + area: 82 + coordinates: [39, 23] + teleporter: [43, 8] +- name: Windia Inn Lobby - Stairs to Beds + id: 337 + area: 82 + coordinates: [45, 24] + teleporter: [215, 0] +- name: Windia Inn Lobby - Exit + id: 338 + area: 82 + coordinates: [53, 30] + teleporter: [135, 3] +- name: Windia Inn Beds - Stairs to Lobby + id: 339 + area: 82 + coordinates: [33, 59] + teleporter: [216, 0] +- name: Windia Vendor House - Entrance + id: 340 + area: 82 + coordinates: [29, 14] + teleporter: [108, 3] +- name: Pazuzu Tower 1F Main Lobby - Main Entrance 1 + id: 341 + area: 83 + coordinates: [47, 29] + teleporter: [184, 0] +- name: Pazuzu Tower 1F Main Lobby - Main Entrance 2 + id: 342 + area: 83 + coordinates: [47, 30] + teleporter: [184, 0] +- name: Pazuzu Tower 1F Main Lobby - Main Entrance 3 + id: 343 + area: 83 + coordinates: [48, 29] + teleporter: [184, 0] +- name: Pazuzu Tower 1F Main Lobby - Main Entrance 4 + id: 344 + area: 83 + coordinates: [48, 30] + teleporter: [184, 0] +- name: Pazuzu Tower 1F Main Lobby - East Entrance + id: 345 + area: 83 + coordinates: [55, 12] + teleporter: [185, 0] +- name: Pazuzu Tower 1F Main Lobby - South Stairs + id: 346 + area: 83 + coordinates: [51, 25] + teleporter: [186, 0] +- name: Pazuzu Tower 1F Main Lobby - Pazuzu Script 1 + id: 347 + area: 83 + coordinates: [47, 8] + teleporter: [16, 8] +- name: Pazuzu Tower 1F Main Lobby - Pazuzu Script 2 + id: 348 + area: 83 + coordinates: [48, 8] + teleporter: [16, 8] +- name: Pazuzu Tower 1F Boxes Room - West Stairs + id: 349 + area: 83 + coordinates: [38, 17] + teleporter: [187, 0] +- name: Pazuzu 2F - West Upper Stairs + id: 350 + area: 84 + coordinates: [7, 11] + teleporter: [188, 0] +- name: Pazuzu 2F - South Stairs + id: 351 + area: 84 + coordinates: [20, 24] + teleporter: [189, 0] +- name: Pazuzu 2F - West Lower Stairs + id: 352 + area: 84 + coordinates: [6, 17] + teleporter: [190, 0] +- name: Pazuzu 2F - Central Stairs + id: 353 + area: 84 + coordinates: [15, 15] + teleporter: [191, 0] +- name: Pazuzu 2F - Pazuzu 1 + id: 354 + area: 84 + coordinates: [15, 8] + teleporter: [17, 8] +- name: Pazuzu 2F - Pazuzu 2 + id: 355 + area: 84 + coordinates: [16, 8] + teleporter: [17, 8] +- name: Pazuzu 3F Main Room - North Stairs + id: 356 + area: 85 + coordinates: [23, 11] + teleporter: [192, 0] +- name: Pazuzu 3F Main Room - West Stairs + id: 357 + area: 85 + coordinates: [7, 15] + teleporter: [193, 0] +- name: Pazuzu 3F Main Room - Pazuzu Script 1 + id: 358 + area: 85 + coordinates: [15, 8] + teleporter: [18, 8] +- name: Pazuzu 3F Main Room - Pazuzu Script 2 + id: 359 + area: 85 + coordinates: [16, 8] + teleporter: [18, 8] +- name: Pazuzu 3F Central Island - Central Stairs + id: 360 + area: 85 + coordinates: [15, 14] + teleporter: [194, 0] +- name: Pazuzu 3F Central Island - South Stairs + id: 361 + area: 85 + coordinates: [17, 25] + teleporter: [195, 0] +- name: Pazuzu 4F - Northwest Stairs + id: 362 + area: 86 + coordinates: [39, 12] + teleporter: [196, 0] +- name: Pazuzu 4F - Southwest Stairs + id: 363 + area: 86 + coordinates: [39, 19] + teleporter: [197, 0] +- name: Pazuzu 4F - South Stairs + id: 364 + area: 86 + coordinates: [47, 24] + teleporter: [198, 0] +- name: Pazuzu 4F - Northeast Stairs + id: 365 + area: 86 + coordinates: [54, 9] + teleporter: [199, 0] +- name: Pazuzu 4F - Pazuzu Script 1 + id: 366 + area: 86 + coordinates: [47, 8] + teleporter: [19, 8] +- name: Pazuzu 4F - Pazuzu Script 2 + id: 367 + area: 86 + coordinates: [48, 8] + teleporter: [19, 8] +- name: Pazuzu 5F Pazuzu Loop - West Stairs + id: 368 + area: 87 + coordinates: [9, 49] + teleporter: [200, 0] +- name: Pazuzu 5F Pazuzu Loop - South Stairs + id: 369 + area: 87 + coordinates: [16, 55] + teleporter: [201, 0] +- name: Pazuzu 5F Upper Loop - Northeast Stairs + id: 370 + area: 87 + coordinates: [22, 40] + teleporter: [202, 0] +- name: Pazuzu 5F Upper Loop - Northwest Stairs + id: 371 + area: 87 + coordinates: [9, 40] + teleporter: [203, 0] +- name: Pazuzu 5F Upper Loop - Pazuzu Script 1 + id: 372 + area: 87 + coordinates: [15, 40] + teleporter: [20, 8] +- name: Pazuzu 5F Upper Loop - Pazuzu Script 2 + id: 373 + area: 87 + coordinates: [16, 40] + teleporter: [20, 8] +- name: Pazuzu 6F - West Stairs + id: 374 + area: 88 + coordinates: [41, 47] + teleporter: [204, 0] +- name: Pazuzu 6F - Northwest Stairs + id: 375 + area: 88 + coordinates: [41, 40] + teleporter: [205, 0] +- name: Pazuzu 6F - Northeast Stairs + id: 376 + area: 88 + coordinates: [54, 40] + teleporter: [206, 0] +- name: Pazuzu 6F - South Stairs + id: 377 + area: 88 + coordinates: [52, 56] + teleporter: [207, 0] +- name: Pazuzu 6F - Pazuzu Script 1 + id: 378 + area: 88 + coordinates: [47, 40] + teleporter: [21, 8] +- name: Pazuzu 6F - Pazuzu Script 2 + id: 379 + area: 88 + coordinates: [48, 40] + teleporter: [21, 8] +- name: Pazuzu 7F Main Room - Southwest Stairs + id: 380 + area: 89 + coordinates: [15, 54] + teleporter: [26, 0] +- name: Pazuzu 7F Main Room - Northeast Stairs + id: 381 + area: 89 + coordinates: [21, 40] + teleporter: [27, 0] +- name: Pazuzu 7F Main Room - Southeast Stairs + id: 382 + area: 89 + coordinates: [21, 56] + teleporter: [28, 0] +- name: Pazuzu 7F Main Room - Pazuzu Script 1 + id: 383 + area: 89 + coordinates: [15, 44] + teleporter: [22, 8] +- name: Pazuzu 7F Main Room - Pazuzu Script 2 + id: 384 + area: 89 + coordinates: [16, 44] + teleporter: [22, 8] +- name: Pazuzu 7F Main Room - Crystal Script # Added for floor shuffle + id: 480 + area: 89 + coordinates: [15, 40] + teleporter: [38, 8] +- name: Pazuzu 1F to 3F - South Stairs + id: 385 + area: 90 + coordinates: [43, 60] + teleporter: [29, 0] +- name: Pazuzu 1F to 3F - North Stairs + id: 386 + area: 90 + coordinates: [43, 36] + teleporter: [30, 0] +- name: Pazuzu 3F to 5F - South Stairs + id: 387 + area: 91 + coordinates: [43, 60] + teleporter: [40, 0] +- name: Pazuzu 3F to 5F - North Stairs + id: 388 + area: 91 + coordinates: [43, 36] + teleporter: [41, 0] +- name: Pazuzu 5F to 7F - South Stairs + id: 389 + area: 92 + coordinates: [43, 60] + teleporter: [38, 0] +- name: Pazuzu 5F to 7F - North Stairs + id: 390 + area: 92 + coordinates: [43, 36] + teleporter: [39, 0] +- name: Pazuzu 2F to 4F - South Stairs + id: 391 + area: 93 + coordinates: [43, 60] + teleporter: [21, 0] +- name: Pazuzu 2F to 4F - North Stairs + id: 392 + area: 93 + coordinates: [43, 36] + teleporter: [22, 0] +- name: Pazuzu 4F to 6F - South Stairs + id: 393 + area: 94 + coordinates: [43, 60] + teleporter: [2, 0] +- name: Pazuzu 4F to 6F - North Stairs + id: 394 + area: 94 + coordinates: [43, 36] + teleporter: [3, 0] +- name: Light Temple - Entrance + id: 395 + area: 95 + coordinates: [28, 57] + teleporter: [19, 6] +- name: Light Temple - Mobius Teleporter Script + id: 396 + area: 95 + coordinates: [29, 37] + teleporter: [70, 8] +- name: Ship Dock - Mobius Teleporter Script + id: 397 + area: 96 + coordinates: [15, 18] + teleporter: [61, 8] +- name: Ship Dock - From Overworld + id: 398 + area: 96 + coordinates: [15, 11] + teleporter: [73, 0] +- name: Ship Dock - Entrance + id: 399 + area: 96 + coordinates: [15, 23] + teleporter: [17, 6] +- name: Mac Ship Deck - East Entrance Script + id: 400 + area: 97 + coordinates: [26, 40] + teleporter: [37, 8] +- name: Mac Ship Deck - Central Stairs Script + id: 401 + area: 97 + coordinates: [16, 47] + teleporter: [50, 8] +- name: Mac Ship Deck - West Stairs Script + id: 402 + area: 97 + coordinates: [8, 34] + teleporter: [51, 8] +- name: Mac Ship Deck - East Stairs Script + id: 403 + area: 97 + coordinates: [24, 36] + teleporter: [52, 8] +- name: Mac Ship Deck - North Stairs Script + id: 404 + area: 97 + coordinates: [12, 9] + teleporter: [53, 8] +- name: Mac Ship B1 Outer Ring - South Stairs + id: 405 + area: 98 + coordinates: [16, 45] + teleporter: [208, 0] +- name: Mac Ship B1 Outer Ring - West Stairs + id: 406 + area: 98 + coordinates: [8, 35] + teleporter: [175, 0] +- name: Mac Ship B1 Outer Ring - East Stairs + id: 407 + area: 98 + coordinates: [25, 37] + teleporter: [172, 0] +- name: Mac Ship B1 Outer Ring - Northwest Stairs + id: 408 + area: 98 + coordinates: [10, 23] + teleporter: [88, 0] +- name: Mac Ship B1 Square Room - North Stairs + id: 409 + area: 98 + coordinates: [14, 9] + teleporter: [141, 0] +- name: Mac Ship B1 Square Room - South Stairs + id: 410 + area: 98 + coordinates: [16, 12] + teleporter: [87, 0] +- name: Mac Ship B1 Mac Room - Stairs # Unused? + id: 411 + area: 98 + coordinates: [16, 51] + teleporter: [101, 0] +- name: Mac Ship B1 Central Corridor - South Stairs + id: 412 + area: 98 + coordinates: [16, 38] + teleporter: [102, 0] +- name: Mac Ship B1 Central Corridor - North Stairs + id: 413 + area: 98 + coordinates: [16, 26] + teleporter: [86, 0] +- name: Mac Ship B2 South Corridor - South Stairs + id: 414 + area: 99 + coordinates: [48, 51] + teleporter: [57, 1] +- name: Mac Ship B2 South Corridor - North Stairs Script + id: 415 + area: 99 + coordinates: [48, 38] + teleporter: [55, 8] +- name: Mac Ship B2 North Corridor - South Stairs Script + id: 416 + area: 99 + coordinates: [48, 27] + teleporter: [56, 8] +- name: Mac Ship B2 North Corridor - North Stairs Script + id: 417 + area: 99 + coordinates: [48, 12] + teleporter: [57, 8] +- name: Mac Ship B2 Outer Ring - Northwest Stairs Script + id: 418 + area: 99 + coordinates: [55, 11] + teleporter: [58, 8] +- name: Mac Ship B1 Outer Ring Cleared - South Stairs + id: 419 + area: 100 + coordinates: [16, 45] + teleporter: [208, 0] +- name: Mac Ship B1 Outer Ring Cleared - West Stairs + id: 420 + area: 100 + coordinates: [8, 35] + teleporter: [175, 0] +- name: Mac Ship B1 Outer Ring Cleared - East Stairs + id: 421 + area: 100 + coordinates: [25, 37] + teleporter: [172, 0] +- name: Mac Ship B1 Square Room Cleared - North Stairs + id: 422 + area: 100 + coordinates: [14, 9] + teleporter: [141, 0] +- name: Mac Ship B1 Square Room Cleared - South Stairs + id: 423 + area: 100 + coordinates: [16, 12] + teleporter: [87, 0] +- name: Mac Ship B1 Mac Room Cleared - Main Stairs + id: 424 + area: 100 + coordinates: [16, 51] + teleporter: [101, 0] +- name: Mac Ship B1 Central Corridor Cleared - South Stairs + id: 425 + area: 100 + coordinates: [16, 38] + teleporter: [102, 0] +- name: Mac Ship B1 Central Corridor Cleared - North Stairs + id: 426 + area: 100 + coordinates: [16, 26] + teleporter: [86, 0] +- name: Mac Ship B1 Central Corridor Cleared - Northwest Stairs + id: 427 + area: 100 + coordinates: [23, 10] + teleporter: [88, 0] +- name: Doom Castle Corridor of Destiny - South Entrance + id: 428 + area: 101 + coordinates: [59, 29] + teleporter: [84, 0] +- name: Doom Castle Corridor of Destiny - Ice Floor Entrance + id: 429 + area: 101 + coordinates: [59, 21] + teleporter: [35, 2] +- name: Doom Castle Corridor of Destiny - Lava Floor Entrance + id: 430 + area: 101 + coordinates: [59, 13] + teleporter: [209, 0] +- name: Doom Castle Corridor of Destiny - Sky Floor Entrance + id: 431 + area: 101 + coordinates: [59, 5] + teleporter: [211, 0] +- name: Doom Castle Corridor of Destiny - Hero Room Entrance + id: 432 + area: 101 + coordinates: [59, 61] + teleporter: [13, 2] +- name: Doom Castle Ice Floor - Entrance + id: 433 + area: 102 + coordinates: [23, 42] + teleporter: [109, 3] +- name: Doom Castle Lava Floor - Entrance + id: 434 + area: 103 + coordinates: [23, 40] + teleporter: [210, 0] +- name: Doom Castle Sky Floor - Entrance + id: 435 + area: 104 + coordinates: [24, 41] + teleporter: [212, 0] +- name: Doom Castle Hero Room - Dark King Entrance 1 + id: 436 + area: 106 + coordinates: [15, 5] + teleporter: [54, 0] +- name: Doom Castle Hero Room - Dark King Entrance 2 + id: 437 + area: 106 + coordinates: [16, 5] + teleporter: [54, 0] +- name: Doom Castle Hero Room - Dark King Entrance 3 + id: 438 + area: 106 + coordinates: [15, 4] + teleporter: [54, 0] +- name: Doom Castle Hero Room - Dark King Entrance 4 + id: 439 + area: 106 + coordinates: [16, 4] + teleporter: [54, 0] +- name: Doom Castle Hero Room - Hero Statue Script + id: 440 + area: 106 + coordinates: [15, 17] + teleporter: [24, 8] +- name: Doom Castle Hero Room - Entrance + id: 441 + area: 106 + coordinates: [15, 24] + teleporter: [110, 3] +- name: Doom Castle Dark King Room - Entrance + id: 442 + area: 107 + coordinates: [14, 26] + teleporter: [52, 0] +- name: Doom Castle Dark King Room - Dark King Script + id: 443 + area: 107 + coordinates: [14, 15] + teleporter: [25, 8] +- name: Doom Castle Dark King Room - Unknown + id: 444 + area: 107 + coordinates: [47, 54] + teleporter: [77, 0] +- name: Overworld - Level Forest + id: 445 + area: 0 + type: "Overworld" + teleporter: [0x2E, 8] +- name: Overworld - Foresta + id: 446 + area: 0 + type: "Overworld" + teleporter: [0x02, 1] +- name: Overworld - Sand Temple + id: 447 + area: 0 + type: "Overworld" + teleporter: [0x03, 1] +- name: Overworld - Bone Dungeon + id: 448 + area: 0 + type: "Overworld" + teleporter: [0x04, 1] +- name: Overworld - Focus Tower Foresta + id: 449 + area: 0 + type: "Overworld" + teleporter: [0x05, 1] +- name: Overworld - Focus Tower Aquaria + id: 450 + area: 0 + type: "Overworld" + teleporter: [0x13, 1] +- name: Overworld - Libra Temple + id: 451 + area: 0 + type: "Overworld" + teleporter: [0x07, 1] +- name: Overworld - Aquaria + id: 452 + area: 0 + type: "Overworld" + teleporter: [0x08, 8] +- name: Overworld - Wintry Cave + id: 453 + area: 0 + type: "Overworld" + teleporter: [0x0A, 1] +- name: Overworld - Life Temple + id: 454 + area: 0 + type: "Overworld" + teleporter: [0x0B, 1] +- name: Overworld - Falls Basin + id: 455 + area: 0 + type: "Overworld" + teleporter: [0x0C, 1] +- name: Overworld - Ice Pyramid + id: 456 + area: 0 + type: "Overworld" + teleporter: [0x0D, 1] # Will be switched to a script +- name: Overworld - Spencer's Place + id: 457 + area: 0 + type: "Overworld" + teleporter: [0x30, 8] +- name: Overworld - Wintry Temple + id: 458 + area: 0 + type: "Overworld" + teleporter: [0x10, 1] +- name: Overworld - Focus Tower Frozen Strip + id: 459 + area: 0 + type: "Overworld" + teleporter: [0x11, 1] +- name: Overworld - Focus Tower Fireburg + id: 460 + area: 0 + type: "Overworld" + teleporter: [0x12, 1] +- name: Overworld - Fireburg + id: 461 + area: 0 + type: "Overworld" + teleporter: [0x14, 1] +- name: Overworld - Mine + id: 462 + area: 0 + type: "Overworld" + teleporter: [0x15, 1] +- name: Overworld - Sealed Temple + id: 463 + area: 0 + type: "Overworld" + teleporter: [0x16, 1] +- name: Overworld - Volcano + id: 464 + area: 0 + type: "Overworld" + teleporter: [0x17, 1] +- name: Overworld - Lava Dome + id: 465 + area: 0 + type: "Overworld" + teleporter: [0x18, 1] +- name: Overworld - Focus Tower Windia + id: 466 + area: 0 + type: "Overworld" + teleporter: [0x06, 1] +- name: Overworld - Rope Bridge + id: 467 + area: 0 + type: "Overworld" + teleporter: [0x19, 1] +- name: Overworld - Alive Forest + id: 468 + area: 0 + type: "Overworld" + teleporter: [0x1A, 1] +- name: Overworld - Giant Tree + id: 469 + area: 0 + type: "Overworld" + teleporter: [0x1B, 1] +- name: Overworld - Kaidge Temple + id: 470 + area: 0 + type: "Overworld" + teleporter: [0x1C, 1] +- name: Overworld - Windia + id: 471 + area: 0 + type: "Overworld" + teleporter: [0x1D, 1] +- name: Overworld - Windhole Temple + id: 472 + area: 0 + type: "Overworld" + teleporter: [0x1E, 1] +- name: Overworld - Mount Gale + id: 473 + area: 0 + type: "Overworld" + teleporter: [0x1F, 1] +- name: Overworld - Pazuzu Tower + id: 474 + area: 0 + type: "Overworld" + teleporter: [0x20, 1] +- name: Overworld - Ship Dock + id: 475 + area: 0 + type: "Overworld" + teleporter: [0x3E, 1] +- name: Overworld - Doom Castle + id: 476 + area: 0 + type: "Overworld" + teleporter: [0x21, 1] +- name: Overworld - Light Temple + id: 477 + area: 0 + type: "Overworld" + teleporter: [0x22, 1] +- name: Overworld - Mac Ship + id: 478 + area: 0 + type: "Overworld" + teleporter: [0x24, 1] +- name: Overworld - Mac Ship Doom + id: 479 + area: 0 + type: "Overworld" + teleporter: [0x24, 1] +- name: Dummy House - Bed Script + id: 480 + area: 17 + coordinates: [0x28, 0x38] + teleporter: [1, 8] +- name: Dummy House - Entrance + id: 481 + area: 17 + coordinates: [0x29, 0x3B] + teleporter: [0, 10] #None diff --git a/worlds/ffmq/data/rooms.yaml b/worlds/ffmq/data/rooms.yaml new file mode 100644 index 000000000000..4343d785eb7d --- /dev/null +++ b/worlds/ffmq/data/rooms.yaml @@ -0,0 +1,4026 @@ +- name: Overworld + id: 0 + type: "Overworld" + game_objects: [] + links: + - target_room: 220 # To Forest Subregion + access: [] +- name: Subregion Foresta + id: 220 + type: "Subregion" + region: "Foresta" + game_objects: + - name: "Foresta South Battlefield" + object_id: 0x01 + location: "ForestaSouthBattlefield" + location_slot: "ForestaSouthBattlefield" + type: "BattlefieldXp" + access: [] + - name: "Foresta West Battlefield" + object_id: 0x02 + location: "ForestaWestBattlefield" + location_slot: "ForestaWestBattlefield" + type: "BattlefieldItem" + access: [] + - name: "Foresta East Battlefield" + object_id: 0x03 + location: "ForestaEastBattlefield" + location_slot: "ForestaEastBattlefield" + type: "BattlefieldGp" + access: [] + links: + - target_room: 15 # Level Forest + location: "LevelForest" + location_slot: "LevelForest" + entrance: 445 + teleporter: [0x2E, 8] + access: [] + - target_room: 16 # Foresta + location: "Foresta" + location_slot: "Foresta" + entrance: 446 + teleporter: [0x02, 1] + access: [] + - target_room: 24 # Sand Temple + location: "SandTemple" + location_slot: "SandTemple" + entrance: 447 + teleporter: [0x03, 1] + access: [] + - target_room: 25 # Bone Dungeon + location: "BoneDungeon" + location_slot: "BoneDungeon" + entrance: 448 + teleporter: [0x04, 1] + access: [] + - target_room: 3 # Focus Tower Foresta + location: "FocusTowerForesta" + location_slot: "FocusTowerForesta" + entrance: 449 + teleporter: [0x05, 1] + access: [] + - target_room: 221 + access: ["SandCoin"] + - target_room: 224 + access: ["RiverCoin"] + - target_room: 226 + access: ["SunCoin"] +- name: Subregion Aquaria + id: 221 + type: "Subregion" + region: "Aquaria" + game_objects: + - name: "South of Libra Temple Battlefield" + object_id: 0x04 + location: "AquariaBattlefield01" + location_slot: "AquariaBattlefield01" + type: "BattlefieldXp" + access: [] + - name: "East of Libra Temple Battlefield" + object_id: 0x05 + location: "AquariaBattlefield02" + location_slot: "AquariaBattlefield02" + type: "BattlefieldGp" + access: [] + - name: "South of Aquaria Battlefield" + object_id: 0x06 + location: "AquariaBattlefield03" + location_slot: "AquariaBattlefield03" + type: "BattlefieldItem" + access: [] + - name: "South of Wintry Cave Battlefield" + object_id: 0x07 + location: "WintryBattlefield01" + location_slot: "WintryBattlefield01" + type: "BattlefieldXp" + access: [] + - name: "West of Wintry Cave Battlefield" + object_id: 0x08 + location: "WintryBattlefield02" + location_slot: "WintryBattlefield02" + type: "BattlefieldGp" + access: [] + - name: "Ice Pyramid Battlefield" + object_id: 0x09 + location: "PyramidBattlefield01" + location_slot: "PyramidBattlefield01" + type: "BattlefieldXp" + access: [] + links: + - target_room: 10 # Focus Tower Aquaria + location: "FocusTowerAquaria" + location_slot: "FocusTowerAquaria" + entrance: 450 + teleporter: [0x13, 1] + access: [] + - target_room: 39 # Libra Temple + location: "LibraTemple" + location_slot: "LibraTemple" + entrance: 451 + teleporter: [0x07, 1] + access: [] + - target_room: 40 # Aquaria + location: "Aquaria" + location_slot: "Aquaria" + entrance: 452 + teleporter: [0x08, 8] + access: [] + - target_room: 45 # Wintry Cave + location: "WintryCave" + location_slot: "WintryCave" + entrance: 453 + teleporter: [0x0A, 1] + access: [] + - target_room: 52 # Falls Basin + location: "FallsBasin" + location_slot: "FallsBasin" + entrance: 455 + teleporter: [0x0C, 1] + access: [] + - target_room: 54 # Ice Pyramid + location: "IcePyramid" + location_slot: "IcePyramid" + entrance: 456 + teleporter: [0x0D, 1] # Will be switched to a script + access: [] + - target_room: 220 + access: ["SandCoin"] + - target_room: 224 + access: ["SandCoin", "RiverCoin"] + - target_room: 226 + access: ["SandCoin", "SunCoin"] + - target_room: 223 + access: ["SummerAquaria"] +- name: Subregion Life Temple + id: 222 + type: "Subregion" + region: "LifeTemple" + game_objects: [] + links: + - target_room: 51 # Life Temple + location: "LifeTemple" + location_slot: "LifeTemple" + entrance: 454 + teleporter: [0x0B, 1] + access: [] +- name: Subregion Frozen Fields + id: 223 + type: "Subregion" + region: "AquariaFrozenField" + game_objects: + - name: "North of Libra Temple Battlefield" + object_id: 0x0A + location: "LibraBattlefield01" + location_slot: "LibraBattlefield01" + type: "BattlefieldItem" + access: [] + - name: "Aquaria Frozen Field Battlefield" + object_id: 0x0B + location: "LibraBattlefield02" + location_slot: "LibraBattlefield02" + type: "BattlefieldXp" + access: [] + links: + - target_room: 74 # Wintry Temple + location: "WintryTemple" + location_slot: "WintryTemple" + entrance: 458 + teleporter: [0x10, 1] + access: [] + - target_room: 14 # Focus Tower Frozen Strip + location: "FocusTowerFrozen" + location_slot: "FocusTowerFrozen" + entrance: 459 + teleporter: [0x11, 1] + access: [] + - target_room: 221 + access: [] + - target_room: 225 + access: ["SummerAquaria", "DualheadHydra"] +- name: Subregion Fireburg + id: 224 + type: "Subregion" + region: "Fireburg" + game_objects: + - name: "Path to Fireburg Southern Battlefield" + object_id: 0x0C + location: "FireburgBattlefield01" + location_slot: "FireburgBattlefield01" + type: "BattlefieldGp" + access: [] + - name: "Path to Fireburg Central Battlefield" + object_id: 0x0D + location: "FireburgBattlefield02" + location_slot: "FireburgBattlefield02" + type: "BattlefieldItem" + access: [] + - name: "Path to Fireburg Northern Battlefield" + object_id: 0x0E + location: "FireburgBattlefield03" + location_slot: "FireburgBattlefield03" + type: "BattlefieldXp" + access: [] + - name: "Sealed Temple Battlefield" + object_id: 0x0F + location: "MineBattlefield01" + location_slot: "MineBattlefield01" + type: "BattlefieldGp" + access: [] + - name: "Mine Battlefield" + object_id: 0x10 + location: "MineBattlefield02" + location_slot: "MineBattlefield02" + type: "BattlefieldItem" + access: [] + - name: "Boulder Battlefield" + object_id: 0x11 + location: "MineBattlefield03" + location_slot: "MineBattlefield03" + type: "BattlefieldXp" + access: [] + links: + - target_room: 13 # Focus Tower Fireburg + location: "FocusTowerFireburg" + location_slot: "FocusTowerFireburg" + entrance: 460 + teleporter: [0x12, 1] + access: [] + - target_room: 76 # Fireburg + location: "Fireburg" + location_slot: "Fireburg" + entrance: 461 + teleporter: [0x14, 1] + access: [] + - target_room: 84 # Mine + location: "Mine" + location_slot: "Mine" + entrance: 462 + teleporter: [0x15, 1] + access: [] + - target_room: 92 # Sealed Temple + location: "SealedTemple" + location_slot: "SealedTemple" + entrance: 463 + teleporter: [0x16, 1] + access: [] + - target_room: 93 # Volcano + location: "Volcano" + location_slot: "Volcano" + entrance: 464 + teleporter: [0x17, 1] # Also this one / 0x0F, 8 + access: [] + - target_room: 100 # Lava Dome + location: "LavaDome" + location_slot: "LavaDome" + entrance: 465 + teleporter: [0x18, 1] + access: [] + - target_room: 220 + access: ["RiverCoin"] + - target_room: 221 + access: ["SandCoin", "RiverCoin"] + - target_room: 226 + access: ["RiverCoin", "SunCoin"] + - target_room: 225 + access: ["DualheadHydra"] +- name: Subregion Volcano Battlefield + id: 225 + type: "Subregion" + region: "VolcanoBattlefield" + game_objects: + - name: "Volcano Battlefield" + object_id: 0x12 + location: "VolcanoBattlefield01" + location_slot: "VolcanoBattlefield01" + type: "BattlefieldXp" + access: [] + links: + - target_room: 224 + access: ["DualheadHydra"] + - target_room: 223 + access: ["SummerAquaria"] +- name: Subregion Windia + id: 226 + type: "Subregion" + region: "Windia" + game_objects: + - name: "Kaidge Temple Battlefield" + object_id: 0x13 + location: "WindiaBattlefield01" + location_slot: "WindiaBattlefield01" + type: "BattlefieldXp" + access: [] + - name: "South of Windia Battlefield" + object_id: 0x14 + location: "WindiaBattlefield02" + location_slot: "WindiaBattlefield02" + type: "BattlefieldXp" + access: [] + links: + - target_room: 9 # Focus Tower Windia + location: "FocusTowerWindia" + location_slot: "FocusTowerWindia" + entrance: 466 + teleporter: [0x06, 1] + access: [] + - target_room: 123 # Rope Bridge + location: "RopeBridge" + location_slot: "RopeBridge" + entrance: 467 + teleporter: [0x19, 1] + access: [] + - target_room: 124 # Alive Forest + location: "AliveForest" + location_slot: "AliveForest" + entrance: 468 + teleporter: [0x1A, 1] + access: [] + - target_room: 125 # Giant Tree + location: "GiantTree" + location_slot: "GiantTree" + entrance: 469 + teleporter: [0x1B, 1] + access: ["Barred"] + - target_room: 152 # Kaidge Temple + location: "KaidgeTemple" + location_slot: "KaidgeTemple" + entrance: 470 + teleporter: [0x1C, 1] + access: [] + - target_room: 156 # Windia + location: "Windia" + location_slot: "Windia" + entrance: 471 + teleporter: [0x1D, 1] + access: [] + - target_room: 154 # Windhole Temple + location: "WindholeTemple" + location_slot: "WindholeTemple" + entrance: 472 + teleporter: [0x1E, 1] + access: [] + - target_room: 155 # Mount Gale + location: "MountGale" + location_slot: "MountGale" + entrance: 473 + teleporter: [0x1F, 1] + access: [] + - target_room: 166 # Pazuzu Tower + location: "PazuzusTower" + location_slot: "PazuzusTower" + entrance: 474 + teleporter: [0x20, 1] + access: [] + - target_room: 220 + access: ["SunCoin"] + - target_room: 221 + access: ["SandCoin", "SunCoin"] + - target_room: 224 + access: ["RiverCoin", "SunCoin"] + - target_room: 227 + access: ["RainbowBridge"] +- name: Subregion Spencer's Cave + id: 227 + type: "Subregion" + region: "SpencerCave" + game_objects: [] + links: + - target_room: 73 # Spencer's Place + location: "SpencersPlace" + location_slot: "SpencersPlace" + entrance: 457 + teleporter: [0x30, 8] + access: [] + - target_room: 226 + access: ["RainbowBridge"] +- name: Subregion Ship Dock + id: 228 + type: "Subregion" + region: "ShipDock" + game_objects: [] + links: + - target_room: 186 # Ship Dock + location: "ShipDock" + location_slot: "ShipDock" + entrance: 475 + teleporter: [0x3E, 1] + access: [] + - target_room: 229 + access: ["ShipLiberated", "ShipDockAccess"] +- name: Subregion Mac's Ship + id: 229 + type: "Subregion" + region: "MacShip" + game_objects: [] + links: + - target_room: 187 # Mac Ship + location: "MacsShip" + location_slot: "MacsShip" + entrance: 478 + teleporter: [0x24, 1] + access: [] + - target_room: 228 + access: ["ShipLiberated", "ShipDockAccess"] + - target_room: 231 + access: ["ShipLoaned", "ShipDockAccess", "ShipSteeringWheel"] +- name: Subregion Light Temple + id: 230 + type: "Subregion" + region: "LightTemple" + game_objects: [] + links: + - target_room: 185 # Light Temple + location: "LightTemple" + location_slot: "LightTemple" + entrance: 477 + teleporter: [0x23, 1] + access: [] +- name: Subregion Doom Castle + id: 231 + type: "Subregion" + region: "DoomCastle" + game_objects: [] + links: + - target_room: 1 # Doom Castle + location: "DoomCastle" + location_slot: "DoomCastle" + entrance: 476 + teleporter: [0x21, 1] + access: [] + - target_room: 187 # Mac Ship Doom + location: "MacsShipDoom" + location_slot: "MacsShipDoom" + entrance: 479 + teleporter: [0x24, 1] + access: ["Barred"] + - target_room: 229 + access: ["ShipLoaned", "ShipDockAccess", "ShipSteeringWheel"] +- name: Doom Castle - Sand Floor + id: 1 + game_objects: + - name: "Doom Castle B2 - Southeast Chest" + object_id: 0x01 + type: "Chest" + access: ["Bomb"] + - name: "Doom Castle B2 - Bone Ledge Box" + object_id: 0x1E + type: "Box" + access: [] + - name: "Doom Castle B2 - Hook Platform Box" + object_id: 0x1F + type: "Box" + access: ["DragonClaw"] + links: + - target_room: 231 + entrance: 1 + teleporter: [1, 6] + access: [] + - target_room: 5 + entrance: 0 + teleporter: [0, 0] + access: ["DragonClaw", "MegaGrenade"] +- name: Doom Castle - Aero Room + id: 2 + game_objects: + - name: "Doom Castle B2 - Sun Door Chest" + object_id: 0x00 + type: "Chest" + access: [] + links: + - target_room: 4 + entrance: 2 + teleporter: [1, 0] + access: [] +- name: Focus Tower B1 - Main Loop + id: 3 + game_objects: [] + links: + - target_room: 220 + entrance: 3 + teleporter: [2, 6] + access: [] + - target_room: 6 + entrance: 4 + teleporter: [4, 0] + access: [] +- name: Focus Tower B1 - Aero Corridor + id: 4 + game_objects: [] + links: + - target_room: 9 + entrance: 5 + teleporter: [5, 0] + access: [] + - target_room: 2 + entrance: 6 + teleporter: [8, 0] + access: [] +- name: Focus Tower B1 - Inner Loop + id: 5 + game_objects: [] + links: + - target_room: 1 + entrance: 8 + teleporter: [7, 0] + access: [] + - target_room: 201 + entrance: 7 + teleporter: [6, 0] + access: [] +- name: Focus Tower 1F Main Lobby + id: 6 + game_objects: + - name: "Focus Tower 1F - Main Lobby Box" + object_id: 0x21 + type: "Box" + access: [] + links: + - target_room: 3 + entrance: 11 + teleporter: [11, 0] + access: [] + - target_room: 7 + access: ["SandCoin"] + - target_room: 8 + access: ["RiverCoin"] + - target_room: 9 + access: ["SunCoin"] +- name: Focus Tower 1F SandCoin Room + id: 7 + game_objects: [] + links: + - target_room: 6 + access: ["SandCoin"] + - target_room: 10 + entrance: 10 + teleporter: [10, 0] + access: [] +- name: Focus Tower 1F RiverCoin Room + id: 8 + game_objects: [] + links: + - target_room: 6 + access: ["RiverCoin"] + - target_room: 11 + entrance: 14 + teleporter: [14, 0] + access: [] +- name: Focus Tower 1F SunCoin Room + id: 9 + game_objects: [] + links: + - target_room: 6 + access: ["SunCoin"] + - target_room: 4 + entrance: 12 + teleporter: [12, 0] + access: [] + - target_room: 226 + entrance: 9 + teleporter: [3, 6] + access: [] +- name: Focus Tower 1F SkyCoin Room + id: 201 + game_objects: [] + links: + - target_room: 195 + entrance: 13 + teleporter: [13, 0] + access: ["SkyCoin", "FlamerusRex", "IceGolem", "DualheadHydra", "Pazuzu"] + - target_room: 5 + entrance: 15 + teleporter: [15, 0] + access: [] +- name: Focus Tower 2F - Sand Coin Passage + id: 10 + game_objects: + - name: "Focus Tower 2F - Sand Door Chest" + object_id: 0x03 + type: "Chest" + access: [] + links: + - target_room: 221 + entrance: 16 + teleporter: [4, 6] + access: [] + - target_room: 7 + entrance: 17 + teleporter: [17, 0] + access: [] +- name: Focus Tower 2F - River Coin Passage + id: 11 + game_objects: [] + links: + - target_room: 8 + entrance: 18 + teleporter: [18, 0] + access: [] + - target_room: 13 + entrance: 19 + teleporter: [20, 0] + access: [] +- name: Focus Tower 2F - Venus Chest Room + id: 12 + game_objects: + - name: "Focus Tower 2F - Back Door Chest" + object_id: 0x02 + type: "Chest" + access: [] + - name: "Focus Tower 2F - Venus Chest" + object_id: 9 + type: "NPC" + access: ["Bomb", "VenusKey"] + links: + - target_room: 14 + entrance: 20 + teleporter: [19, 0] + access: [] +- name: Focus Tower 3F - Lower Floor + id: 13 + game_objects: + - name: "Focus Tower 3F - River Door Box" + object_id: 0x22 + type: "Box" + access: [] + links: + - target_room: 224 + entrance: 22 + teleporter: [6, 6] + access: [] + - target_room: 11 + entrance: 23 + teleporter: [24, 0] + access: [] +- name: Focus Tower 3F - Upper Floor + id: 14 + game_objects: [] + links: + - target_room: 223 + entrance: 24 + teleporter: [5, 6] + access: [] + - target_room: 12 + entrance: 25 + teleporter: [23, 0] + access: [] +- name: Level Forest + id: 15 + game_objects: + - name: "Level Forest - Northwest Box" + object_id: 0x28 + type: "Box" + access: ["Axe"] + - name: "Level Forest - Northeast Box" + object_id: 0x29 + type: "Box" + access: ["Axe"] + - name: "Level Forest - Middle Box" + object_id: 0x2A + type: "Box" + access: [] + - name: "Level Forest - Southwest Box" + object_id: 0x2B + type: "Box" + access: ["Axe"] + - name: "Level Forest - Southeast Box" + object_id: 0x2C + type: "Box" + access: ["Axe"] + - name: "Minotaur" + object_id: 0 + type: "Trigger" + on_trigger: ["Minotaur"] + access: ["Kaeli1"] + - name: "Level Forest - Old Man" + object_id: 0 + type: "NPC" + access: [] + - name: "Level Forest - Kaeli" + object_id: 1 + type: "NPC" + access: ["Kaeli1", "Minotaur"] + links: + - target_room: 220 + entrance: 28 + teleporter: [25, 0] + access: [] +- name: Foresta + id: 16 + game_objects: + - name: "Foresta - Outside Box" + object_id: 0x2D + type: "Box" + access: ["Axe"] + links: + - target_room: 220 + entrance: 38 + teleporter: [31, 0] + access: [] + - target_room: 17 + entrance: 44 + teleporter: [0, 5] + access: [] + - target_room: 18 + entrance: 42 + teleporter: [32, 4] + access: [] + - target_room: 19 + entrance: 43 + teleporter: [33, 0] + access: [] + - target_room: 20 + entrance: 45 + teleporter: [1, 5] + access: [] +- name: Kaeli's House + id: 17 + game_objects: + - name: "Foresta - Kaeli's House Box" + object_id: 0x2E + type: "Box" + access: [] + - name: "Kaeli 1" + object_id: 0 + type: "Trigger" + on_trigger: ["Kaeli1"] + access: ["TreeWither"] + - name: "Kaeli 2" + object_id: 0 + type: "Trigger" + on_trigger: ["Kaeli2"] + access: ["Kaeli1", "Minotaur", "Elixir"] + links: + - target_room: 16 + entrance: 46 + teleporter: [86, 3] + access: [] +- name: Foresta Houses - Old Man's House Main + id: 18 + game_objects: [] + links: + - target_room: 19 + access: ["BarrelPushed"] + - target_room: 16 + entrance: 47 + teleporter: [34, 0] + access: [] +- name: Foresta Houses - Old Man's House Back + id: 19 + game_objects: + - name: "Foresta - Old Man House Chest" + object_id: 0x05 + type: "Chest" + access: [] + - name: "Old Man Barrel" + object_id: 0 + type: "Trigger" + on_trigger: ["BarrelPushed"] + access: [] + links: + - target_room: 18 + access: ["BarrelPushed"] + - target_room: 16 + entrance: 48 + teleporter: [35, 0] + access: [] +- name: Foresta Houses - Rest House + id: 20 + game_objects: + - name: "Foresta - Rest House Box" + object_id: 0x2F + type: "Box" + access: [] + links: + - target_room: 16 + entrance: 50 + teleporter: [87, 3] + access: [] +- name: Libra Treehouse + id: 21 + game_objects: + - name: "Alive Forest - Libra Treehouse Box" + object_id: 0x32 + type: "Box" + access: [] + links: + - target_room: 124 + entrance: 51 + teleporter: [67, 8] + access: ["LibraCrest"] +- name: Gemini Treehouse + id: 22 + game_objects: + - name: "Alive Forest - Gemini Treehouse Box" + object_id: 0x33 + type: "Box" + access: [] + links: + - target_room: 124 + entrance: 52 + teleporter: [68, 8] + access: ["GeminiCrest"] +- name: Mobius Treehouse + id: 23 + game_objects: + - name: "Alive Forest - Mobius Treehouse West Box" + object_id: 0x30 + type: "Box" + access: [] + - name: "Alive Forest - Mobius Treehouse East Box" + object_id: 0x31 + type: "Box" + access: [] + links: + - target_room: 124 + entrance: 53 + teleporter: [69, 8] + access: ["MobiusCrest"] +- name: Sand Temple + id: 24 + game_objects: + - name: "Tristam Sand Temple" + object_id: 0 + type: "Trigger" + on_trigger: ["Tristam"] + access: [] + links: + - target_room: 220 + entrance: 54 + teleporter: [36, 0] + access: [] +- name: Bone Dungeon 1F + id: 25 + game_objects: + - name: "Bone Dungeon 1F - Entrance Room West Box" + object_id: 0x35 + type: "Box" + access: [] + - name: "Bone Dungeon 1F - Entrance Room Middle Box" + object_id: 0x36 + type: "Box" + access: [] + - name: "Bone Dungeon 1F - Entrance Room East Box" + object_id: 0x37 + type: "Box" + access: [] + links: + - target_room: 220 + entrance: 55 + teleporter: [37, 0] + access: [] + - target_room: 26 + entrance: 56 + teleporter: [2, 2] + access: [] +- name: Bone Dungeon B1 - Waterway + id: 26 + game_objects: + - name: "Bone Dungeon B1 - Skull Chest" + object_id: 0x06 + type: "Chest" + access: ["Bomb"] + - name: "Bone Dungeon B1 - Tristam" + object_id: 2 + type: "NPC" + access: ["Tristam"] + links: + - target_room: 25 + entrance: 59 + teleporter: [88, 3] + access: [] + - target_room: 28 + entrance: 57 + teleporter: [3, 2] + access: ["Bomb"] +- name: Bone Dungeon B1 - Checker Room + id: 28 + game_objects: + - name: "Bone Dungeon B1 - Checker Room Box" + object_id: 0x38 + type: "Box" + access: ["Bomb"] + links: + - target_room: 26 + entrance: 61 + teleporter: [89, 3] + access: [] + - target_room: 30 + entrance: 60 + teleporter: [4, 2] + access: [] +- name: Bone Dungeon B1 - Hidden Room + id: 29 + game_objects: + - name: "Bone Dungeon B1 - Ribcage Waterway Box" + object_id: 0x39 + type: "Box" + access: [] + links: + - target_room: 31 + entrance: 62 + teleporter: [91, 3] + access: [] +- name: Bone Dungeon B2 - Exploding Skull Room - First Room + id: 30 + game_objects: + - name: "Bone Dungeon B2 - Spines Room Alcove Box" + object_id: 0x3B + type: "Box" + access: [] + - name: "Long Spine" + object_id: 0 + type: "Trigger" + on_trigger: ["LongSpineBombed"] + access: ["Bomb"] + links: + - target_room: 28 + entrance: 65 + teleporter: [90, 3] + access: [] + - target_room: 31 + access: ["LongSpineBombed"] +- name: Bone Dungeon B2 - Exploding Skull Room - Second Room + id: 31 + game_objects: + - name: "Bone Dungeon B2 - Spines Room Looped Hallway Box" + object_id: 0x3A + type: "Box" + access: [] + - name: "Short Spine" + object_id: 0 + type: "Trigger" + on_trigger: ["ShortSpineBombed"] + access: ["Bomb"] + links: + - target_room: 29 + entrance: 63 + teleporter: [5, 2] + access: ["LongSpineBombed"] + - target_room: 32 + access: ["ShortSpineBombed"] + - target_room: 30 + access: ["LongSpineBombed"] +- name: Bone Dungeon B2 - Exploding Skull Room - Third Room + id: 32 + game_objects: [] + links: + - target_room: 35 + entrance: 64 + teleporter: [6, 2] + access: [] + - target_room: 31 + access: ["ShortSpineBombed"] +- name: Bone Dungeon B2 - Box Room + id: 33 + game_objects: + - name: "Bone Dungeon B2 - Lone Room Box" + object_id: 0x3D + type: "Box" + access: [] + links: + - target_room: 36 + entrance: 66 + teleporter: [93, 3] + access: [] +- name: Bone Dungeon B2 - Quake Room + id: 34 + game_objects: + - name: "Bone Dungeon B2 - Penultimate Room Chest" + object_id: 0x07 + type: "Chest" + access: [] + links: + - target_room: 37 + entrance: 67 + teleporter: [94, 3] + access: [] +- name: Bone Dungeon B2 - Two Skulls Room - First Room + id: 35 + game_objects: + - name: "Bone Dungeon B2 - Two Skulls Room Box" + object_id: 0x3C + type: "Box" + access: [] + - name: "Skull 1" + object_id: 0 + type: "Trigger" + on_trigger: ["Skull1Bombed"] + access: ["Bomb"] + links: + - target_room: 32 + entrance: 71 + teleporter: [92, 3] + access: [] + - target_room: 36 + access: ["Skull1Bombed"] +- name: Bone Dungeon B2 - Two Skulls Room - Second Room + id: 36 + game_objects: + - name: "Skull 2" + object_id: 0 + type: "Trigger" + on_trigger: ["Skull2Bombed"] + access: ["Bomb"] + links: + - target_room: 33 + entrance: 68 + teleporter: [7, 2] + access: [] + - target_room: 37 + access: ["Skull2Bombed"] + - target_room: 35 + access: ["Skull1Bombed"] +- name: Bone Dungeon B2 - Two Skulls Room - Third Room + id: 37 + game_objects: [] + links: + - target_room: 34 + entrance: 69 + teleporter: [8, 2] + access: [] + - target_room: 38 + entrance: 70 + teleporter: [9, 2] + access: ["Bomb"] + - target_room: 36 + access: ["Skull2Bombed"] +- name: Bone Dungeon B2 - Boss Room + id: 38 + game_objects: + - name: "Bone Dungeon B2 - North Box" + object_id: 0x3E + type: "Box" + access: [] + - name: "Bone Dungeon B2 - South Box" + object_id: 0x3F + type: "Box" + access: [] + - name: "Bone Dungeon B2 - Flamerus Rex Chest" + object_id: 0x08 + type: "Chest" + access: [] + - name: "Bone Dungeon B2 - Tristam's Treasure Chest" + object_id: 0x04 + type: "Chest" + access: [] + - name: "Flamerus Rex" + object_id: 0 + type: "Trigger" + on_trigger: ["FlamerusRex"] + access: [] + links: + - target_room: 37 + entrance: 74 + teleporter: [95, 3] + access: [] +- name: Libra Temple + id: 39 + game_objects: + - name: "Libra Temple - Box" + object_id: 0x40 + type: "Box" + access: [] + - name: "Phoebe" + object_id: 0 + type: "Trigger" + on_trigger: ["Phoebe1"] + access: [] + links: + - target_room: 221 + entrance: 75 + teleporter: [13, 6] + access: [] + - target_room: 51 + entrance: 76 + teleporter: [59, 8] + access: ["LibraCrest"] +- name: Aquaria + id: 40 + game_objects: + - name: "Summer Aquaria" + object_id: 0 + type: "Trigger" + on_trigger: ["SummerAquaria"] + access: ["WakeWater"] + links: + - target_room: 221 + entrance: 77 + teleporter: [8, 6] + access: [] + - target_room: 41 + entrance: 81 + teleporter: [10, 5] + access: [] + - target_room: 42 + entrance: 82 + teleporter: [44, 4] + access: [] + - target_room: 44 + entrance: 83 + teleporter: [11, 5] + access: [] + - target_room: 71 + entrance: 89 + teleporter: [42, 0] + access: ["SummerAquaria"] + - target_room: 71 + entrance: 90 + teleporter: [43, 0] + access: ["SummerAquaria"] +- name: Phoebe's House + id: 41 + game_objects: + - name: "Aquaria - Phoebe's House Chest" + object_id: 0x41 + type: "Box" + access: [] + links: + - target_room: 40 + entrance: 93 + teleporter: [5, 8] + access: [] +- name: Aquaria Vendor House + id: 42 + game_objects: + - name: "Aquaria - Vendor" + object_id: 4 + type: "NPC" + access: [] + - name: "Aquaria - Vendor House Box" + object_id: 0x42 + type: "Box" + access: [] + links: + - target_room: 40 + entrance: 94 + teleporter: [40, 8] + access: [] + - target_room: 43 + entrance: 95 + teleporter: [47, 0] + access: [] +- name: Aquaria Gemini Room + id: 43 + game_objects: [] + links: + - target_room: 42 + entrance: 97 + teleporter: [48, 0] + access: [] + - target_room: 81 + entrance: 96 + teleporter: [72, 8] + access: ["GeminiCrest"] +- name: Aquaria INN + id: 44 + game_objects: [] + links: + - target_room: 40 + entrance: 98 + teleporter: [75, 8] + access: [] +- name: Wintry Cave 1F - East Ledge + id: 45 + game_objects: + - name: "Wintry Cave 1F - North Box" + object_id: 0x43 + type: "Box" + access: [] + - name: "Wintry Cave 1F - Entrance Box" + object_id: 0x46 + type: "Box" + access: [] + - name: "Wintry Cave 1F - Slippery Cliff Box" + object_id: 0x44 + type: "Box" + access: ["Claw"] + - name: "Wintry Cave 1F - Phoebe" + object_id: 5 + type: "NPC" + access: ["Phoebe1"] + links: + - target_room: 221 + entrance: 99 + teleporter: [49, 0] + access: [] + - target_room: 49 + entrance: 100 + teleporter: [14, 2] + access: ["Bomb"] + - target_room: 46 + access: ["Claw"] +- name: Wintry Cave 1F - Central Space + id: 46 + game_objects: + - name: "Wintry Cave 1F - Scenic Overlook Box" + object_id: 0x45 + type: "Box" + access: ["Claw"] + links: + - target_room: 45 + access: ["Claw"] + - target_room: 47 + access: ["Claw"] +- name: Wintry Cave 1F - West Ledge + id: 47 + game_objects: [] + links: + - target_room: 48 + entrance: 101 + teleporter: [15, 2] + access: ["Bomb"] + - target_room: 46 + access: ["Claw"] +- name: Wintry Cave 2F + id: 48 + game_objects: + - name: "Wintry Cave 2F - West Left Box" + object_id: 0x47 + type: "Box" + access: [] + - name: "Wintry Cave 2F - West Right Box" + object_id: 0x48 + type: "Box" + access: [] + - name: "Wintry Cave 2F - East Left Box" + object_id: 0x49 + type: "Box" + access: [] + - name: "Wintry Cave 2F - East Right Box" + object_id: 0x4A + type: "Box" + access: [] + links: + - target_room: 47 + entrance: 104 + teleporter: [97, 3] + access: [] + - target_room: 50 + entrance: 103 + teleporter: [50, 0] + access: [] +- name: Wintry Cave 3F Top + id: 49 + game_objects: + - name: "Wintry Cave 3F - West Box" + object_id: 0x4B + type: "Box" + access: [] + - name: "Wintry Cave 3F - East Box" + object_id: 0x4C + type: "Box" + access: [] + links: + - target_room: 45 + entrance: 105 + teleporter: [96, 3] + access: [] +- name: Wintry Cave 3F Bottom + id: 50 + game_objects: + - name: "Wintry Cave 3F - Squidite Chest" + object_id: 0x09 + type: "Chest" + access: ["Phanquid"] + - name: "Phanquid" + object_id: 0 + type: "Trigger" + on_trigger: ["Phanquid"] + access: [] + - name: "Wintry Cave 3F - Before Boss Box" + object_id: 0x4D + type: "Box" + access: [] + links: + - target_room: 48 + entrance: 106 + teleporter: [51, 0] + access: [] +- name: Life Temple + id: 51 + game_objects: + - name: "Life Temple - Box" + object_id: 0x4E + type: "Box" + access: [] + - name: "Life Temple - Mysterious Man" + object_id: 6 + type: "NPC" + access: [] + links: + - target_room: 222 + entrance: 107 + teleporter: [14, 6] + access: [] + - target_room: 39 + entrance: 108 + teleporter: [60, 8] + access: ["LibraCrest"] +- name: Fall Basin + id: 52 + game_objects: + - name: "Falls Basin - Snow Crab Chest" + object_id: 0x0A + type: "Chest" + access: ["FreezerCrab"] + - name: "Freezer Crab" + object_id: 0 + type: "Trigger" + on_trigger: ["FreezerCrab"] + access: [] + - name: "Falls Basin - Box" + object_id: 0x4F + type: "Box" + access: [] + links: + - target_room: 221 + entrance: 111 + teleporter: [53, 0] + access: [] +- name: Ice Pyramid B1 Taunt Room + id: 53 + game_objects: + - name: "Ice Pyramid B1 - Chest" + object_id: 0x0B + type: "Chest" + access: [] + - name: "Ice Pyramid B1 - West Box" + object_id: 0x50 + type: "Box" + access: [] + - name: "Ice Pyramid B1 - North Box" + object_id: 0x51 + type: "Box" + access: [] + - name: "Ice Pyramid B1 - East Box" + object_id: 0x52 + type: "Box" + access: [] + links: + - target_room: 68 + entrance: 113 + teleporter: [55, 0] + access: [] +- name: Ice Pyramid 1F Maze Lobby + id: 54 + game_objects: + - name: "Ice Pyramid 1F Statue" + object_id: 0 + type: "Trigger" + on_trigger: ["IcePyramid1FStatue"] + access: ["Sword"] + links: + - target_room: 221 + entrance: 114 + teleporter: [56, 0] + access: [] + - target_room: 55 + access: ["IcePyramid1FStatue"] +- name: Ice Pyramid 1F Maze + id: 55 + game_objects: + - name: "Ice Pyramid 1F - East Alcove Chest" + object_id: 0x0D + type: "Chest" + access: [] + - name: "Ice Pyramid 1F - Sandwiched Alcove Box" + object_id: 0x53 + type: "Box" + access: [] + - name: "Ice Pyramid 1F - Southwest Left Box" + object_id: 0x54 + type: "Box" + access: [] + - name: "Ice Pyramid 1F - Southwest Right Box" + object_id: 0x55 + type: "Box" + access: [] + links: + - target_room: 56 + entrance: 116 + teleporter: [57, 0] + access: [] + - target_room: 57 + entrance: 117 + teleporter: [58, 0] + access: [] + - target_room: 58 + entrance: 118 + teleporter: [59, 0] + access: [] + - target_room: 59 + entrance: 119 + teleporter: [60, 0] + access: [] + - target_room: 60 + entrance: 120 + teleporter: [61, 0] + access: [] + - target_room: 54 + access: ["IcePyramid1FStatue"] +- name: Ice Pyramid 2F South Tiled Room + id: 56 + game_objects: + - name: "Ice Pyramid 2F - South Side Glass Door Box" + object_id: 0x57 + type: "Box" + access: ["Sword"] + - name: "Ice Pyramid 2F - South Side East Box" + object_id: 0x5B + type: "Box" + access: [] + links: + - target_room: 55 + entrance: 122 + teleporter: [62, 0] + access: [] + - target_room: 61 + entrance: 123 + teleporter: [67, 0] + access: [] +- name: Ice Pyramid 2F West Room + id: 57 + game_objects: + - name: "Ice Pyramid 2F - Northwest Room Box" + object_id: 0x5A + type: "Box" + access: [] + links: + - target_room: 55 + entrance: 124 + teleporter: [63, 0] + access: [] +- name: Ice Pyramid 2F Center Room + id: 58 + game_objects: + - name: "Ice Pyramid 2F - Center Room Box" + object_id: 0x56 + type: "Box" + access: [] + links: + - target_room: 55 + entrance: 125 + teleporter: [64, 0] + access: [] +- name: Ice Pyramid 2F Small North Room + id: 59 + game_objects: + - name: "Ice Pyramid 2F - North Room Glass Door Box" + object_id: 0x58 + type: "Box" + access: ["Sword"] + links: + - target_room: 55 + entrance: 126 + teleporter: [65, 0] + access: [] +- name: Ice Pyramid 2F North Corridor + id: 60 + game_objects: + - name: "Ice Pyramid 2F - North Corridor Glass Door Box" + object_id: 0x59 + type: "Box" + access: ["Sword"] + links: + - target_room: 55 + entrance: 127 + teleporter: [66, 0] + access: [] + - target_room: 62 + entrance: 128 + teleporter: [68, 0] + access: [] +- name: Ice Pyramid 3F Two Boxes Room + id: 61 + game_objects: + - name: "Ice Pyramid 3F - Staircase Dead End Left Box" + object_id: 0x5E + type: "Box" + access: [] + - name: "Ice Pyramid 3F - Staircase Dead End Right Box" + object_id: 0x5F + type: "Box" + access: [] + links: + - target_room: 56 + entrance: 129 + teleporter: [69, 0] + access: [] +- name: Ice Pyramid 3F Main Loop + id: 62 + game_objects: + - name: "Ice Pyramid 3F - Inner Room North Box" + object_id: 0x5C + type: "Box" + access: [] + - name: "Ice Pyramid 3F - Inner Room South Box" + object_id: 0x5D + type: "Box" + access: [] + - name: "Ice Pyramid 3F - East Alcove Box" + object_id: 0x60 + type: "Box" + access: [] + - name: "Ice Pyramid 3F - Leapfrog Box" + object_id: 0x61 + type: "Box" + access: [] + - name: "Ice Pyramid 3F Statue" + object_id: 0 + type: "Trigger" + on_trigger: ["IcePyramid3FStatue"] + access: ["Sword"] + links: + - target_room: 60 + entrance: 130 + teleporter: [70, 0] + access: [] + - target_room: 63 + access: ["IcePyramid3FStatue"] +- name: Ice Pyramid 3F Blocked Room + id: 63 + game_objects: [] + links: + - target_room: 64 + entrance: 131 + teleporter: [71, 0] + access: [] + - target_room: 62 + access: ["IcePyramid3FStatue"] +- name: Ice Pyramid 4F Main Loop + id: 64 + game_objects: [] + links: + - target_room: 66 + entrance: 133 + teleporter: [73, 0] + access: [] + - target_room: 63 + entrance: 132 + teleporter: [72, 0] + access: [] + - target_room: 65 + access: ["IcePyramid4FStatue"] +- name: Ice Pyramid 4F Treasure Room + id: 65 + game_objects: + - name: "Ice Pyramid 4F - Chest" + object_id: 0x0C + type: "Chest" + access: [] + - name: "Ice Pyramid 4F - Northwest Box" + object_id: 0x62 + type: "Box" + access: [] + - name: "Ice Pyramid 4F - West Left Box" + object_id: 0x63 + type: "Box" + access: [] + - name: "Ice Pyramid 4F - West Right Box" + object_id: 0x64 + type: "Box" + access: [] + - name: "Ice Pyramid 4F - South Left Box" + object_id: 0x65 + type: "Box" + access: [] + - name: "Ice Pyramid 4F - South Right Box" + object_id: 0x66 + type: "Box" + access: [] + - name: "Ice Pyramid 4F - East Left Box" + object_id: 0x67 + type: "Box" + access: [] + - name: "Ice Pyramid 4F - East Right Box" + object_id: 0x68 + type: "Box" + access: [] + - name: "Ice Pyramid 4F Statue" + object_id: 0 + type: "Trigger" + on_trigger: ["IcePyramid4FStatue"] + access: ["Sword"] + links: + - target_room: 64 + access: ["IcePyramid4FStatue"] +- name: Ice Pyramid 5F Leap of Faith Room + id: 66 + game_objects: + - name: "Ice Pyramid 5F - Glass Door Left Box" + object_id: 0x69 + type: "Box" + access: ["IcePyramid5FStatue"] + - name: "Ice Pyramid 5F - West Ledge Box" + object_id: 0x6A + type: "Box" + access: [] + - name: "Ice Pyramid 5F - South Shelf Box" + object_id: 0x6B + type: "Box" + access: [] + - name: "Ice Pyramid 5F - South Leapfrog Box" + object_id: 0x6C + type: "Box" + access: [] + - name: "Ice Pyramid 5F - Glass Door Right Box" + object_id: 0x6D + type: "Box" + access: ["IcePyramid5FStatue"] + - name: "Ice Pyramid 5F - North Box" + object_id: 0x6E + type: "Box" + access: [] + links: + - target_room: 64 + entrance: 134 + teleporter: [74, 0] + access: [] + - target_room: 65 + access: [] + - target_room: 53 + access: ["Bomb", "Claw", "Sword"] +- name: Ice Pyramid 5F Stairs to Ice Golem + id: 67 + game_objects: + - name: "Ice Pyramid 5F Statue" + object_id: 0 + type: "Trigger" + on_trigger: ["IcePyramid5FStatue"] + access: ["Sword"] + links: + - target_room: 69 + entrance: 137 + teleporter: [76, 0] + access: [] + - target_room: 65 + access: [] + - target_room: 70 + entrance: 136 + teleporter: [75, 0] + access: [] +- name: Ice Pyramid Climbing Wall Room Lower Space + id: 68 + game_objects: [] + links: + - target_room: 53 + entrance: 139 + teleporter: [78, 0] + access: [] + - target_room: 69 + access: ["Claw"] +- name: Ice Pyramid Climbing Wall Room Upper Space + id: 69 + game_objects: [] + links: + - target_room: 67 + entrance: 140 + teleporter: [79, 0] + access: [] + - target_room: 68 + access: ["Claw"] +- name: Ice Pyramid Ice Golem Room + id: 70 + game_objects: + - name: "Ice Pyramid 6F - Ice Golem Chest" + object_id: 0x0E + type: "Chest" + access: ["IceGolem"] + - name: "Ice Golem" + object_id: 0 + type: "Trigger" + on_trigger: ["IceGolem"] + access: [] + links: + - target_room: 67 + entrance: 141 + teleporter: [80, 0] + access: [] + - target_room: 66 + access: [] +- name: Spencer Waterfall + id: 71 + game_objects: [] + links: + - target_room: 72 + entrance: 143 + teleporter: [81, 0] + access: [] + - target_room: 40 + entrance: 145 + teleporter: [82, 0] + access: [] + - target_room: 40 + entrance: 148 + teleporter: [83, 0] + access: [] +- name: Spencer Cave Normal Main + id: 72 + game_objects: + - name: "Spencer's Cave - Box" + object_id: 0x6F + type: "Box" + access: ["Claw"] + - name: "Spencer's Cave - Spencer" + object_id: 8 + type: "NPC" + access: [] + - name: "Spencer's Cave - Locked Chest" + object_id: 13 + type: "NPC" + access: ["VenusKey"] + links: + - target_room: 71 + entrance: 150 + teleporter: [85, 0] + access: [] +- name: Spencer Cave Normal South Ledge + id: 73 + game_objects: + - name: "Collapse Spencer's Cave" + object_id: 0 + type: "Trigger" + on_trigger: ["ShipLiberated"] + access: ["MegaGrenade"] + links: + - target_room: 227 + entrance: 151 + teleporter: [7, 6] + access: [] + - target_room: 203 + access: ["MegaGrenade"] +# - target_room: 72 # access to spencer? +# access: ["MegaGrenade"] +- name: Spencer Cave Caved In Main Loop + id: 203 + game_objects: [] + links: + - target_room: 73 + access: [] + - target_room: 207 + entrance: 156 + teleporter: [36, 8] + access: ["MobiusCrest"] + - target_room: 204 + access: ["Claw"] + - target_room: 205 + access: ["Bomb"] +- name: Spencer Cave Caved In Waters + id: 204 + game_objects: + - name: "Bomb Libra Block" + object_id: 0 + type: "Trigger" + on_trigger: ["SpencerCaveLibraBlockBombed"] + access: ["MegaGrenade", "Claw"] + links: + - target_room: 203 + access: ["Claw"] +- name: Spencer Cave Caved In Libra Nook + id: 205 + game_objects: [] + links: + - target_room: 206 + entrance: 153 + teleporter: [33, 8] + access: ["LibraCrest"] +- name: Spencer Cave Caved In Libra Corridor + id: 206 + game_objects: [] + links: + - target_room: 205 + entrance: 154 + teleporter: [34, 8] + access: ["LibraCrest"] + - target_room: 207 + access: ["SpencerCaveLibraBlockBombed"] +- name: Spencer Cave Caved In Mobius Chest + id: 207 + game_objects: + - name: "Spencer's Cave - Mobius Chest" + object_id: 0x0F + type: "Chest" + access: [] + links: + - target_room: 203 + entrance: 155 + teleporter: [35, 8] + access: ["MobiusCrest"] + - target_room: 206 + access: ["Bomb"] +- name: Wintry Temple Outer Room + id: 74 + game_objects: [] + links: + - target_room: 223 + entrance: 157 + teleporter: [15, 6] + access: [] +- name: Wintry Temple Inner Room + id: 75 + game_objects: + - name: "Wintry Temple - West Box" + object_id: 0x70 + type: "Box" + access: [] + - name: "Wintry Temple - North Box" + object_id: 0x71 + type: "Box" + access: [] + links: + - target_room: 92 + entrance: 158 + teleporter: [62, 8] + access: ["GeminiCrest"] +- name: Fireburg Upper Plaza + id: 76 + game_objects: [] + links: + - target_room: 224 + entrance: 159 + teleporter: [9, 6] + access: [] + - target_room: 80 + entrance: 163 + teleporter: [91, 0] + access: [] + - target_room: 77 + entrance: 164 + teleporter: [16, 2] + access: [] + - target_room: 82 + entrance: 165 + teleporter: [17, 2] + access: [] + - target_room: 208 + access: ["Claw"] +- name: Fireburg Lower Plaza + id: 208 + game_objects: + - name: "Fireburg - Hidden Tunnel Box" + object_id: 0x74 + type: "Box" + access: [] + links: + - target_room: 76 + access: ["Claw"] + - target_room: 78 + entrance: 166 + teleporter: [11, 8] + access: ["MultiKey"] +- name: Reuben's House + id: 77 + game_objects: + - name: "Fireburg - Reuben's House Arion" + object_id: 14 + type: "NPC" + access: ["ReubenDadSaved"] + - name: "Reuben" + object_id: 0 + type: "Trigger" + on_trigger: ["Reuben1"] + access: [] + - name: "Fireburg - Reuben's House Box" + object_id: 0x75 + type: "Box" + access: [] + links: + - target_room: 76 + entrance: 167 + teleporter: [98, 3] + access: [] +- name: GrenadeMan's House + id: 78 + game_objects: + - name: "Fireburg - Locked House Man" + object_id: 12 + type: "NPC" + access: [] + links: + - target_room: 208 + entrance: 168 + teleporter: [9, 8] + access: ["MultiKey"] + - target_room: 79 + entrance: 169 + teleporter: [93, 0] + access: [] +- name: GrenadeMan's Mobius Room + id: 79 + game_objects: [] + links: + - target_room: 78 + entrance: 170 + teleporter: [94, 0] + access: [] + - target_room: 161 + entrance: 171 + teleporter: [54, 8] + access: ["MobiusCrest"] +- name: Fireburg Vendor House + id: 80 + game_objects: + - name: "Fireburg - Vendor" + object_id: 11 + type: "NPC" + access: [] + links: + - target_room: 76 + entrance: 172 + teleporter: [95, 0] + access: [] + - target_room: 81 + entrance: 173 + teleporter: [96, 0] + access: [] +- name: Fireburg Gemini Room + id: 81 + game_objects: [] + links: + - target_room: 80 + entrance: 174 + teleporter: [97, 0] + access: [] + - target_room: 43 + entrance: 175 + teleporter: [45, 8] + access: ["GeminiCrest"] +- name: Fireburg Hotel Lobby + id: 82 + game_objects: + - name: "Fireburg - Tristam" + object_id: 10 + type: "NPC" + access: [] + - name: "Tristam Fireburg" + object_id: 0 + type: "Trigger" + on_trigger: ["Tristam"] + access: [] + links: + - target_room: 76 + entrance: 177 + teleporter: [99, 3] + access: [] + - target_room: 83 + entrance: 176 + teleporter: [213, 0] + access: [] +- name: Fireburg Hotel Beds + id: 83 + game_objects: [] + links: + - target_room: 82 + entrance: 178 + teleporter: [214, 0] + access: [] +- name: Mine Exterior North West Platforms + id: 84 + game_objects: [] + links: + - target_room: 224 + entrance: 179 + teleporter: [98, 0] + access: [] + - target_room: 88 + entrance: 181 + teleporter: [20, 2] + access: ["Bomb"] + - target_room: 85 + access: ["Claw"] + - target_room: 86 + access: ["Claw"] + - target_room: 87 + access: ["Claw"] +- name: Mine Exterior Central Ledge + id: 85 + game_objects: [] + links: + - target_room: 90 + entrance: 183 + teleporter: [22, 2] + access: ["Bomb"] + - target_room: 84 + access: ["Claw"] +- name: Mine Exterior North Ledge + id: 86 + game_objects: [] + links: + - target_room: 89 + entrance: 182 + teleporter: [21, 2] + access: ["Bomb"] + - target_room: 85 + access: ["Claw"] +- name: Mine Exterior South East Platforms + id: 87 + game_objects: + - name: "Jinn" + object_id: 0 + type: "Trigger" + on_trigger: ["Jinn"] + access: [] + links: + - target_room: 91 + entrance: 180 + teleporter: [99, 0] + access: ["Jinn"] + - target_room: 86 + access: [] + - target_room: 85 + access: ["Claw"] +- name: Mine Parallel Room + id: 88 + game_objects: + - name: "Mine - Parallel Room West Box" + object_id: 0x77 + type: "Box" + access: ["Claw"] + - name: "Mine - Parallel Room East Box" + object_id: 0x78 + type: "Box" + access: ["Claw"] + links: + - target_room: 84 + entrance: 185 + teleporter: [100, 3] + access: [] +- name: Mine Crescent Room + id: 89 + game_objects: + - name: "Mine - Crescent Room Chest" + object_id: 0x10 + type: "Chest" + access: [] + links: + - target_room: 86 + entrance: 186 + teleporter: [101, 3] + access: [] +- name: Mine Climbing Room + id: 90 + game_objects: + - name: "Mine - Glitchy Collision Cave Box" + object_id: 0x76 + type: "Box" + access: ["Claw"] + links: + - target_room: 85 + entrance: 187 + teleporter: [102, 3] + access: [] +- name: Mine Cliff + id: 91 + game_objects: + - name: "Mine - Cliff Southwest Box" + object_id: 0x79 + type: "Box" + access: [] + - name: "Mine - Cliff Northwest Box" + object_id: 0x7A + type: "Box" + access: [] + - name: "Mine - Cliff Northeast Box" + object_id: 0x7B + type: "Box" + access: [] + - name: "Mine - Cliff Southeast Box" + object_id: 0x7C + type: "Box" + access: [] + - name: "Mine - Reuben" + object_id: 7 + type: "NPC" + access: ["Reuben1"] + - name: "Reuben's dad Saved" + object_id: 0 + type: "Trigger" + on_trigger: ["ReubenDadSaved"] + access: ["MegaGrenade"] + links: + - target_room: 87 + entrance: 188 + teleporter: [100, 0] + access: [] +- name: Sealed Temple + id: 92 + game_objects: + - name: "Sealed Temple - West Box" + object_id: 0x7D + type: "Box" + access: [] + - name: "Sealed Temple - East Box" + object_id: 0x7E + type: "Box" + access: [] + links: + - target_room: 224 + entrance: 190 + teleporter: [16, 6] + access: [] + - target_room: 75 + entrance: 191 + teleporter: [63, 8] + access: ["GeminiCrest"] +- name: Volcano Base + id: 93 + game_objects: + - name: "Volcano - Base Chest" + object_id: 0x11 + type: "Chest" + access: [] + - name: "Volcano - Base West Box" + object_id: 0x7F + type: "Box" + access: [] + - name: "Volcano - Base East Left Box" + object_id: 0x80 + type: "Box" + access: [] + - name: "Volcano - Base East Right Box" + object_id: 0x81 + type: "Box" + access: [] + links: + - target_room: 224 + entrance: 192 + teleporter: [103, 0] + access: [] + - target_room: 98 + entrance: 196 + teleporter: [31, 8] + access: [] + - target_room: 96 + entrance: 197 + teleporter: [30, 8] + access: [] +- name: Volcano Top Left + id: 94 + game_objects: + - name: "Volcano - Medusa Chest" + object_id: 0x12 + type: "Chest" + access: ["Medusa"] + - name: "Medusa" + object_id: 0 + type: "Trigger" + on_trigger: ["Medusa"] + access: [] + - name: "Volcano - Behind Medusa Box" + object_id: 0x82 + type: "Box" + access: [] + links: + - target_room: 209 + entrance: 199 + teleporter: [26, 8] + access: [] +- name: Volcano Top Right + id: 95 + game_objects: + - name: "Volcano - Top of the Volcano Left Box" + object_id: 0x83 + type: "Box" + access: [] + - name: "Volcano - Top of the Volcano Right Box" + object_id: 0x84 + type: "Box" + access: [] + links: + - target_room: 99 + entrance: 200 + teleporter: [79, 8] + access: [] +- name: Volcano Right Path + id: 96 + game_objects: + - name: "Volcano - Right Path Box" + object_id: 0x87 + type: "Box" + access: [] + links: + - target_room: 93 + entrance: 201 + teleporter: [15, 8] + access: [] +- name: Volcano Left Path + id: 98 + game_objects: + - name: "Volcano - Left Path Box" + object_id: 0x86 + type: "Box" + access: [] + links: + - target_room: 93 + entrance: 204 + teleporter: [27, 8] + access: [] + - target_room: 99 + entrance: 202 + teleporter: [25, 2] + access: [] + - target_room: 209 + entrance: 203 + teleporter: [26, 2] + access: [] +- name: Volcano Cross Left-Right + id: 99 + game_objects: [] + links: + - target_room: 95 + entrance: 206 + teleporter: [29, 8] + access: [] + - target_room: 98 + entrance: 205 + teleporter: [103, 3] + access: [] +- name: Volcano Cross Right-Left + id: 209 + game_objects: + - name: "Volcano - Crossover Section Box" + object_id: 0x85 + type: "Box" + access: [] + links: + - target_room: 98 + entrance: 208 + teleporter: [104, 3] + access: [] + - target_room: 94 + entrance: 207 + teleporter: [28, 8] + access: [] +- name: Lava Dome Inner Ring Main Loop + id: 100 + game_objects: + - name: "Lava Dome - Exterior Caldera Near Switch Cliff Box" + object_id: 0x88 + type: "Box" + access: [] + - name: "Lava Dome - Exterior South Cliff Box" + object_id: 0x89 + type: "Box" + access: [] + links: + - target_room: 224 + entrance: 209 + teleporter: [104, 0] + access: [] + - target_room: 113 + entrance: 211 + teleporter: [105, 0] + access: [] + - target_room: 114 + entrance: 212 + teleporter: [106, 0] + access: [] + - target_room: 116 + entrance: 213 + teleporter: [108, 0] + access: [] + - target_room: 118 + entrance: 214 + teleporter: [111, 0] + access: [] +- name: Lava Dome Inner Ring Center Ledge + id: 101 + game_objects: + - name: "Lava Dome - Exterior Center Dropoff Ledge Box" + object_id: 0x8A + type: "Box" + access: [] + links: + - target_room: 115 + entrance: 215 + teleporter: [107, 0] + access: [] + - target_room: 100 + access: ["Claw"] +- name: Lava Dome Inner Ring Plate Ledge + id: 102 + game_objects: + - name: "Lava Dome Plate" + object_id: 0 + type: "Trigger" + on_trigger: ["LavaDomePlate"] + access: [] + links: + - target_room: 119 + entrance: 216 + teleporter: [109, 0] + access: [] +- name: Lava Dome Inner Ring Upper Ledge West + id: 103 + game_objects: [] + links: + - target_room: 111 + entrance: 219 + teleporter: [112, 0] + access: [] + - target_room: 108 + entrance: 220 + teleporter: [113, 0] + access: [] + - target_room: 104 + access: ["Claw"] + - target_room: 100 + access: ["Claw"] +- name: Lava Dome Inner Ring Upper Ledge East + id: 104 + game_objects: [] + links: + - target_room: 110 + entrance: 218 + teleporter: [110, 0] + access: [] + - target_room: 103 + access: ["Claw"] +- name: Lava Dome Inner Ring Big Door Ledge + id: 105 + game_objects: [] + links: + - target_room: 107 + entrance: 221 + teleporter: [114, 0] + access: [] + - target_room: 121 + entrance: 222 + teleporter: [29, 2] + access: ["LavaDomePlate"] +- name: Lava Dome Inner Ring Tiny Bottom Ledge + id: 106 + game_objects: + - name: "Lava Dome - Exterior Dead End Caldera Box" + object_id: 0x8B + type: "Box" + access: [] + links: + - target_room: 120 + entrance: 226 + teleporter: [115, 0] + access: [] +- name: Lava Dome Jump Maze II + id: 107 + game_objects: + - name: "Lava Dome - Gold Maze Northwest Box" + object_id: 0x8C + type: "Box" + access: [] + - name: "Lava Dome - Gold Maze Southwest Box" + object_id: 0xF6 + type: "Box" + access: [] + - name: "Lava Dome - Gold Maze Northeast Box" + object_id: 0xF7 + type: "Box" + access: [] + - name: "Lava Dome - Gold Maze North Box" + object_id: 0xF8 + type: "Box" + access: [] + - name: "Lava Dome - Gold Maze Center Box" + object_id: 0xF9 + type: "Box" + access: [] + - name: "Lava Dome - Gold Maze Southeast Box" + object_id: 0xFA + type: "Box" + access: [] + links: + - target_room: 105 + entrance: 227 + teleporter: [116, 0] + access: [] + - target_room: 108 + entrance: 228 + teleporter: [119, 0] + access: [] + - target_room: 120 + entrance: 229 + teleporter: [120, 0] + access: [] +- name: Lava Dome Up-Down Corridor + id: 108 + game_objects: [] + links: + - target_room: 107 + entrance: 231 + teleporter: [118, 0] + access: [] + - target_room: 103 + entrance: 230 + teleporter: [117, 0] + access: [] +- name: Lava Dome Jump Maze I + id: 109 + game_objects: + - name: "Lava Dome - Bare Maze Leapfrog Alcove North Box" + object_id: 0x8D + type: "Box" + access: [] + - name: "Lava Dome - Bare Maze Leapfrog Alcove South Box" + object_id: 0x8E + type: "Box" + access: [] + - name: "Lava Dome - Bare Maze Center Box" + object_id: 0x8F + type: "Box" + access: [] + - name: "Lava Dome - Bare Maze Southwest Box" + object_id: 0x90 + type: "Box" + access: [] + links: + - target_room: 118 + entrance: 232 + teleporter: [121, 0] + access: [] + - target_room: 111 + entrance: 233 + teleporter: [122, 0] + access: [] +- name: Lava Dome Pointless Room + id: 110 + game_objects: [] + links: + - target_room: 104 + entrance: 234 + teleporter: [123, 0] + access: [] +- name: Lava Dome Lower Moon Helm Room + id: 111 + game_objects: + - name: "Lava Dome - U-Bend Room North Box" + object_id: 0x92 + type: "Box" + access: [] + - name: "Lava Dome - U-Bend Room South Box" + object_id: 0x93 + type: "Box" + access: [] + links: + - target_room: 103 + entrance: 235 + teleporter: [124, 0] + access: [] + - target_room: 109 + entrance: 236 + teleporter: [125, 0] + access: [] +- name: Lava Dome Moon Helm Room + id: 112 + game_objects: + - name: "Lava Dome - Beyond River Room Chest" + object_id: 0x13 + type: "Chest" + access: [] + - name: "Lava Dome - Beyond River Room Box" + object_id: 0x91 + type: "Box" + access: [] + links: + - target_room: 117 + entrance: 237 + teleporter: [126, 0] + access: [] +- name: Lava Dome Three Jumps Room + id: 113 + game_objects: + - name: "Lava Dome - Three Jumps Room Box" + object_id: 0x96 + type: "Box" + access: [] + links: + - target_room: 100 + entrance: 238 + teleporter: [127, 0] + access: [] +- name: Lava Dome Life Chest Room Lower Ledge + id: 114 + game_objects: + - name: "Lava Dome - Gold Bar Room Boulder Chest" + object_id: 0x1C + type: "Chest" + access: ["MegaGrenade"] + links: + - target_room: 100 + entrance: 239 + teleporter: [128, 0] + access: [] + - target_room: 115 + access: ["Claw"] +- name: Lava Dome Life Chest Room Upper Ledge + id: 115 + game_objects: + - name: "Lava Dome - Gold Bar Room Leapfrog Alcove Box West" + object_id: 0x94 + type: "Box" + access: [] + - name: "Lava Dome - Gold Bar Room Leapfrog Alcove Box East" + object_id: 0x95 + type: "Box" + access: [] + links: + - target_room: 101 + entrance: 240 + teleporter: [129, 0] + access: [] + - target_room: 114 + access: ["Claw"] +- name: Lava Dome Big Jump Room Main Area + id: 116 + game_objects: + - name: "Lava Dome - Lava River Room North Box" + object_id: 0x98 + type: "Box" + access: [] + - name: "Lava Dome - Lava River Room East Box" + object_id: 0x99 + type: "Box" + access: [] + - name: "Lava Dome - Lava River Room South Box" + object_id: 0x9A + type: "Box" + access: [] + links: + - target_room: 100 + entrance: 241 + teleporter: [133, 0] + access: [] + - target_room: 119 + entrance: 243 + teleporter: [132, 0] + access: [] + - target_room: 117 + access: ["MegaGrenade"] +- name: Lava Dome Big Jump Room MegaGrenade Area + id: 117 + game_objects: [] + links: + - target_room: 112 + entrance: 242 + teleporter: [131, 0] + access: [] + - target_room: 116 + access: ["Bomb"] +- name: Lava Dome Split Corridor + id: 118 + game_objects: + - name: "Lava Dome - Split Corridor Box" + object_id: 0x97 + type: "Box" + access: [] + links: + - target_room: 109 + entrance: 244 + teleporter: [130, 0] + access: [] + - target_room: 100 + entrance: 245 + teleporter: [134, 0] + access: [] +- name: Lava Dome Plate Corridor + id: 119 + game_objects: [] + links: + - target_room: 102 + entrance: 246 + teleporter: [135, 0] + access: [] + - target_room: 116 + entrance: 247 + teleporter: [137, 0] + access: [] +- name: Lava Dome Four Boxes Stairs + id: 120 + game_objects: + - name: "Lava Dome - Caldera Stairway West Left Box" + object_id: 0x9B + type: "Box" + access: [] + - name: "Lava Dome - Caldera Stairway West Right Box" + object_id: 0x9C + type: "Box" + access: [] + - name: "Lava Dome - Caldera Stairway East Left Box" + object_id: 0x9D + type: "Box" + access: [] + - name: "Lava Dome - Caldera Stairway East Right Box" + object_id: 0x9E + type: "Box" + access: [] + links: + - target_room: 107 + entrance: 248 + teleporter: [136, 0] + access: [] + - target_room: 106 + entrance: 249 + teleporter: [16, 0] + access: [] +- name: Lava Dome Hydra Room + id: 121 + game_objects: + - name: "Lava Dome - Dualhead Hydra Chest" + object_id: 0x14 + type: "Chest" + access: ["DualheadHydra"] + - name: "Dualhead Hydra" + object_id: 0 + type: "Trigger" + on_trigger: ["DualheadHydra"] + access: [] + - name: "Lava Dome - Hydra Room Northwest Box" + object_id: 0x9F + type: "Box" + access: [] + - name: "Lava Dome - Hydra Room Southweast Box" + object_id: 0xA0 + type: "Box" + access: [] + links: + - target_room: 105 + entrance: 250 + teleporter: [105, 3] + access: [] + - target_room: 122 + entrance: 251 + teleporter: [138, 0] + access: ["DualheadHydra"] +- name: Lava Dome Escape Corridor + id: 122 + game_objects: [] + links: + - target_room: 121 + entrance: 253 + teleporter: [139, 0] + access: [] +- name: Rope Bridge + id: 123 + game_objects: + - name: "Rope Bridge - West Box" + object_id: 0xA3 + type: "Box" + access: [] + - name: "Rope Bridge - East Box" + object_id: 0xA4 + type: "Box" + access: [] + links: + - target_room: 226 + entrance: 255 + teleporter: [140, 0] + access: [] +- name: Alive Forest + id: 124 + game_objects: + - name: "Alive Forest - Tree Stump Chest" + object_id: 0x15 + type: "Chest" + access: ["Axe"] + - name: "Alive Forest - Near Entrance Box" + object_id: 0xA5 + type: "Box" + access: ["Axe"] + - name: "Alive Forest - After Bridge Box" + object_id: 0xA6 + type: "Box" + access: ["Axe"] + - name: "Alive Forest - Gemini Stump Box" + object_id: 0xA7 + type: "Box" + access: ["Axe"] + links: + - target_room: 226 + entrance: 272 + teleporter: [142, 0] + access: ["Axe"] + - target_room: 21 + entrance: 275 + teleporter: [64, 8] + access: ["LibraCrest", "Axe"] + - target_room: 22 + entrance: 276 + teleporter: [65, 8] + access: ["GeminiCrest", "Axe"] + - target_room: 23 + entrance: 277 + teleporter: [66, 8] + access: ["MobiusCrest", "Axe"] + - target_room: 125 + entrance: 274 + teleporter: [143, 0] + access: ["Axe"] +- name: Giant Tree 1F Main Area + id: 125 + game_objects: + - name: "Giant Tree 1F - Northwest Box" + object_id: 0xA8 + type: "Box" + access: [] + - name: "Giant Tree 1F - Southwest Box" + object_id: 0xA9 + type: "Box" + access: [] + - name: "Giant Tree 1F - Center Box" + object_id: 0xAA + type: "Box" + access: [] + - name: "Giant Tree 1F - East Box" + object_id: 0xAB + type: "Box" + access: [] + links: + - target_room: 124 + entrance: 278 + teleporter: [56, 1] # [49, 8] script restored if no map shuffling + access: [] + - target_room: 202 + access: ["DragonClaw"] +- name: Giant Tree 1F North Island + id: 202 + game_objects: [] + links: + - target_room: 127 + entrance: 280 + teleporter: [144, 0] + access: [] + - target_room: 125 + access: ["DragonClaw"] +- name: Giant Tree 1F Central Island + id: 126 + game_objects: [] + links: + - target_room: 202 + access: ["DragonClaw"] +- name: Giant Tree 2F Main Lobby + id: 127 + game_objects: + - name: "Giant Tree 2F - North Box" + object_id: 0xAC + type: "Box" + access: [] + links: + - target_room: 126 + access: ["DragonClaw"] + - target_room: 125 + entrance: 281 + teleporter: [145, 0] + access: [] + - target_room: 133 + entrance: 283 + teleporter: [149, 0] + access: [] + - target_room: 129 + access: ["DragonClaw"] +- name: Giant Tree 2F West Ledge + id: 128 + game_objects: + - name: "Giant Tree 2F - Dropdown Ledge Box" + object_id: 0xAE + type: "Box" + access: [] + links: + - target_room: 140 + entrance: 284 + teleporter: [147, 0] + access: ["Sword"] + - target_room: 130 + access: ["DragonClaw"] +- name: Giant Tree 2F Lower Area + id: 129 + game_objects: + - name: "Giant Tree 2F - South Box" + object_id: 0xAD + type: "Box" + access: [] + links: + - target_room: 130 + access: ["Claw"] + - target_room: 131 + access: ["Claw"] +- name: Giant Tree 2F Central Island + id: 130 + game_objects: [] + links: + - target_room: 129 + access: ["Claw"] + - target_room: 135 + entrance: 282 + teleporter: [146, 0] + access: ["Sword"] +- name: Giant Tree 2F East Ledge + id: 131 + game_objects: [] + links: + - target_room: 129 + access: ["Claw"] + - target_room: 130 + access: ["DragonClaw"] +- name: Giant Tree 2F Meteor Chest Room + id: 132 + game_objects: + - name: "Giant Tree 2F - Gidrah Chest" + object_id: 0x16 + type: "Chest" + access: [] + links: + - target_room: 133 + entrance: 285 + teleporter: [148, 0] + access: [] +- name: Giant Tree 2F Mushroom Room + id: 133 + game_objects: + - name: "Giant Tree 2F - Mushroom Tunnel West Box" + object_id: 0xAF + type: "Box" + access: ["Axe"] + - name: "Giant Tree 2F - Mushroom Tunnel East Box" + object_id: 0xB0 + type: "Box" + access: ["Axe"] + links: + - target_room: 127 + entrance: 286 + teleporter: [150, 0] + access: ["Axe"] + - target_room: 132 + entrance: 287 + teleporter: [151, 0] + access: ["Axe", "Gidrah"] +- name: Giant Tree 3F Central Island + id: 135 + game_objects: + - name: "Giant Tree 3F - Central Island Box" + object_id: 0xB3 + type: "Box" + access: [] + links: + - target_room: 130 + entrance: 288 + teleporter: [152, 0] + access: [] + - target_room: 136 + access: ["Claw"] + - target_room: 137 + access: ["DragonClaw"] +- name: Giant Tree 3F Central Area + id: 136 + game_objects: + - name: "Giant Tree 3F - Center North Box" + object_id: 0xB1 + type: "Box" + access: [] + - name: "Giant Tree 3F - Center West Box" + object_id: 0xB2 + type: "Box" + access: [] + links: + - target_room: 135 + access: ["Claw"] + - target_room: 127 + access: [] + - target_room: 131 + access: [] +- name: Giant Tree 3F Lower Ledge + id: 137 + game_objects: [] + links: + - target_room: 135 + access: ["DragonClaw"] + - target_room: 142 + entrance: 289 + teleporter: [153, 0] + access: ["Sword"] +- name: Giant Tree 3F West Area + id: 138 + game_objects: + - name: "Giant Tree 3F - West Side Box" + object_id: 0xB4 + type: "Box" + access: [] + links: + - target_room: 128 + access: [] + - target_room: 210 + entrance: 290 + teleporter: [154, 0] + access: [] +- name: Giant Tree 3F Middle Up Island + id: 139 + game_objects: [] + links: + - target_room: 136 + access: ["Claw"] +- name: Giant Tree 3F West Platform + id: 140 + game_objects: [] + links: + - target_room: 139 + access: ["Claw"] + - target_room: 141 + access: ["Claw"] + - target_room: 128 + entrance: 291 + teleporter: [155, 0] + access: [] +- name: Giant Tree 3F North Ledge + id: 141 + game_objects: [] + links: + - target_room: 143 + entrance: 292 + teleporter: [156, 0] + access: ["Sword"] + - target_room: 139 + access: ["Claw"] + - target_room: 136 + access: ["Claw"] +- name: Giant Tree Worm Room Upper Ledge + id: 142 + game_objects: + - name: "Giant Tree 3F - Worm Room North Box" + object_id: 0xB5 + type: "Box" + access: ["Axe"] + - name: "Giant Tree 3F - Worm Room South Box" + object_id: 0xB6 + type: "Box" + access: ["Axe"] + links: + - target_room: 137 + entrance: 293 + teleporter: [157, 0] + access: ["Axe"] + - target_room: 210 + access: ["Axe", "Claw"] +- name: Giant Tree Worm Room Lower Ledge + id: 210 + game_objects: [] + links: + - target_room: 138 + entrance: 294 + teleporter: [158, 0] + access: [] +- name: Giant Tree 4F Lower Floor + id: 143 + game_objects: [] + links: + - target_room: 141 + entrance: 295 + teleporter: [159, 0] + access: [] + - target_room: 148 + entrance: 296 + teleporter: [160, 0] + access: [] + - target_room: 148 + entrance: 297 + teleporter: [161, 0] + access: [] + - target_room: 147 + entrance: 298 + teleporter: [162, 0] + access: ["Sword"] +- name: Giant Tree 4F Middle Floor + id: 144 + game_objects: + - name: "Giant Tree 4F - Highest Platform North Box" + object_id: 0xB7 + type: "Box" + access: [] + - name: "Giant Tree 4F - Highest Platform South Box" + object_id: 0xB8 + type: "Box" + access: [] + links: + - target_room: 149 + entrance: 299 + teleporter: [163, 0] + access: [] + - target_room: 145 + access: ["Claw"] + - target_room: 146 + access: ["DragonClaw"] +- name: Giant Tree 4F Upper Floor + id: 145 + game_objects: [] + links: + - target_room: 150 + entrance: 300 + teleporter: [164, 0] + access: ["Sword"] + - target_room: 144 + access: ["Claw"] +- name: Giant Tree 4F South Ledge + id: 146 + game_objects: + - name: "Giant Tree 4F - Hook Ledge Northeast Box" + object_id: 0xB9 + type: "Box" + access: [] + - name: "Giant Tree 4F - Hook Ledge Southwest Box" + object_id: 0xBA + type: "Box" + access: [] + links: + - target_room: 144 + access: ["DragonClaw"] +- name: Giant Tree 4F Slime Room East Area + id: 147 + game_objects: + - name: "Giant Tree 4F - East Slime Room Box" + object_id: 0xBC + type: "Box" + access: ["Axe"] + links: + - target_room: 143 + entrance: 304 + teleporter: [168, 0] + access: [] +- name: Giant Tree 4F Slime Room West Area + id: 148 + game_objects: [] + links: + - target_room: 143 + entrance: 303 + teleporter: [167, 0] + access: ["Axe"] + - target_room: 143 + entrance: 302 + teleporter: [166, 0] + access: ["Axe"] + - target_room: 149 + access: ["Axe", "Claw"] +- name: Giant Tree 4F Slime Room Platform + id: 149 + game_objects: + - name: "Giant Tree 4F - West Slime Room Box" + object_id: 0xBB + type: "Box" + access: [] + links: + - target_room: 144 + entrance: 301 + teleporter: [165, 0] + access: [] + - target_room: 148 + access: ["Claw"] +- name: Giant Tree 5F Lower Area + id: 150 + game_objects: + - name: "Giant Tree 5F - Northwest Left Box" + object_id: 0xBD + type: "Box" + access: [] + - name: "Giant Tree 5F - Northwest Right Box" + object_id: 0xBE + type: "Box" + access: [] + - name: "Giant Tree 5F - South Left Box" + object_id: 0xBF + type: "Box" + access: [] + - name: "Giant Tree 5F - South Right Box" + object_id: 0xC0 + type: "Box" + access: [] + links: + - target_room: 145 + entrance: 305 + teleporter: [169, 0] + access: [] + - target_room: 151 + access: ["Claw"] + - target_room: 143 + access: [] +- name: Giant Tree 5F Gidrah Platform + id: 151 + game_objects: + - name: "Gidrah" + object_id: 0 + type: "Trigger" + on_trigger: ["Gidrah"] + access: [] + links: + - target_room: 150 + access: ["Claw"] +- name: Kaidge Temple Lower Ledge + id: 152 + game_objects: [] + links: + - target_room: 226 + entrance: 307 + teleporter: [18, 6] + access: [] + - target_room: 153 + access: ["Claw"] +- name: Kaidge Temple Upper Ledge + id: 153 + game_objects: + - name: "Kaidge Temple - Box" + object_id: 0xC1 + type: "Box" + access: [] + links: + - target_room: 185 + entrance: 308 + teleporter: [71, 8] + access: ["MobiusCrest"] + - target_room: 152 + access: ["Claw"] +- name: Windhole Temple + id: 154 + game_objects: + - name: "Windhole Temple - Box" + object_id: 0xC2 + type: "Box" + access: [] + links: + - target_room: 226 + entrance: 309 + teleporter: [173, 0] + access: [] +- name: Mount Gale + id: 155 + game_objects: + - name: "Mount Gale - Dullahan Chest" + object_id: 0x17 + type: "Chest" + access: ["DragonClaw", "Dullahan"] + - name: "Dullahan" + object_id: 0 + type: "Trigger" + on_trigger: ["Dullahan"] + access: ["DragonClaw"] + - name: "Mount Gale - East Box" + object_id: 0xC3 + type: "Box" + access: ["DragonClaw"] + - name: "Mount Gale - West Box" + object_id: 0xC4 + type: "Box" + access: [] + links: + - target_room: 226 + entrance: 310 + teleporter: [174, 0] + access: [] +- name: Windia + id: 156 + game_objects: [] + links: + - target_room: 226 + entrance: 312 + teleporter: [10, 6] + access: [] + - target_room: 157 + entrance: 320 + teleporter: [30, 5] + access: [] + - target_room: 163 + entrance: 321 + teleporter: [31, 2] + access: [] + - target_room: 165 + entrance: 322 + teleporter: [32, 5] + access: [] + - target_room: 159 + entrance: 323 + teleporter: [176, 4] + access: [] + - target_room: 160 + entrance: 324 + teleporter: [177, 4] + access: [] +- name: Otto's House + id: 157 + game_objects: + - name: "Otto" + object_id: 0 + type: "Trigger" + on_trigger: ["RainbowBridge"] + access: ["ThunderRock"] + links: + - target_room: 156 + entrance: 327 + teleporter: [106, 3] + access: [] + - target_room: 158 + entrance: 326 + teleporter: [33, 2] + access: [] +- name: Otto's Attic + id: 158 + game_objects: + - name: "Windia - Otto's Attic Box" + object_id: 0xC5 + type: "Box" + access: [] + links: + - target_room: 157 + entrance: 328 + teleporter: [107, 3] + access: [] +- name: Windia Kid House + id: 159 + game_objects: [] + links: + - target_room: 156 + entrance: 329 + teleporter: [178, 0] + access: [] + - target_room: 161 + entrance: 330 + teleporter: [180, 0] + access: [] +- name: Windia Old People House + id: 160 + game_objects: [] + links: + - target_room: 156 + entrance: 331 + teleporter: [179, 0] + access: [] + - target_room: 162 + entrance: 332 + teleporter: [181, 0] + access: [] +- name: Windia Kid House Basement + id: 161 + game_objects: [] + links: + - target_room: 159 + entrance: 333 + teleporter: [182, 0] + access: [] + - target_room: 79 + entrance: 334 + teleporter: [44, 8] + access: ["MobiusCrest"] +- name: Windia Old People House Basement + id: 162 + game_objects: + - name: "Windia - Mobius Basement West Box" + object_id: 0xC8 + type: "Box" + access: [] + - name: "Windia - Mobius Basement East Box" + object_id: 0xC9 + type: "Box" + access: [] + links: + - target_room: 160 + entrance: 335 + teleporter: [183, 0] + access: [] + - target_room: 186 + entrance: 336 + teleporter: [43, 8] + access: ["MobiusCrest"] +- name: Windia Inn Lobby + id: 163 + game_objects: [] + links: + - target_room: 156 + entrance: 338 + teleporter: [135, 3] + access: [] + - target_room: 164 + entrance: 337 + teleporter: [215, 0] + access: [] +- name: Windia Inn Beds + id: 164 + game_objects: + - name: "Windia - Inn Bedroom North Box" + object_id: 0xC6 + type: "Box" + access: [] + - name: "Windia - Inn Bedroom South Box" + object_id: 0xC7 + type: "Box" + access: [] + - name: "Windia - Kaeli" + object_id: 15 + type: "NPC" + access: ["Kaeli2"] + links: + - target_room: 163 + entrance: 339 + teleporter: [216, 0] + access: [] +- name: Windia Vendor House + id: 165 + game_objects: + - name: "Windia - Vendor" + object_id: 16 + type: "NPC" + access: [] + links: + - target_room: 156 + entrance: 340 + teleporter: [108, 3] + access: [] +- name: Pazuzu Tower 1F Main Lobby + id: 166 + game_objects: + - name: "Pazuzu 1F" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu1F"] + access: [] + links: + - target_room: 226 + entrance: 341 + teleporter: [184, 0] + access: [] + - target_room: 180 + entrance: 345 + teleporter: [185, 0] + access: [] +- name: Pazuzu Tower 1F Boxes Room + id: 167 + game_objects: + - name: "Pazuzu's Tower 1F - Descent Bomb Wall West Box" + object_id: 0xCA + type: "Box" + access: ["Bomb"] + - name: "Pazuzu's Tower 1F - Descent Bomb Wall Center Box" + object_id: 0xCB + type: "Box" + access: ["Bomb"] + - name: "Pazuzu's Tower 1F - Descent Bomb Wall East Box" + object_id: 0xCC + type: "Box" + access: ["Bomb"] + - name: "Pazuzu's Tower 1F - Descent Box" + object_id: 0xCD + type: "Box" + access: [] + links: + - target_room: 169 + entrance: 349 + teleporter: [187, 0] + access: [] +- name: Pazuzu Tower 1F Southern Platform + id: 168 + game_objects: [] + links: + - target_room: 169 + entrance: 346 + teleporter: [186, 0] + access: [] + - target_room: 166 + access: ["DragonClaw"] +- name: Pazuzu 2F + id: 169 + game_objects: + - name: "Pazuzu's Tower 2F - East Room West Box" + object_id: 0xCE + type: "Box" + access: [] + - name: "Pazuzu's Tower 2F - East Room East Box" + object_id: 0xCF + type: "Box" + access: [] + - name: "Pazuzu 2F Lock" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu2FLock"] + access: ["Axe"] + - name: "Pazuzu 2F" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu2F"] + access: ["Bomb"] + links: + - target_room: 183 + entrance: 350 + teleporter: [188, 0] + access: [] + - target_room: 168 + entrance: 351 + teleporter: [189, 0] + access: [] + - target_room: 167 + entrance: 352 + teleporter: [190, 0] + access: [] + - target_room: 171 + entrance: 353 + teleporter: [191, 0] + access: [] +- name: Pazuzu 3F Main Room + id: 170 + game_objects: + - name: "Pazuzu's Tower 3F - Guest Room West Box" + object_id: 0xD0 + type: "Box" + access: [] + - name: "Pazuzu's Tower 3F - Guest Room East Box" + object_id: 0xD1 + type: "Box" + access: [] + - name: "Pazuzu 3F" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu3F"] + access: [] + links: + - target_room: 180 + entrance: 356 + teleporter: [192, 0] + access: [] + - target_room: 181 + entrance: 357 + teleporter: [193, 0] + access: [] +- name: Pazuzu 3F Central Island + id: 171 + game_objects: [] + links: + - target_room: 169 + entrance: 360 + teleporter: [194, 0] + access: [] + - target_room: 170 + access: ["DragonClaw"] + - target_room: 172 + access: ["DragonClaw"] +- name: Pazuzu 3F Southern Island + id: 172 + game_objects: + - name: "Pazuzu's Tower 3F - South Ledge Box" + object_id: 0xD2 + type: "Box" + access: [] + links: + - target_room: 173 + entrance: 361 + teleporter: [195, 0] + access: [] + - target_room: 171 + access: ["DragonClaw"] +- name: Pazuzu 4F + id: 173 + game_objects: + - name: "Pazuzu's Tower 4F - Elevator West Box" + object_id: 0xD3 + type: "Box" + access: ["Bomb"] + - name: "Pazuzu's Tower 4F - Elevator East Box" + object_id: 0xD4 + type: "Box" + access: ["Bomb"] + - name: "Pazuzu's Tower 4F - East Storage Room Chest" + object_id: 0x18 + type: "Chest" + access: [] + - name: "Pazuzu 4F Lock" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu4FLock"] + access: ["Axe"] + - name: "Pazuzu 4F" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu4F"] + access: ["Bomb"] + links: + - target_room: 183 + entrance: 362 + teleporter: [196, 0] + access: [] + - target_room: 184 + entrance: 363 + teleporter: [197, 0] + access: [] + - target_room: 172 + entrance: 364 + teleporter: [198, 0] + access: [] + - target_room: 175 + entrance: 365 + teleporter: [199, 0] + access: [] +- name: Pazuzu 5F Pazuzu Loop + id: 174 + game_objects: + - name: "Pazuzu 5F" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu5F"] + access: [] + links: + - target_room: 181 + entrance: 368 + teleporter: [200, 0] + access: [] + - target_room: 182 + entrance: 369 + teleporter: [201, 0] + access: [] +- name: Pazuzu 5F Upper Loop + id: 175 + game_objects: + - name: "Pazuzu's Tower 5F - North Box" + object_id: 0xD5 + type: "Box" + access: [] + - name: "Pazuzu's Tower 5F - South Box" + object_id: 0xD6 + type: "Box" + access: [] + links: + - target_room: 173 + entrance: 370 + teleporter: [202, 0] + access: [] + - target_room: 176 + entrance: 371 + teleporter: [203, 0] + access: [] +- name: Pazuzu 6F + id: 176 + game_objects: + - name: "Pazuzu's Tower 6F - Box" + object_id: 0xD7 + type: "Box" + access: [] + - name: "Pazuzu's Tower 6F - Chest" + object_id: 0x19 + type: "Chest" + access: [] + - name: "Pazuzu 6F Lock" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu6FLock"] + access: ["Bomb", "Axe"] + - name: "Pazuzu 6F" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu6F"] + access: ["Bomb"] + links: + - target_room: 184 + entrance: 374 + teleporter: [204, 0] + access: [] + - target_room: 175 + entrance: 375 + teleporter: [205, 0] + access: [] + - target_room: 178 + entrance: 376 + teleporter: [206, 0] + access: [] + - target_room: 178 + entrance: 377 + teleporter: [207, 0] + access: [] +- name: Pazuzu 7F Southwest Area + id: 177 + game_objects: [] + links: + - target_room: 182 + entrance: 380 + teleporter: [26, 0] + access: [] + - target_room: 178 + access: ["DragonClaw"] +- name: Pazuzu 7F Rest of the Area + id: 178 + game_objects: [] + links: + - target_room: 177 + access: ["DragonClaw"] + - target_room: 176 + entrance: 381 + teleporter: [27, 0] + access: [] + - target_room: 176 + entrance: 382 + teleporter: [28, 0] + access: [] + - target_room: 179 + access: ["DragonClaw", "Pazuzu2FLock", "Pazuzu4FLock", "Pazuzu6FLock", "Pazuzu1F", "Pazuzu2F", "Pazuzu3F", "Pazuzu4F", "Pazuzu5F", "Pazuzu6F"] +- name: Pazuzu 7F Sky Room + id: 179 + game_objects: + - name: "Pazuzu's Tower 7F - Pazuzu Chest" + object_id: 0x1A + type: "Chest" + access: [] + - name: "Pazuzu" + object_id: 0 + type: "Trigger" + on_trigger: ["Pazuzu"] + access: ["Pazuzu2FLock", "Pazuzu4FLock", "Pazuzu6FLock", "Pazuzu1F", "Pazuzu2F", "Pazuzu3F", "Pazuzu4F", "Pazuzu5F", "Pazuzu6F"] + links: + - target_room: 178 + access: ["DragonClaw"] +- name: Pazuzu 1F to 3F + id: 180 + game_objects: [] + links: + - target_room: 166 + entrance: 385 + teleporter: [29, 0] + access: [] + - target_room: 170 + entrance: 386 + teleporter: [30, 0] + access: [] +- name: Pazuzu 3F to 5F + id: 181 + game_objects: [] + links: + - target_room: 170 + entrance: 387 + teleporter: [40, 0] + access: [] + - target_room: 174 + entrance: 388 + teleporter: [41, 0] + access: [] +- name: Pazuzu 5F to 7F + id: 182 + game_objects: [] + links: + - target_room: 174 + entrance: 389 + teleporter: [38, 0] + access: [] + - target_room: 177 + entrance: 390 + teleporter: [39, 0] + access: [] +- name: Pazuzu 2F to 4F + id: 183 + game_objects: [] + links: + - target_room: 169 + entrance: 391 + teleporter: [21, 0] + access: [] + - target_room: 173 + entrance: 392 + teleporter: [22, 0] + access: [] +- name: Pazuzu 4F to 6F + id: 184 + game_objects: [] + links: + - target_room: 173 + entrance: 393 + teleporter: [2, 0] + access: [] + - target_room: 176 + entrance: 394 + teleporter: [3, 0] + access: [] +- name: Light Temple + id: 185 + game_objects: + - name: "Light Temple - Box" + object_id: 0xD8 + type: "Box" + access: [] + links: + - target_room: 230 + entrance: 395 + teleporter: [19, 6] + access: [] + - target_room: 153 + entrance: 396 + teleporter: [70, 8] + access: ["MobiusCrest"] +- name: Ship Dock + id: 186 + game_objects: + - name: "Ship Dock Access" + object_id: 0 + type: "Trigger" + on_trigger: ["ShipDockAccess"] + access: [] + links: + - target_room: 228 + entrance: 399 + teleporter: [17, 6] + access: [] + - target_room: 162 + entrance: 397 + teleporter: [61, 8] + access: ["MobiusCrest"] +- name: Mac Ship Deck + id: 187 + game_objects: + - name: "Mac Ship Steering Wheel" + object_id: 00 + type: "Trigger" + on_trigger: ["ShipSteeringWheel"] + access: [] + - name: "Mac's Ship Deck - North Box" + object_id: 0xD9 + type: "Box" + access: [] + - name: "Mac's Ship Deck - Center Box" + object_id: 0xDA + type: "Box" + access: [] + - name: "Mac's Ship Deck - South Box" + object_id: 0xDB + type: "Box" + access: [] + links: + - target_room: 229 + entrance: 400 + teleporter: [37, 8] + access: [] + - target_room: 188 + entrance: 401 + teleporter: [50, 8] + access: [] + - target_room: 188 + entrance: 402 + teleporter: [51, 8] + access: [] + - target_room: 188 + entrance: 403 + teleporter: [52, 8] + access: [] + - target_room: 189 + entrance: 404 + teleporter: [53, 8] + access: [] +- name: Mac Ship B1 Outer Ring + id: 188 + game_objects: + - name: "Mac's Ship B1 - Northwest Hook Platform Box" + object_id: 0xE4 + type: "Box" + access: ["DragonClaw"] + - name: "Mac's Ship B1 - Center Hook Platform Box" + object_id: 0xE5 + type: "Box" + access: ["DragonClaw"] + links: + - target_room: 187 + entrance: 405 + teleporter: [208, 0] + access: [] + - target_room: 187 + entrance: 406 + teleporter: [175, 0] + access: [] + - target_room: 187 + entrance: 407 + teleporter: [172, 0] + access: [] + - target_room: 193 + entrance: 408 + teleporter: [88, 0] + access: [] + - target_room: 193 + access: [] +- name: Mac Ship B1 Square Room + id: 189 + game_objects: [] + links: + - target_room: 187 + entrance: 409 + teleporter: [141, 0] + access: [] + - target_room: 192 + entrance: 410 + teleporter: [87, 0] + access: [] +- name: Mac Ship B1 Central Corridor + id: 190 + game_objects: + - name: "Mac's Ship B1 - Central Corridor Box" + object_id: 0xE6 + type: "Box" + access: [] + links: + - target_room: 192 + entrance: 413 + teleporter: [86, 0] + access: [] + - target_room: 191 + entrance: 412 + teleporter: [102, 0] + access: [] + - target_room: 193 + access: [] +- name: Mac Ship B2 South Corridor + id: 191 + game_objects: [] + links: + - target_room: 190 + entrance: 415 + teleporter: [55, 8] + access: [] + - target_room: 194 + entrance: 414 + teleporter: [57, 1] + access: [] +- name: Mac Ship B2 North Corridor + id: 192 + game_objects: [] + links: + - target_room: 190 + entrance: 416 + teleporter: [56, 8] + access: [] + - target_room: 189 + entrance: 417 + teleporter: [57, 8] + access: [] +- name: Mac Ship B2 Outer Ring + id: 193 + game_objects: + - name: "Mac's Ship B2 - Barrel Room South Box" + object_id: 0xDF + type: "Box" + access: [] + - name: "Mac's Ship B2 - Barrel Room North Box" + object_id: 0xE0 + type: "Box" + access: [] + - name: "Mac's Ship B2 - Southwest Room Box" + object_id: 0xE1 + type: "Box" + access: [] + - name: "Mac's Ship B2 - Southeast Room Box" + object_id: 0xE2 + type: "Box" + access: [] + links: + - target_room: 188 + entrance: 418 + teleporter: [58, 8] + access: [] +- name: Mac Ship B1 Mac Room + id: 194 + game_objects: + - name: "Mac's Ship B1 - Mac Room Chest" + object_id: 0x1B + type: "Chest" + access: [] + - name: "Captain Mac" + object_id: 0 + type: "Trigger" + on_trigger: ["ShipLoaned"] + access: ["CaptainCap"] + links: + - target_room: 191 + entrance: 424 + teleporter: [101, 0] + access: [] +- name: Doom Castle Corridor of Destiny + id: 195 + game_objects: [] + links: + - target_room: 201 + entrance: 428 + teleporter: [84, 0] + access: [] + - target_room: 196 + entrance: 429 + teleporter: [35, 2] + access: [] + - target_room: 197 + entrance: 430 + teleporter: [209, 0] + access: ["StoneGolem"] + - target_room: 198 + entrance: 431 + teleporter: [211, 0] + access: ["StoneGolem", "TwinheadWyvern"] + - target_room: 199 + entrance: 432 + teleporter: [13, 2] + access: ["StoneGolem", "TwinheadWyvern", "Zuh"] +- name: Doom Castle Ice Floor + id: 196 + game_objects: + - name: "Doom Castle 4F - Northwest Room Box" + object_id: 0xE7 + type: "Box" + access: ["Sword", "DragonClaw"] + - name: "Doom Castle 4F - Southwest Room Box" + object_id: 0xE8 + type: "Box" + access: ["Sword", "DragonClaw"] + - name: "Doom Castle 4F - Northeast Room Box" + object_id: 0xE9 + type: "Box" + access: ["Sword"] + - name: "Doom Castle 4F - Southeast Room Box" + object_id: 0xEA + type: "Box" + access: ["Sword", "DragonClaw"] + - name: "Stone Golem" + object_id: 0 + type: "Trigger" + on_trigger: ["StoneGolem"] + access: ["Sword", "DragonClaw"] + links: + - target_room: 195 + entrance: 433 + teleporter: [109, 3] + access: [] +- name: Doom Castle Lava Floor + id: 197 + game_objects: + - name: "Doom Castle 5F - North Left Box" + object_id: 0xEB + type: "Box" + access: ["DragonClaw"] + - name: "Doom Castle 5F - North Right Box" + object_id: 0xEC + type: "Box" + access: ["DragonClaw"] + - name: "Doom Castle 5F - South Left Box" + object_id: 0xED + type: "Box" + access: ["DragonClaw"] + - name: "Doom Castle 5F - South Right Box" + object_id: 0xEE + type: "Box" + access: ["DragonClaw"] + - name: "Twinhead Wyvern" + object_id: 0 + type: "Trigger" + on_trigger: ["TwinheadWyvern"] + access: ["DragonClaw"] + links: + - target_room: 195 + entrance: 434 + teleporter: [210, 0] + access: [] +- name: Doom Castle Sky Floor + id: 198 + game_objects: + - name: "Doom Castle 6F - West Box" + object_id: 0xEF + type: "Box" + access: [] + - name: "Doom Castle 6F - East Box" + object_id: 0xF0 + type: "Box" + access: [] + - name: "Zuh" + object_id: 0 + type: "Trigger" + on_trigger: ["Zuh"] + access: ["DragonClaw"] + links: + - target_room: 195 + entrance: 435 + teleporter: [212, 0] + access: [] + - target_room: 197 + access: [] +- name: Doom Castle Hero Room + id: 199 + game_objects: + - name: "Doom Castle Hero Chest 01" + object_id: 0xF2 + type: "Chest" + access: [] + - name: "Doom Castle Hero Chest 02" + object_id: 0xF3 + type: "Chest" + access: [] + - name: "Doom Castle Hero Chest 03" + object_id: 0xF4 + type: "Chest" + access: [] + - name: "Doom Castle Hero Chest 04" + object_id: 0xF5 + type: "Chest" + access: [] + links: + - target_room: 200 + entrance: 436 + teleporter: [54, 0] + access: [] + - target_room: 195 + entrance: 441 + teleporter: [110, 3] + access: [] +- name: Doom Castle Dark King Room + id: 200 + game_objects: [] + links: + - target_room: 199 + entrance: 442 + teleporter: [52, 0] + access: [] diff --git a/worlds/ffmq/data/settings.yaml b/worlds/ffmq/data/settings.yaml new file mode 100644 index 000000000000..aa973ee22b0b --- /dev/null +++ b/worlds/ffmq/data/settings.yaml @@ -0,0 +1,140 @@ +# YAML Preset file for FFMQR +Final Fantasy Mystic Quest: + enemies_density: + All: 0 + ThreeQuarter: 0 + Half: 0 + Quarter: 0 + None: 0 + chests_shuffle: + Prioritize: 0 + Include: 0 + shuffle_boxes_content: + true: 0 + false: 0 + npcs_shuffle: + Prioritize: 0 + Include: 0 + Exclude: 0 + battlefields_shuffle: + Prioritize: 0 + Include: 0 + Exclude: 0 + logic_options: + Friendly: 0 + Standard: 0 + Expert: 0 + shuffle_enemies_position: + true: 0 + false: 0 + enemies_scaling_lower: + Quarter: 0 + Half: 0 + ThreeQuarter: 0 + Normal: 0 + OneAndQuarter: 0 + OneAndHalf: 0 + Double: 0 + DoubleAndHalf: 0 + Triple: 0 + enemies_scaling_upper: + Quarter: 0 + Half: 0 + ThreeQuarter: 0 + Normal: 0 + OneAndQuarter: 0 + OneAndHalf: 0 + Double: 0 + DoubleAndHalf: 0 + Triple: 0 + bosses_scaling_lower: + Quarter: 0 + Half: 0 + ThreeQuarter: 0 + Normal: 0 + OneAndQuarter: 0 + OneAndHalf: 0 + Double: 0 + DoubleAndHalf: 0 + Triple: 0 + bosses_scaling_upper: + Quarter: 0 + Half: 0 + ThreeQuarter: 0 + Normal: 0 + OneAndQuarter: 0 + OneAndHalf: 0 + Double: 0 + DoubleAndHalf: 0 + Triple: 0 + enemizer_attacks: + Normal: 0 + Safe: 0 + Chaos: 0 + SelfDestruct: 0 + SimpleShuffle: 0 + leveling_curve: + Half: 0 + Normal: 0 + OneAndHalf: 0 + Double: 0 + DoubleHalf: 0 + Triple: 0 + Quadruple: 0 + battles_quantity: + Ten: 0 + Seven: 0 + Five: 0 + Three: 0 + One: 0 + RandomHigh: 0 + RandomLow: 0 + shuffle_battlefield_rewards: + true: 0 + false: 0 + random_starting_weapon: + true: 0 + false: 0 + progressive_gear: + true: 0 + false: 0 + tweaked_dungeons: + true: 0 + false: 0 + doom_castle_mode: + Standard: 0 + BossRush: 0 + DarkKingOnly: 0 + doom_castle_shortcut: + true: 0 + false: 0 + sky_coin_mode: + Standard: 0 + StartWith: 0 + SaveTheCrystals: 0 + ShatteredSkyCoin: 0 + sky_coin_fragments_qty: + Low16: 0 + Mid24: 0 + High32: 0 + RandomNarrow: 0 + RandomWide: 0 + enable_spoilers: + true: 0 + false: 0 + progressive_formations: + Disabled: 0 + RegionsStrict: 0 + RegionsKeepType: 0 + map_shuffling: + None: 0 + Overworld: 0 + Dungeons: 0 + OverworldDungeons: 0 + Everything: 0 + crest_shuffle: + true: 0 + false: 0 +description: Generated by Archipelago +game: Final Fantasy Mystic Quest +name: Player diff --git a/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md new file mode 100644 index 000000000000..dd4ea354fab1 --- /dev/null +++ b/worlds/ffmq/docs/en_Final Fantasy Mystic Quest.md @@ -0,0 +1,33 @@ +# Final Fantasy Mystic Quest + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? + +Besides items being shuffled, you have multiple options for shuffling maps, crest warps, and battlefield locations. +There are a number of other options for tweaking the difficulty of the game. + +## What items and locations get shuffled? + +Items received normally through chests, from NPCs, or battlefields are shuffled. Optionally, you may also include +the items from brown boxes. + +## Which items can be in another player's world? + +Any of the items which can be shuffled may also be placed into another player's world. + +## What does another world's item look like in Final Fantasy Mystic Quest? + +For locations that are originally boxes or chests, they will appear as a box if the item in it is categorized as a +filler item, and a chest if it contains a useful or advancement item. Trap items may randomly appear as a box or chest. +When opening a chest with an item for another player, you will see the Archipelago icon and it will tell you you've +found an "Archipelago Item" + +## When the player receives an item, what happens? + +A dialogue box will open to show you the item you've received. You will not receive items while you are in battle, +menus, or the overworld (except sometimes when closing the menu). + diff --git a/worlds/ffmq/docs/setup_en.md b/worlds/ffmq/docs/setup_en.md new file mode 100644 index 000000000000..9d9088dbc232 --- /dev/null +++ b/worlds/ffmq/docs/setup_en.md @@ -0,0 +1,162 @@ +# Final Fantasy Mystic Quest Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client` + +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI such as: + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) + - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, + - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other + compatible hardware + +- Your legally obtained Final Fantasy Mystic Quest 1.1 ROM file, probably named `Final Fantasy - Mystic Quest (U) (V1.1).sfc` +The Archipelago community cannot supply you with this. + +## Installation Procedures + +### Windows Setup + +1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this, + or you are on an older version, you may run the installer again to install the SNI Client. +2. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The Player Settings page on the website allows you to configure your personal settings and export a config file from +them. Player settings page: [Final Fantasy Mystic Quest Player Settings Page](/games/Final%20Fantasy%20Mystic%20Quest/player-settings) + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## Generating a Single-Player Game + +1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. + - Player Settings page: [Final Fantasy Mystic Quest Player Settings Page](/games/Final%20Fantasy%20Mystic%20Quest/player-settings) +2. You will be presented with a "Seed Info" page. +3. Click the "Create New Room" link. +4. You will be presented with a server page, from which you can download your `.apmq` patch file. +5. Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest 1.1 ROM +and the .apmq file you received, choose optional preferences, and click `Generate` to get your patched ROM. +7. Since this is a single-player game, you will no longer need the client, so feel free to close it. + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing +everyone's patch files. Your patch file should have a `.apmq` extension. + +Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest 1.1 ROM +and the .apmq file you received, choose optional preferences, and click `Generate` to get your patched ROM. + +Manually launch the SNI Client, and run the patched ROM in your chosen software or hardware. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x-rr + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these + menu options: + `Config --> Cores --> SNES --> BSNES` + Once you have changed the loaded core, you must restart BizHawk. +2. Load your ROM file if it hasn't already been loaded. +3. Click on the Tools menu and click on **Lua Console** +4. Click the Open Folder icon that says `Open Script` via the tooltip on mouse hover, or click the Script Menu then `Open Script...`, or press `Ctrl-O`. +5. Select the `Connector.lua` file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only. + +##### RetroArch 1.10.1 or newer + +You only have to do these steps once. Note, RetroArch 1.9.x will not work as it is older than 1.10.1. + +1. Enter the RetroArch main menu screen. +2. Go to Settings --> User Interface. Set "Show Advanced Settings" to ON. +3. Go to Settings --> Network. Set "Network Commands" to ON. (It is found below Request Device 16.) Leave the default + Network Command Port at 55355. + +![Screenshot of Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Go to Main Menu --> Online Updater --> Core Downloader. Scroll down and select "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +When loading a ROM, be sure to select a **bsnes-mercury** core. These are the only cores that allow external tools to +read ROM data. + +#### With hardware + +This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do +this now. SD2SNES and FXPak Pro users may download the appropriate firmware on the SD2SNES releases page. SD2SNES +releases page: [SD2SNES Releases Page](https://github.com/RedGuyyyy/sd2snes/releases) + +Other hardware may find helpful information on the usb2snes platforms +page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platforms) + +1. Close your emulator, which may have auto-launched. +2. Power on your device and load the ROM. + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our hosting service. The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the Generate page above. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing. From ce2f9312ca18106168870c8cf836dfd545b7488b Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Tue, 28 Nov 2023 13:50:12 -0800 Subject: [PATCH 06/56] BizHawkClient: Change `open_connection` to use 127.0.0.1 instead of localhost (#2525) When using localhost on mac, both ipv4 and ipv6 are tried and raise separate errors which are combined by asyncio and difficult/inelegant to handle. Python 3.12 adds the argument all_errors, which would make this easier. --- worlds/_bizhawk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/_bizhawk/__init__.py b/worlds/_bizhawk/__init__.py index cddfde4ff37f..94a9ce1ddf04 100644 --- a/worlds/_bizhawk/__init__.py +++ b/worlds/_bizhawk/__init__.py @@ -97,7 +97,7 @@ async def connect(ctx: BizHawkContext) -> bool: for port in ports: try: - ctx.streams = await asyncio.open_connection("localhost", port) + ctx.streams = await asyncio.open_connection("127.0.0.1", port) ctx.connection_status = ConnectionStatus.TENTATIVE ctx._port = port return True From 737686a88d54f8ace38f8b577d54d55f5b6c4250 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Tue, 28 Nov 2023 13:56:27 -0800 Subject: [PATCH 07/56] BizHawkClient: Use `local_path` when autolaunching BizHawk with lua script (#2526) * BizHawkClient: Change autolaunch path to lua script to use local_path * BizHawkClient: Remove unnecessary call to os.path.join and linting --- worlds/_bizhawk/context.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/worlds/_bizhawk/context.py b/worlds/_bizhawk/context.py index 2699b0f5f106..4ee6e24f591d 100644 --- a/worlds/_bizhawk/context.py +++ b/worlds/_bizhawk/context.py @@ -208,19 +208,30 @@ async def _run_game(rom: str): if auto_start is True: emuhawk_path = Utils.get_settings().bizhawkclient_options.emuhawk_path - subprocess.Popen([emuhawk_path, "--lua=data/lua/connector_bizhawk_generic.lua", os.path.realpath(rom)], - cwd=Utils.local_path("."), - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.Popen( + [ + emuhawk_path, + f"--lua={Utils.local_path('data', 'lua', 'connector_bizhawk_generic.lua')}", + os.path.realpath(rom), + ], + cwd=Utils.local_path("."), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) elif isinstance(auto_start, str): import shlex - subprocess.Popen([*shlex.split(auto_start), os.path.realpath(rom)], - cwd=Utils.local_path("."), - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.Popen( + [ + *shlex.split(auto_start), + os.path.realpath(rom) + ], + cwd=Utils.local_path("."), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) async def _patch_and_run_game(patch_file: str): From 39969abd6ad6aa715d979ef6eece1f242e58e575 Mon Sep 17 00:00:00 2001 From: el-u <109771707+el-u@users.noreply.github.com> Date: Wed, 29 Nov 2023 00:11:17 +0100 Subject: [PATCH 08/56] WebHostLib: fix NamedRange in options presets (#2528) --- WebHostLib/static/assets/player-options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/static/assets/player-options.js b/WebHostLib/static/assets/player-options.js index 37ba7f98ff19..92cd6c43f3cc 100644 --- a/WebHostLib/static/assets/player-options.js +++ b/WebHostLib/static/assets/player-options.js @@ -369,7 +369,7 @@ const setPresets = (optionsData, presetName) => { break; } - case 'special_range': { + case 'named_range': { const selectElement = document.querySelector(`select[data-key='${option}']`); const rangeElement = document.querySelector(`input[data-key='${option}']`); const randomElement = document.querySelector(`.randomize-button[data-key='${option}']`); From 6c5f8250fba413dd9188041c58a132b3aa7981bd Mon Sep 17 00:00:00 2001 From: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Wed, 29 Nov 2023 01:19:42 -0500 Subject: [PATCH 09/56] TLOZ: Use the proper location name lookup (#2529) --- Zelda1Client.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Zelda1Client.py b/Zelda1Client.py index db3d3519aa60..cd76a0a5ca78 100644 --- a/Zelda1Client.py +++ b/Zelda1Client.py @@ -13,7 +13,6 @@ import Utils from Utils import async_start -from worlds import lookup_any_location_id_to_name from CommonClient import CommonContext, server_loop, gui_enabled, console_loop, ClientCommandProcessor, logger, \ get_base_parser @@ -153,7 +152,7 @@ def get_payload(ctx: ZeldaContext): def reconcile_shops(ctx: ZeldaContext): - checked_location_names = [lookup_any_location_id_to_name[location] for location in ctx.checked_locations] + checked_location_names = [ctx.location_names[location] for location in ctx.checked_locations] shops = [location for location in checked_location_names if "Shop" in location] left_slots = [shop for shop in shops if "Left" in shop] middle_slots = [shop for shop in shops if "Middle" in shop] @@ -191,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone= locations_checked = [] location = None for location in ctx.missing_locations: - location_name = lookup_any_location_id_to_name[location] + location_name = ctx.location_names[location] if location_name in Locations.overworld_locations and zone == "overworld": status = locations_array[Locations.major_location_offsets[location_name]] From a83501a2a077fabd1c7cfe9fa4a66b9db1ce33ba Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Wed, 29 Nov 2023 22:57:40 -0500 Subject: [PATCH 10/56] Fix a bug in weighted-settings causing accepted range values to be exclusive of outer range (#2535) --- WebHostLib/static/assets/weighted-options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/static/assets/weighted-options.js b/WebHostLib/static/assets/weighted-options.js index a2fedb5383b7..80f8efd1d7de 100644 --- a/WebHostLib/static/assets/weighted-options.js +++ b/WebHostLib/static/assets/weighted-options.js @@ -576,7 +576,7 @@ class GameSettings { option = parseInt(option, 10); let optionAcceptable = false; - if ((option > setting.min) && (option < setting.max)) { + if ((option >= setting.min) && (option <= setting.max)) { optionAcceptable = true; } if (setting.hasOwnProperty('value_names') && Object.values(setting.value_names).includes(option)){ From b9ce2052c5dfeb72d421f3052f9b8c6b23986fe8 Mon Sep 17 00:00:00 2001 From: Brooty Johnson <83629348+Br00ty@users.noreply.github.com> Date: Thu, 30 Nov 2023 03:29:55 -0500 Subject: [PATCH 11/56] DS3: update setup guide to preserve downpatching instructions (#2531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update DS3 setup guide to preserve downpatching instructions we want to preserve this on the AP site as the future of the speedsouls wiki is unknown and may disappear at any time. * Update worlds/dark_souls_3/docs/setup_en.md Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com> * Update setup_en.md --------- Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com> --- worlds/dark_souls_3/docs/setup_en.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index d9dbb2e54729..7a3ca4e9bd86 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -21,7 +21,20 @@ This client has only been tested with the Official Steam version of the game at ## Downpatching Dark Souls III -Follow instructions from the [speedsouls wiki](https://wiki.speedsouls.com/darksouls3:Downpatching) to download version 1.15. Your download command, including the correct depot and manifest ids, will be "download_depot 374320 374321 4471176929659548333" +To downpatch DS3 for use with Archipelago, use the following instructions from the speedsouls wiki database. + +1. Launch Steam (in online mode). +2. Press the Windows Key + R. This will open the Run window. +3. Open the Steam console by typing the following string: steam://open/console , Steam should now open in Console Mode. +4. Insert the string of the depot you wish to download. For the AP supported v1.15, you will want to use: download_depot 374320 374321 4471176929659548333. +5. Steam will now download the depot. Note: There is no progress bar of the download in Steam, but it is still downloading in the background. +6. Turn off auto-updates in Steam by right-clicking Dark Souls III in your library > Properties > Updates > set "Automatic Updates" to "Only update this game when I launch it" (or change the value for AutoUpdateBehavior to 1 in "\Steam\steamapps\appmanifest_374320.acf"). +7. Back up your existing game folder in "\Steam\steamapps\common\DARK SOULS III". +8. Return back to Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like "\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX". Back up this game folder as well. +9. Delete your existing game folder in "\Steam\steamapps\common\DARK SOULS III", then replace it with your game folder in "\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX". +10. Back up and delete your save file "DS30000.sl2" in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type %appdata% and hit enter or: open File Explorer > View > Hidden Items and follow "C:\Users\your username\AppData\Roaming\DarkSoulsIII\numbers". +11. If you did all these steps correctly, you should be able to confirm your game version in the upper left corner after launching Dark Souls III. + ## Installing the Archipelago mod From 80fed1c6fb444664cba8f7bc73c3a8c557eb6d12 Mon Sep 17 00:00:00 2001 From: agilbert1412 Date: Thu, 30 Nov 2023 03:32:32 -0500 Subject: [PATCH 12/56] Stardew Valley: Fixed potential softlock with walnut purchases if Entrance Randomizer locks access to the field office (#2261) * - Added logic rules for reaching, then completing, the field office in order to be allowed to spend significant amounts of walnuts * - Revert moving a method for some reason --- worlds/stardew_valley/logic.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/worlds/stardew_valley/logic.py b/worlds/stardew_valley/logic.py index 5a6244cf37ae..d4476a3f313a 100644 --- a/worlds/stardew_valley/logic.py +++ b/worlds/stardew_valley/logic.py @@ -1536,6 +1536,7 @@ def has_walnut(self, number: int) -> StardewRule: reach_west = self.can_reach_region(Region.island_west) reach_hut = self.can_reach_region(Region.leo_hut) reach_southeast = self.can_reach_region(Region.island_south_east) + reach_field_office = self.can_reach_region(Region.field_office) reach_pirate_cove = self.can_reach_region(Region.pirate_cove) reach_outside_areas = And(reach_south, reach_north, reach_west, reach_hut) reach_volcano_regions = [self.can_reach_region(Region.volcano), @@ -1544,12 +1545,12 @@ def has_walnut(self, number: int) -> StardewRule: self.can_reach_region(Region.volcano_floor_10)] reach_volcano = Or(reach_volcano_regions) reach_all_volcano = And(reach_volcano_regions) - reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano] + reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office] reach_caves = And(self.can_reach_region(Region.qi_walnut_room), self.can_reach_region(Region.dig_site), self.can_reach_region(Region.gourmand_frog_cave), self.can_reach_region(Region.colored_crystals_cave), self.can_reach_region(Region.shipwreck), self.has(Weapon.any_slingshot)) - reach_entire_island = And(reach_outside_areas, reach_all_volcano, + reach_entire_island = And(reach_outside_areas, reach_field_office, reach_all_volcano, reach_caves, reach_southeast, reach_pirate_cove) if number <= 5: return Or(reach_south, reach_north, reach_west, reach_volcano) @@ -1563,7 +1564,8 @@ def has_walnut(self, number: int) -> StardewRule: return reach_entire_island gems = [Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz] return reach_entire_island & self.has(Fruit.banana) & self.has(gems) & self.can_mine_perfectly() & \ - self.can_fish_perfectly() & self.has(Craftable.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) + self.can_fish_perfectly() & self.has(Craftable.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) & \ + self.can_complete_field_office() def has_everything(self, all_progression_items: Set[str]) -> StardewRule: all_regions = [region.name for region in vanilla_regions] From c7d4c2f63ccef5ce61f7eca9bad3baf504b9a658 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Fri, 1 Dec 2023 03:26:27 -0600 Subject: [PATCH 13/56] Docs: Add documentation on writing and running tests (#2348) * Docs: Add documentation on writing and running tests * review improvements * sliver requests --- docs/contributing.md | 2 +- docs/tests.md | 90 ++++++++++++++++++++++++++++++++++++++++++++ docs/world api.md | 6 ++- 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 docs/tests.md diff --git a/docs/contributing.md b/docs/contributing.md index 6fd80fe86ee4..9b5f93e1980b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -7,7 +7,7 @@ Contributions are welcome. We have a few requests for new contributors: * **Ensure that critical changes are covered by tests.** It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working. -If you wish to contribute by adding a new game, please take a look at the [logic unit test documentation](/docs/world%20api.md#tests). +If you wish to contribute by adding a new game, please take a look at the [logic unit test documentation](/docs/tests.md). If you wish to contribute to the website, please take a look at [these tests](/test/webhost). * **Do not introduce unit test failures/regressions.** diff --git a/docs/tests.md b/docs/tests.md new file mode 100644 index 000000000000..7a3531f0f84f --- /dev/null +++ b/docs/tests.md @@ -0,0 +1,90 @@ +# Archipelago Unit Testing API + +This document covers some of the generic tests available using Archipelago's unit testing system, as well as some basic +steps on how to write your own. + +## Generic Tests + +Some generic tests are run on every World to ensure basic functionality with default options. These basic tests can be +found in the [general test directory](/test/general). + +## Defining World Tests + +In order to run tests from your world, you will need to create a `test` package within your world package. This can be +done by creating a `test` directory with a file named `__init__.py` inside it inside your world. By convention, a base +for your world tests can be created in this file that you can then import into other modules. + +### WorldTestBase + +In order to test basic functionality of varying options, as well as to test specific edge cases or that certain +interactions in the world interact as expected, you will want to use the [WorldTestBase](/test/bases.py). This class +comes with the basics for test setup as well as a few preloaded tests that most worlds might want to check on varying +options combinations. + +Example `/worlds//test/__init__.py`: + +```python +from test.bases import WorldTestBase + + +class MyGameTestBase(WorldTestBase): + game = "My Game" +``` + +The basic tests that WorldTestBase comes with include `test_all_state_can_reach_everything`, +`test_empty_state_can_reach_something`, and `test_fill`. These test that with all collected items everything is +reachable, with no collected items at least something is reachable, and that a valid multiworld can be completed with +all steps being called, respectively. + +### Writing Tests + +#### Using WorldTestBase + +Adding runs for the basic tests for a different option combination is as easy as making a new module in the test +package, creating a class that inherits from your game's TestBase, and defining the options in a dict as a field on the +class. The new module should be named `test_.py` and have at least one class inheriting from the base, or +define its own testing methods. Newly defined test methods should follow standard PEP8 snake_case format and also start +with `test_`. + +Example `/worlds//test/test_chest_access.py`: + +```python +from . import MyGameTestBase + + +class TestChestAccess(MyGameTestBase): + options = { + "difficulty": "easy", + "final_boss_hp": 4000, + } + + def test_sword_chests(self) -> None: + """Test locations that require a sword""" + locations = ["Chest1", "Chest2"] + items = [["Sword"]] + # This tests that the provided locations aren't accessible without the provided items, but can be accessed once + # the items are obtained. + # This will also check that any locations not provided don't have the same dependency requirement. + # Optionally, passing only_check_listed=True to the method will only check the locations provided. + self.assertAccessDependency(locations, items) +``` + +When tests are run, this class will create a multiworld with a single player having the provided options, and run the +generic tests, as well as the new custom test. Each test method definition will create its own separate solo multiworld +that will be cleaned up after. If you don't want to run the generic tests on a base, `run_default_tests` can be +overridden. For more information on what methods are available to your class, check the +[WorldTestBase definition](/test/bases.py#L104). + +#### Alternatives to WorldTestBase + +Unit tests can also be created using [TestBase](/test/bases.py#L14) or +[unittest.TestCase](https://docs.python.org/3/library/unittest.html#unittest.TestCase) depending on your use case. These +may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for +testing portions of your code that can be tested without relying on a multiworld to be created first. + +## Running Tests + +In PyCharm, running all tests can be done by right-clicking the root `test` directory and selecting `run Python tests`. +If you do not have pytest installed, you may get import failures. To solve this, edit the run configuration, and set the +working directory of the run to the Archipelago directory. If you only want to run your world's defined tests, repeat +the steps for the test directory within your world. diff --git a/docs/world api.md b/docs/world api.md index 6393f245ba68..0ab06da65603 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -870,7 +870,7 @@ TestBase, and can then define options to test in the class body, and run tests i Example `__init__.py` ```python -from test.test_base import WorldTestBase +from test.bases import WorldTestBase class MyGameTestBase(WorldTestBase): @@ -879,7 +879,7 @@ class MyGameTestBase(WorldTestBase): Next using the rules defined in the above `set_rules` we can test that the chests have the correct access rules. -Example `testChestAccess.py` +Example `test_chest_access.py` ```python from . import MyGameTestBase @@ -899,3 +899,5 @@ class TestChestAccess(MyGameTestBase): # this will test that chests 3-5 can't be accessed without any weapon, but can be with just one of them. self.assertAccessDependency(locations, items) ``` + +For more information on tests check the [tests doc](tests.md). From 5e5018dd6443b7e3e90ce824d1e1a3b3d2e05047 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 1 Dec 2023 21:19:41 +0100 Subject: [PATCH 14/56] WebHost: flash each message only once (#2547) --- WebHostLib/templates/pageWrapper.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/templates/pageWrapper.html b/WebHostLib/templates/pageWrapper.html index ec7888ac7317..c7dda523ef4e 100644 --- a/WebHostLib/templates/pageWrapper.html +++ b/WebHostLib/templates/pageWrapper.html @@ -16,7 +16,7 @@ {% with messages = get_flashed_messages() %} {% if messages %}
- {% for message in messages %} + {% for message in messages | unique %}
{{ message }}
{% endfor %}
From 6e38126add3cf47c6a88000ca2ce0a62826e1c78 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Fri, 1 Dec 2023 14:20:24 -0600 Subject: [PATCH 15/56] Webhost: fix options page redirects (#2540) --- WebHostLib/templates/supportedGames.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebHostLib/templates/supportedGames.html b/WebHostLib/templates/supportedGames.html index 3252b16ad4e7..6666323c9387 100644 --- a/WebHostLib/templates/supportedGames.html +++ b/WebHostLib/templates/supportedGames.html @@ -53,7 +53,7 @@

{% endif %} {% if world.web.options_page is string %} | - Options Page + Options Page {% elif world.web.options_page %} | Options Page From e8ceb122813c9771a89f538dd042cf160de92485 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sat, 2 Dec 2023 12:40:38 -0500 Subject: [PATCH 16/56] =?UTF-8?q?Pok=C3=A9mon=20RB:=20Fix=20connection=20n?= =?UTF-8?q?ames=20+=20missing=20connection=20(#2553)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- worlds/pokemon_rb/regions.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index f844976548bd..97e63c05573d 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -1631,7 +1631,7 @@ def create_regions(self): connect(multiworld, player, "Cerulean City", "Route 24", one_way=True) connect(multiworld, player, "Cerulean City", "Cerulean City-T", lambda state: state.has("Help Bill", player)) connect(multiworld, player, "Cerulean City-Outskirts", "Cerulean City", one_way=True) - connect(multiworld, player, "Cerulean City-Outskirts", "Cerulean City", lambda state: logic.can_cut(state, player)) + connect(multiworld, player, "Cerulean City", "Cerulean City-Outskirts", lambda state: logic.can_cut(state, player), one_way=True) connect(multiworld, player, "Cerulean City-Outskirts", "Route 9", lambda state: logic.can_cut(state, player)) connect(multiworld, player, "Cerulean City-Outskirts", "Route 5") connect(multiworld, player, "Cerulean Cave B1F", "Cerulean Cave B1F-E", lambda state: logic.can_surf(state, player), one_way=True) @@ -1707,7 +1707,6 @@ def create_regions(self): connect(multiworld, player, "Route 12-S", "Route 12-Grass", lambda state: logic.can_cut(state, player), one_way=True) connect(multiworld, player, "Route 12-L", "Lavender Town") connect(multiworld, player, "Route 10-S", "Lavender Town") - connect(multiworld, player, "Route 8-W", "Saffron City") connect(multiworld, player, "Route 8", "Lavender Town") connect(multiworld, player, "Pokemon Tower 6F", "Pokemon Tower 6F-S", lambda state: state.has("Silph Scope", player) or (state.has("Buy Poke Doll", player) and state.multiworld.poke_doll_skip[player])) connect(multiworld, player, "Route 8", "Route 8-Grass", lambda state: logic.can_cut(state, player), one_way=True) @@ -1831,7 +1830,8 @@ def create_regions(self): connect(multiworld, player, "Silph Co 6F", "Silph Co 6F-SW", lambda state: logic.card_key(state, 6, player)) connect(multiworld, player, "Silph Co 7F", "Silph Co 7F-E", lambda state: logic.card_key(state, 7, player)) connect(multiworld, player, "Silph Co 7F-SE", "Silph Co 7F-E", lambda state: logic.card_key(state, 7, player)) - connect(multiworld, player, "Silph Co 8F", "Silph Co 8F-W", lambda state: logic.card_key(state, 8, player)) + connect(multiworld, player, "Silph Co 8F", "Silph Co 8F-W", lambda state: logic.card_key(state, 8, player), one_way=True, name="Silph Co 8F to Silph Co 8F-W (Card Key)") + connect(multiworld, player, "Silph Co 8F-W", "Silph Co 8F", lambda state: logic.card_key(state, 8, player), one_way=True, name="Silph Co 8F-W to Silph Co 8F (Card Key)") connect(multiworld, player, "Silph Co 9F", "Silph Co 9F-SW", lambda state: logic.card_key(state, 9, player)) connect(multiworld, player, "Silph Co 9F-NW", "Silph Co 9F-SW", lambda state: logic.card_key(state, 9, player)) connect(multiworld, player, "Silph Co 10F", "Silph Co 10F-SE", lambda state: logic.card_key(state, 10, player)) @@ -1864,22 +1864,23 @@ def create_regions(self): # access to any part of a city will enable flying to the Pokemon Center connect(multiworld, player, "Cerulean City-Cave", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) connect(multiworld, player, "Cerulean City-Badge House Backyard", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) + connect(multiworld, player, "Cerulean City-T", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True, name="Cerulean City-T to Cerulean City (Fly)") connect(multiworld, player, "Fuchsia City-Good Rod House Backyard", "Fuchsia City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Saffron City-G", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Saffron City-Pidgey", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Saffron City-Silph", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Saffron City-Copycat", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Celadon City-G", "Celadon City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Vermilion City-G", "Vermilion City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Vermilion City-Dock", "Vermilion City", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Cinnabar Island-G", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True) - connect(multiworld, player, "Cinnabar Island-M", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True) + connect(multiworld, player, "Saffron City-G", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True, name="Saffron City-G to Saffron City (Fly)") + connect(multiworld, player, "Saffron City-Pidgey", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True, name="Saffron City-Pidgey to Saffron City (Fly)") + connect(multiworld, player, "Saffron City-Silph", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True, name="Saffron City-Silph to Saffron City (Fly)") + connect(multiworld, player, "Saffron City-Copycat", "Saffron City", lambda state: logic.can_fly(state, player), one_way=True, name="Saffron City-Copycat to Saffron City (Fly)") + connect(multiworld, player, "Celadon City-G", "Celadon City", lambda state: logic.can_fly(state, player), one_way=True, name="Celadon City-G to Celadon City (Fly)") + connect(multiworld, player, "Vermilion City-G", "Vermilion City", lambda state: logic.can_fly(state, player), one_way=True, name="Vermilion City-G to Vermilion City (Fly)") + connect(multiworld, player, "Vermilion City-Dock", "Vermilion City", lambda state: logic.can_fly(state, player), one_way=True, name="Vermilion City-Dock to Vermilion City (Fly)") + connect(multiworld, player, "Cinnabar Island-G", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-G to Cinnabar Island (Fly)") + connect(multiworld, player, "Cinnabar Island-M", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-M to Cinnabar Island (Fly)") # drops - connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True) - connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F-NE", one_way=True) - connect(multiworld, player, "Seafoam Islands B1F", "Seafoam Islands B2F-NW", one_way=True) + connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F (Drop)") + connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F-NE", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F-NE (Drop)") + connect(multiworld, player, "Seafoam Islands B1F", "Seafoam Islands B2F-NW", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B2F-NW (Drop)") connect(multiworld, player, "Seafoam Islands B1F-NE", "Seafoam Islands B2F-NE", one_way=True) connect(multiworld, player, "Seafoam Islands B2F-NW", "Seafoam Islands B3F", lambda state: logic.can_strength(state, player) and state.has("Seafoam Exit Boulder", player, 6), one_way=True) connect(multiworld, player, "Seafoam Islands B2F-NE", "Seafoam Islands B3F", lambda state: logic.can_strength(state, player) and state.has("Seafoam Exit Boulder", player, 6), one_way=True) @@ -1888,7 +1889,7 @@ def create_regions(self): # If you haven't dropped the boulders, you'll go straight to B4F connect(multiworld, player, "Seafoam Islands B2F-NW", "Seafoam Islands B4F-W", one_way=True) connect(multiworld, player, "Seafoam Islands B2F-NE", "Seafoam Islands B4F-W", one_way=True) - connect(multiworld, player, "Seafoam Islands B3F", "Seafoam Islands B4F", one_way=True) + connect(multiworld, player, "Seafoam Islands B3F", "Seafoam Islands B4F", one_way=True, name="Seafoam Islands B1F to Seafoam Islands B4F (Drop)") connect(multiworld, player, "Seafoam Islands B3F", "Seafoam Islands B4F-W", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Pokemon Mansion 3F-SE", "Pokemon Mansion 2F", one_way=True) connect(multiworld, player, "Pokemon Mansion 3F-SE", "Pokemon Mansion 1F-SE", one_way=True) @@ -1944,7 +1945,8 @@ def create_regions(self): connect(multiworld, player, region.name, entrance_data["to"]["map"], lambda state: logic.rock_tunnel(state, player), one_way=True) else: - connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True) + connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, + name=entrance_data["name"] if "name" in entrance_data else None) forced_connections = set() From a83bf2f61687c55337903d77fd8d3451bb0e4962 Mon Sep 17 00:00:00 2001 From: zig-for Date: Sun, 3 Dec 2023 12:24:35 -0800 Subject: [PATCH 17/56] LADX: Fix bug with Webhost usage (#2556) We were using data created in init when we never called init --- worlds/ladx/Options.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index f1d5c5130168..691891c0b350 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -349,18 +349,19 @@ class GfxMod(FreeText, LADXROption): normal = '' default = 'Link' + __spriteDir: str = Utils.local_path(os.path.join('data', 'sprites','ladx')) __spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list) - __spriteDir: str = None extensions = [".bin", ".bdiff", ".png", ".bmp"] + + for file in os.listdir(__spriteDir): + name, extension = os.path.splitext(file) + if extension in extensions: + __spriteFiles[name].append(file) + def __init__(self, value: str): super().__init__(value) - if not GfxMod.__spriteDir: - GfxMod.__spriteDir = Utils.local_path(os.path.join('data', 'sprites','ladx')) - for file in os.listdir(GfxMod.__spriteDir): - name, extension = os.path.splitext(file) - if extension in self.extensions: - GfxMod.__spriteFiles[name].append(file) + def verify(self, world, player_name: str, plando_options) -> None: if self.value == "Link" or self.value in GfxMod.__spriteFiles: From 39a92e98c6a42070871af9e99447125f7b3e9224 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 3 Dec 2023 18:06:11 -0500 Subject: [PATCH 18/56] Lingo: Default color shuffle to on (#2548) * Lingo: Default color shuffle on * Raise error if no progression in multiworld --- worlds/lingo/__init__.py | 10 ++++++++++ worlds/lingo/options.py | 2 +- worlds/lingo/test/TestDoors.py | 9 ++++++--- worlds/lingo/test/TestProgressive.py | 3 ++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index da8a246e79c0..a8dac8622162 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -1,6 +1,8 @@ """ Archipelago init file for Lingo """ +from logging import warning + from BaseClasses import Item, ItemClassification, Tutorial from worlds.AutoWorld import WebWorld, World from .items import ALL_ITEM_TABLE, LingoItem @@ -49,6 +51,14 @@ class LingoWorld(World): player_logic: LingoPlayerLogic def generate_early(self): + if not (self.options.shuffle_doors or self.options.shuffle_colors): + 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 or Color Shuffle if that doesn't seem right.") + else: + raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any" + f" progression items. Please turn on Door Shuffle or Color Shuffle.") + self.player_logic = LingoPlayerLogic(self) def create_regions(self): diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py index fc9ddee0e0e9..c00208621f9e 100644 --- a/worlds/lingo/options.py +++ b/worlds/lingo/options.py @@ -32,7 +32,7 @@ class LocationChecks(Choice): option_insanity = 2 -class ShuffleColors(Toggle): +class ShuffleColors(DefaultOnToggle): """If on, an item is added to the pool for every puzzle color (besides White). You will need to unlock the requisite colors in order to be able to solve puzzles of that color.""" display_name = "Shuffle Colors" diff --git a/worlds/lingo/test/TestDoors.py b/worlds/lingo/test/TestDoors.py index 5dc989af5989..f496c5f5785a 100644 --- a/worlds/lingo/test/TestDoors.py +++ b/worlds/lingo/test/TestDoors.py @@ -3,7 +3,8 @@ class TestRequiredRoomLogic(LingoTestBase): options = { - "shuffle_doors": "complex" + "shuffle_doors": "complex", + "shuffle_colors": "false", } def test_pilgrim_first(self) -> None: @@ -49,7 +50,8 @@ def test_hidden_first(self) -> None: class TestRequiredDoorLogic(LingoTestBase): options = { - "shuffle_doors": "complex" + "shuffle_doors": "complex", + "shuffle_colors": "false", } def test_through_rhyme(self) -> None: @@ -76,7 +78,8 @@ def test_through_hidden(self) -> None: class TestSimpleDoors(LingoTestBase): options = { - "shuffle_doors": "simple" + "shuffle_doors": "simple", + "shuffle_colors": "false", } def test_requirement(self): diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index 026971c45d65..917c6e7e8939 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -81,7 +81,8 @@ def test_item(self): class TestProgressiveArtGallery(LingoTestBase): options = { - "shuffle_doors": "complex" + "shuffle_doors": "complex", + "shuffle_colors": "false", } def test_item(self): From b7111eeccc0873d158934537adf3e9eb64044648 Mon Sep 17 00:00:00 2001 From: el-u <109771707+el-u@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:06:52 +0100 Subject: [PATCH 19/56] lufia2ac: fix disappearing Ancient key (#2537) Since the coop update, the Ancient key (which is always the reward for defeating the boss) would disappear when leaving the cave, making it impossible to open the locked door behind the Ancient Cave entrance counter. While this is basically cosmetic and has no adverse effects on the multiworld (as the door does not lead to any multiworld locations and is only accessible after defeating the final boss anyway), players may still want to enter this room as part of a ritual to celebrate their victory. Why does this happen? The game keeps track of two different inventories, one for outside and another one for the cave dive. When entering or leaving the cave, important things such as blue chest items and Iris treasures are automatically copied to the other inventory. However, it turns out that the Ancient key doesn't participate in this mechanism. Instead, the script that runs when exiting the cave checks whether event flag 0xC3 is set, and if it is on, it calls a script action that adds the key item to the outside inventory. (Whether or not the player actually had the key item in their in-cave inventory is not checked at all; only the flag matters.) In the unmodified game, that flag is set by the cutscene script that awards the key. It actually sets two event flags, 0xC3 and 0xD1. The latter is used by the game when trying to display the boss in the cafe basement and is used by AP as the indicator that the boss goal was completed. With the coop update, the event script method that created the key was intercepted and modified to send out a location check instead. That location always has the Ancient key as a fixed item placement; the benefit of handling it as a remote item is that in this way the key essentially serves as a signal that transmits the information of the boss' defeat to all clients cooping on the slot. When receiving the key, however, the custom ASM did only set flag 0xD1. As part of the bugfix, it is now changed to set flag 0xC3 as well. But that alone is still not enough to make it work. The subroutine that is called by the game to create the key when exiting the cave with flag 0xC3 is the same subroutine that gets called in the cutscene that originally tried to award the key. But that's the one that has been rewritten to send the location check instead. So instead of creating the key when leaving the cave, it would just send the same location check again, effectively doing nothing. Therefore, the other part of the bugfix is to only intercept this subroutine if the player is currently on the Ancient Cave Final Floor (where the cutscene takes place), thus making it possible to recreate the key item when exiting. --- worlds/lufia2ac/basepatch/basepatch.asm | 6 ++++++ worlds/lufia2ac/basepatch/basepatch.bsdiff4 | Bin 8638 -> 8652 bytes 2 files changed, 6 insertions(+) diff --git a/worlds/lufia2ac/basepatch/basepatch.asm b/worlds/lufia2ac/basepatch/basepatch.asm index f298a1129d93..f9c48a5fecd1 100644 --- a/worlds/lufia2ac/basepatch/basepatch.asm +++ b/worlds/lufia2ac/basepatch/basepatch.asm @@ -170,6 +170,9 @@ pullpc ScriptTX: STA $7FD4F1 ; (overwritten instruction) + LDA $05AC ; load map number + CMP.b #$F1 ; check if ancient cave final floor + BNE + REP #$20 LDA $7FD4EF ; read script item id CMP.w #$01C2 ; test for ancient key @@ -261,6 +264,9 @@ SpecialItemGet: BRA ++ +: CMP.w #$01C2 ; ancient key BNE + + LDA.w #$0008 + ORA $0796 + STA $0796 ; set ancient key EV flag ($C3) LDA.w #$0200 ORA $0797 STA $0797 ; set boss item EV flag ($D1) diff --git a/worlds/lufia2ac/basepatch/basepatch.bsdiff4 b/worlds/lufia2ac/basepatch/basepatch.bsdiff4 index 4ed1815039a04c4f3c1ce8a6c5a28ddfda86f96e..664e197c4a1929f6958c1245b11750716b7a9d7e 100644 GIT binary patch literal 8652 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 literal 8638 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 Date: Mon, 4 Dec 2023 16:26:00 +0100 Subject: [PATCH 20/56] The Witness: Fix various incorrect symbol requirements in Vanilla Puzzles (#2543) * Fix Vanilla First Floor Left * More vanilla logic fixes --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/witness/WitnessLogicVanilla.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/WitnessLogicVanilla.txt index 719eae6c4e56..8591a30d1fbb 100644 --- a/worlds/witness/WitnessLogicVanilla.txt +++ b/worlds/witness/WitnessLogicVanilla.txt @@ -257,7 +257,7 @@ Quarry Stoneworks Middle Floor (Quarry Stoneworks) - Quarry Stoneworks Lift - Tr 158125 - 0x00E0C (Lower Row 1) - True - Dots & Eraser 158126 - 0x01489 (Lower Row 2) - 0x00E0C - Dots & Eraser 158127 - 0x0148A (Lower Row 3) - 0x01489 - Dots & Eraser -158128 - 0x014D9 (Lower Row 4) - 0x0148A - Dots & Eraser +158128 - 0x014D9 (Lower Row 4) - 0x0148A - Dots & Full Dots & Eraser 158129 - 0x014E7 (Lower Row 5) - 0x014D9 - Dots 158130 - 0x014E8 (Lower Row 6) - 0x014E7 - Dots & Eraser @@ -307,9 +307,9 @@ Quarry Boathouse Upper Middle (Quarry Boathouse) - Quarry Boathouse Upper Back - Quarry Boathouse Upper Back (Quarry Boathouse) - Quarry Boathouse Upper Middle - 0x3865F: 158155 - 0x38663 (Second Barrier Panel) - True - True Door - 0x3865F (Second Barrier) - 0x38663 -158156 - 0x021B5 (Back First Row 1) - True - Stars & Stars + Same Colored Symbol & Eraser +158156 - 0x021B5 (Back First Row 1) - True - Stars & Eraser 158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars & Stars + Same Colored Symbol & Eraser -158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars & Stars + Same Colored Symbol & Eraser +158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars & Eraser 158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars & Stars + Same Colored Symbol & Eraser 158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars & Stars + Same Colored Symbol & Eraser 158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars & Stars + Same Colored Symbol & Eraser @@ -427,7 +427,7 @@ Keep Tower (Keep) - Keep - 0x04F8F: 158206 - 0x0361B (Tower Shortcut Panel) - True - True Door - 0x04F8F (Tower Shortcut) - 0x0361B 158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Rotated Shapers +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers Laser - 0x014BB (Laser) - 0x0360E | 0x03317 159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True 159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True @@ -516,13 +516,13 @@ Town Red Rooftop (Town): 158607 - 0x17C71 (Rooftop Discard) - True - Triangles 158230 - 0x28AC7 (Red Rooftop 1) - True - Symmetry & Black/White Squares 158231 - 0x28AC8 (Red Rooftop 2) - 0x28AC7 - Symmetry & Black/White Squares -158232 - 0x28ACA (Red Rooftop 3) - 0x28AC8 - Symmetry & Black/White Squares & Dots +158232 - 0x28ACA (Red Rooftop 3) - 0x28AC8 - Symmetry & Black/White Squares 158233 - 0x28ACB (Red Rooftop 4) - 0x28ACA - Symmetry & Black/White Squares & Dots 158234 - 0x28ACC (Red Rooftop 5) - 0x28ACB - Symmetry & Black/White Squares & Dots 158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - True Town Wooden Rooftop (Town): -158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Dots & Eraser & Full Dots +158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Shapers & Dots & Eraser & Full Dots Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True @@ -740,7 +740,7 @@ Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underw 158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers 158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers 158331 - 0x00C2E (Beyond Rotating Bridge 3) - 0x00A1E - Rotated Shapers & Shapers -158332 - 0x00E3A (Beyond Rotating Bridge 4) - 0x00C2E - Rotated Shapers +158332 - 0x00E3A (Beyond Rotating Bridge 4) - 0x00C2E - Rotated Shapers & Shapers Door - 0x18482 (Blue Water Pump) - 0x00E3A 159332 - 0x3365F (Boat EP) - 0x09DB8 - True 159333 - 0x03731 (Long Bridge Side EP) - 0x17E2B - True @@ -859,7 +859,7 @@ Treehouse Green Bridge (Treehouse) - Treehouse Green Bridge Front House - 0x17E6 158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Rotated Shapers 158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Rotated Shapers 158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Stars + Same Colored Symbol -158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol & Rotated Shapers +158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Negative Shapers & Rotated Shapers 158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Rotated Shapers Treehouse Green Bridge Front House (Treehouse): @@ -917,10 +917,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158416 - 0x09E78 (Left Row 3) - 0x09E75 - Dots & Shapers 158417 - 0x09E79 (Left Row 4) - 0x09E78 - Shapers & Rotated Shapers 158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Black/White Squares -158419 - 0x09E6F (Left Row 6) - 0x09E6C - Shapers +158419 - 0x09E6F (Left Row 6) - 0x09E6C - Shapers & Dots 158420 - 0x09E6B (Left Row 7) - 0x09E6F - Dots 158421 - 0x33AF5 (Back Row 1) - True - Black/White Squares & Symmetry -158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Black/White Squares & Stars +158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Black/White Squares 158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Dots 158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers @@ -933,7 +933,7 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Dots 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol 158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers -158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares & Symmetry +158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares Door - 0x09FFB (Staircase Near) - 0x09FD8 Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: @@ -1009,8 +1009,8 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: 158469 - 0x009A4 (Blue Tunnel Left Third 1) - True - Shapers 158470 - 0x018A0 (Blue Tunnel Right Third 1) - True - Shapers & Symmetry 158471 - 0x00A72 (Blue Tunnel Left Fourth 1) - True - Shapers & Negative Shapers -158472 - 0x32962 (First Floor Left) - True - Rotated Shapers -158473 - 0x32966 (First Floor Grounded) - True - Stars & Black/White Squares & Stars + Same Colored Symbol +158472 - 0x32962 (First Floor Left) - True - Rotated Shapers & Shapers +158473 - 0x32966 (First Floor Grounded) - True - Stars & Black/White Squares 158474 - 0x01A31 (First Floor Middle) - True - Colored Squares 158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars & Stars + Same Colored Symbol & Eraser 158478 - 0x288EA (First Wooden Beam) - True - Rotated Shapers From 229a263131370570e29f027df7e3fb5a55a0f834 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:17:27 +0100 Subject: [PATCH 21/56] The Witness: Fix logic error with Symmetry Island Upper in doors: panels (broken seed reported) (#2565) Door entities think they can be solved without any other panels needing to be solved. Usually, this is true, because they no longer need to be "powered on" by a previous panel. However, there are some entities that need another entity to be powered/solved for a different reason. In this case, Symmetry Island Lower Left set opens the latches that block your ability to solve the panel. The panel itself actually starts on. Playing doors: panels does not change this, unlike usually where dependencies like this get removed by playing that mode. In the long term, I want to somehow be able to "mark" dependencies as "environmental" or "power based" so I can distinguish them properly. --- worlds/witness/player_logic.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index cfd36c09be24..a4a5b04d896a 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -70,15 +70,19 @@ def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]: for items_option in these_items: all_options.add(items_option.union(dependentItem)) - # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved... - if panel_hex != "0x28A0D": - return frozenset(all_options) - # ...except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. + # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved, + # except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. # In the future, it would be wise to make a distinction between "power dependencies" and other dependencies. - if any("0x28998" in option for option in these_panels): - return frozenset(all_options) + if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels): + these_items = all_options + + # Another dependency that is not power-based: The Symmetry Island Upper Panel latches + elif panel_hex == 0x18269: + these_items = all_options - these_items = all_options + # For any other door entity, we just return a set with the item that opens it & disregard power dependencies + else: + return frozenset(all_options) disabled_eps = {eHex for eHex in self.COMPLETELY_DISABLED_ENTITIES if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[eHex]["entityType"] == "EP"} From 530617c9a7099c94ac7649f260809e942ef296a6 Mon Sep 17 00:00:00 2001 From: Yussur Mustafa Oraji Date: Wed, 6 Dec 2023 18:19:03 +0100 Subject: [PATCH 22/56] sm64ex: Refactor Regions (#2546) Refactors region code to remove references to course index. There were bugs somewhere, but I dont know where tbh. This fixes them but leaves logic otherwise intact, and much cleaner to look at as there's one list less to take care of. Additionally, this fixes stopping the clock from Big Boos Haunt. --- worlds/sm64ex/Regions.py | 67 +++++++++++++-------- worlds/sm64ex/Rules.py | 119 ++++++++++++++++++++------------------ worlds/sm64ex/__init__.py | 11 ++-- 3 files changed, 111 insertions(+), 86 deletions(-) diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index c2e9e2d98115..d0e767e7ecde 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -5,26 +5,43 @@ locHMC_table, locLLL_table, locSSL_table, locDDD_table, locSL_table, \ locWDW_table, locTTM_table, locTHI_table, locTTC_table, locRR_table, \ locPSS_table, locSA_table, locBitDW_table, locTotWC_table, locCotMC_table, \ - locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table - -# List of all courses, including secrets, without BitS as that one is static -sm64courses = ["Bob-omb Battlefield", "Whomp's Fortress", "Jolly Roger Bay", "Cool, Cool Mountain", "Big Boo's Haunt", - "Hazy Maze Cave", "Lethal Lava Land", "Shifting Sand Land", "Dire, Dire Docks", "Snowman's Land", - "Wet-Dry World", "Tall, Tall Mountain", "Tiny-Huge Island", "Tick Tock Clock", "Rainbow Ride", - "The Princess's Secret Slide", "The Secret Aquarium", "Bowser in the Dark World", "Tower of the Wing Cap", - "Cavern of the Metal Cap", "Vanish Cap under the Moat", "Bowser in the Fire Sea", "Wing Mario over the Rainbow"] - -# sm64paintings is list of entrances, format LEVEL | AREA. String Reference below -sm64paintings = [91,241,121,51,41,71,221,81,231,101,111,361,132,131,141,151] -sm64paintings_s = ["BOB", "WF", "JRB", "CCM", "BBH", "HMC", "LLL", "SSL", "DDD", "SL", "WDW", "TTM", "THI Tiny", "THI Huge", "TTC", "RR"] -# sm64secrets is list of secret areas -sm64secrets = [271, 201, 171, 291, 281, 181, 191, 311] -sm64secrets_s = ["PSS", "SA", "BitDW", "TOTWC", "COTMC", "VCUTM", "BitFS", "WMOTR"] - -sm64entrances = sm64paintings + sm64secrets -sm64entrances_s = sm64paintings_s + sm64secrets_s -sm64_internalloc_to_string = dict(zip(sm64paintings+sm64secrets, sm64entrances_s)) -sm64_internalloc_to_regionid = dict(zip(sm64paintings+sm64secrets, list(range(13)) + [12,13,14] + list(range(15,15+len(sm64secrets))))) + locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table + +# sm64paintings is dict of entrances, format LEVEL | AREA +sm64_level_to_paintings = { + 91: "Bob-omb Battlefield", + 241: "Whomp's Fortress", + 121: "Jolly Roger Bay", + 51: "Cool, Cool Mountain", + 41: "Big Boo's Haunt", + 71: "Hazy Maze Cave", + 221: "Lethal Lava Land", + 81: "Shifting Sand Land", + 231: "Dire, Dire Docks", + 101: "Snowman's Land", + 111: "Wet-Dry World", + 361: "Tall, Tall Mountain", + 132: "Tiny-Huge Island (Tiny)", + 131: "Tiny-Huge Island (Huge)", + 141: "Tick Tock Clock", + 151: "Rainbow Ride" +} +sm64_paintings_to_level = { painting: level for (level,painting) in sm64_level_to_paintings.items() } +# sm64secrets is list of secret areas, same format +sm64_level_to_secrets = { + 271: "The Princess's Secret Slide", + 201: "The Secret Aquarium", + 171: "Bowser in the Dark World", + 291: "Tower of the Wing Cap", + 281: "Cavern of the Metal Cap", + 181: "Vanish Cap under the Moat", + 191: "Bowser in the Fire Sea", + 311: "Wing Mario over the Rainbow" +} +sm64_secrets_to_level = { secret: level for (level,secret) in sm64_level_to_secrets.items() } + +sm64_entrances_to_level = { **sm64_paintings_to_level, **sm64_secrets_to_level } +sm64_level_to_entrances = { **sm64_level_to_paintings, **sm64_level_to_secrets } def create_regions(world: MultiWorld, player: int): regSS = Region("Menu", player, world, "Castle Area") @@ -137,11 +154,13 @@ def create_regions(world: MultiWorld, player: int): regTTM.locations.append(SM64Location(player, "TTM: 100 Coins", location_table["TTM: 100 Coins"], regTTM)) world.regions.append(regTTM) - regTHI = create_region("Tiny-Huge Island", player, world) - create_default_locs(regTHI, locTHI_table, player) + regTHIT = create_region("Tiny-Huge Island (Tiny)", player, world) + create_default_locs(regTHIT, locTHI_table, player) if (world.EnableCoinStars[player].value): - regTHI.locations.append(SM64Location(player, "THI: 100 Coins", location_table["THI: 100 Coins"], regTHI)) - world.regions.append(regTHI) + regTHIT.locations.append(SM64Location(player, "THI: 100 Coins", location_table["THI: 100 Coins"], regTHIT)) + world.regions.append(regTHIT) + regTHIH = create_region("Tiny-Huge Island (Huge)", player, world) + world.regions.append(regTHIH) regFloor3 = create_region("Third Floor", player, world) world.regions.append(regFloor3) diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index 27b5fc8f7e38..d21ac30004e0 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -1,77 +1,84 @@ from ..generic.Rules import add_rule -from .Regions import connect_regions, sm64courses, sm64paintings, sm64secrets, sm64entrances - -def fix_reg(entrance_ids, reg, invalidspot, swaplist, world): - if entrance_ids.index(reg) == invalidspot: # Unlucky :C - swaplist.remove(invalidspot) - rand = world.random.choice(swaplist) - entrance_ids[invalidspot], entrance_ids[rand] = entrance_ids[rand], entrance_ids[invalidspot] - swaplist.append(invalidspot) - swaplist.remove(rand) - -def set_rules(world, player: int, area_connections): - destination_regions = list(range(13)) + [12,13,14] + list(range(15,15+len(sm64secrets))) # Two instances of Destination Course THI. Past normal course idx are secret regions - secret_entrance_ids = list(range(len(sm64paintings), len(sm64paintings) + len(sm64secrets))) - course_entrance_ids = list(range(len(sm64paintings))) - if world.AreaRandomizer[player].value >= 1: # Some randomization is happening, randomize Courses - world.random.shuffle(course_entrance_ids) +from .Regions import connect_regions, sm64_level_to_paintings, sm64_paintings_to_level, sm64_level_to_secrets, sm64_entrances_to_level, sm64_level_to_entrances + +def shuffle_dict_keys(world, obj: dict) -> dict: + keys = list(obj.keys()) + values = list(obj.values()) + world.random.shuffle(keys) + return dict(zip(keys,values)) + +def fix_reg(entrance_ids, entrance, destination, swapdict, world): + if entrance_ids[entrance] == destination: # Unlucky :C + rand = world.random.choice(swapdict.keys()) + entrance_ids[entrance], entrance_ids[swapdict[rand]] = rand, entrance_ids[entrance] + swapdict[rand] = entrance_ids[entrance] + swapdict.pop(entrance) + +def set_rules(world, player: int, area_connections: dict): + randomized_level_to_paintings = sm64_level_to_paintings.copy() + randomized_level_to_secrets = sm64_level_to_secrets.copy() + if world.AreaRandomizer[player].value == 1: # Some randomization is happening, randomize Courses + randomized_level_to_paintings = shuffle_dict_keys(world,sm64_level_to_paintings) if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well - world.random.shuffle(secret_entrance_ids) - entrance_ids = course_entrance_ids + secret_entrance_ids + randomized_level_to_secrets = shuffle_dict_keys(world,sm64_level_to_secrets) + randomized_entrances = { **randomized_level_to_paintings, **randomized_level_to_secrets } if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool - world.random.shuffle(entrance_ids) + randomized_entrances = shuffle_dict_keys(world,randomized_entrances) # Guarantee first entrance is a course - swaplist = list(range(len(entrance_ids))) - if entrance_ids.index(0) > 15: # Unlucky :C - rand = world.random.randint(0,15) - entrance_ids[entrance_ids.index(0)], entrance_ids[rand] = entrance_ids[rand], entrance_ids[entrance_ids.index(0)] - swaplist.remove(entrance_ids.index(0)) + swapdict = { entrance: level for (level,entrance) in randomized_entrances } + if randomized_entrances[91] not in sm64_paintings_to_level.keys(): # Unlucky :C (91 -> BoB Entrance) + rand = world.random.choice(sm64_paintings_to_level.values()) + randomized_entrances[91], randomized_entrances[swapdict[rand]] = rand, randomized_entrances[91] + swapdict[rand] = randomized_entrances[91] + swapdict.pop("Bob-omb Battlefield") # Guarantee COTMC is not mapped to HMC, cuz thats impossible - fix_reg(entrance_ids, 20, 5, swaplist, world) + fix_reg(randomized_entrances, "Cavern of the Metal Cap", "Hazy Maze Cave", swapdict, world) # Guarantee BITFS is not mapped to DDD - fix_reg(entrance_ids, 22, 8, swaplist, world) - if entrance_ids.index(22) == 5: # If BITFS is mapped to HMC... - fix_reg(entrance_ids, 20, 8, swaplist, world) # ... then dont allow COTMC to be mapped to DDD - temp_assign = dict(zip(entrance_ids,destination_regions)) # Used for Rules only + fix_reg(randomized_entrances, "Bowser in the Fire Sea", "Dire, Dire Docks", swapdict, world) + if randomized_entrances[191] == "Hazy Maze Cave": # If BITFS is mapped to HMC... + fix_reg(randomized_entrances, "Cavern of the Metal Cap", "Dire, Dire Docks", swapdict, world) # ... then dont allow COTMC to be mapped to DDD # Destination Format: LVL | AREA with LVL = LEVEL_x, AREA = Area as used in sm64 code - area_connections.update({sm64entrances[entrance]: destination for entrance, destination in zip(entrance_ids,sm64entrances)}) + area_connections.update({entrance_lvl: sm64_entrances_to_level[destination] for (entrance_lvl,destination) in randomized_entrances.items()}) + randomized_entrances_s = {sm64_level_to_entrances[entrance_lvl]: destination for (entrance_lvl,destination) in randomized_entrances.items()} - connect_regions(world, player, "Menu", sm64courses[temp_assign[0]]) # BOB - connect_regions(world, player, "Menu", sm64courses[temp_assign[1]], lambda state: state.has("Power Star", player, 1)) # WF - connect_regions(world, player, "Menu", sm64courses[temp_assign[2]], lambda state: state.has("Power Star", player, 3)) # JRB - connect_regions(world, player, "Menu", sm64courses[temp_assign[3]], lambda state: state.has("Power Star", player, 3)) # CCM - connect_regions(world, player, "Menu", sm64courses[temp_assign[4]], lambda state: state.has("Power Star", player, 12)) # BBH - connect_regions(world, player, "Menu", sm64courses[temp_assign[16]], lambda state: state.has("Power Star", player, 1)) # PSS - connect_regions(world, player, "Menu", sm64courses[temp_assign[17]], lambda state: state.has("Power Star", player, 3)) # SA - connect_regions(world, player, "Menu", sm64courses[temp_assign[19]], lambda state: state.has("Power Star", player, 10)) # TOTWC - connect_regions(world, player, "Menu", sm64courses[temp_assign[18]], lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value)) # BITDW + connect_regions(world, player, "Menu", randomized_entrances_s["Bob-omb Battlefield"]) + connect_regions(world, player, "Menu", randomized_entrances_s["Whomp's Fortress"], lambda state: state.has("Power Star", player, 1)) + connect_regions(world, player, "Menu", randomized_entrances_s["Jolly Roger Bay"], lambda state: state.has("Power Star", player, 3)) + connect_regions(world, player, "Menu", randomized_entrances_s["Cool, Cool Mountain"], lambda state: state.has("Power Star", player, 3)) + connect_regions(world, player, "Menu", randomized_entrances_s["Big Boo's Haunt"], lambda state: state.has("Power Star", player, 12)) + connect_regions(world, player, "Menu", randomized_entrances_s["The Princess's Secret Slide"], lambda state: state.has("Power Star", player, 1)) + connect_regions(world, player, "Menu", randomized_entrances_s["The Secret Aquarium"], lambda state: state.has("Power Star", player, 3)) + connect_regions(world, player, "Menu", randomized_entrances_s["Tower of the Wing Cap"], lambda state: state.has("Power Star", player, 10)) + connect_regions(world, player, "Menu", randomized_entrances_s["Bowser in the Dark World"], lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value)) connect_regions(world, player, "Menu", "Basement", lambda state: state.has("Basement Key", player) or state.has("Progressive Key", player, 1)) - connect_regions(world, player, "Basement", sm64courses[temp_assign[5]]) # HMC - connect_regions(world, player, "Basement", sm64courses[temp_assign[6]]) # LLL - connect_regions(world, player, "Basement", sm64courses[temp_assign[7]]) # SSL - connect_regions(world, player, "Basement", sm64courses[temp_assign[8]], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value)) # DDD - connect_regions(world, player, "Hazy Maze Cave", sm64courses[temp_assign[20]]) # COTMC - connect_regions(world, player, "Basement", sm64courses[temp_assign[21]]) # VCUTM - connect_regions(world, player, "Basement", sm64courses[temp_assign[22]], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value) and - state.can_reach("DDD: Board Bowser's Sub", 'Location', player)) # BITFS + connect_regions(world, player, "Basement", randomized_entrances_s["Hazy Maze Cave"]) + connect_regions(world, player, "Basement", randomized_entrances_s["Lethal Lava Land"]) + connect_regions(world, player, "Basement", randomized_entrances_s["Shifting Sand Land"]) + connect_regions(world, player, "Basement", randomized_entrances_s["Dire, Dire Docks"], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value)) + connect_regions(world, player, "Hazy Maze Cave", randomized_entrances_s["Cavern of the Metal Cap"]) + connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"]) + connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value) and + state.can_reach("DDD: Board Bowser's Sub", 'Location', player)) connect_regions(world, player, "Menu", "Second Floor", lambda state: state.has("Second Floor Key", player) or state.has("Progressive Key", player, 2)) - connect_regions(world, player, "Second Floor", sm64courses[temp_assign[9]]) # SL - connect_regions(world, player, "Second Floor", sm64courses[temp_assign[10]]) # WDW - connect_regions(world, player, "Second Floor", sm64courses[temp_assign[11]]) # TTM - connect_regions(world, player, "Second Floor", sm64courses[temp_assign[12]]) # THI Tiny - connect_regions(world, player, "Second Floor", sm64courses[temp_assign[13]]) # THI Huge + connect_regions(world, player, "Second Floor", randomized_entrances_s["Snowman's Land"]) + connect_regions(world, player, "Second Floor", randomized_entrances_s["Wet-Dry World"]) + connect_regions(world, player, "Second Floor", randomized_entrances_s["Tall, Tall Mountain"]) + connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Tiny)"]) + connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Huge)"]) + connect_regions(world, player, "Tiny-Huge Island (Tiny)", "Tiny-Huge Island (Huge)") + connect_regions(world, player, "Tiny-Huge Island (Huge)", "Tiny-Huge Island (Tiny)") connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, world.SecondFloorStarDoorCost[player].value)) - connect_regions(world, player, "Third Floor", sm64courses[temp_assign[14]]) # TTC - connect_regions(world, player, "Third Floor", sm64courses[temp_assign[15]]) # RR - connect_regions(world, player, "Third Floor", sm64courses[temp_assign[23]]) # WMOTR - connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, world.StarsToFinish[player].value)) # BITS + connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"]) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"]) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"]) + connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, world.StarsToFinish[player].value)) #Special Rules for some Locations add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Cannon Unlock BoB", player)) diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index 3cc87708e723..ab7409a324c3 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -5,7 +5,7 @@ from .Locations import location_table, SM64Location from .Options import sm64_options from .Rules import set_rules -from .Regions import create_regions, sm64courses, sm64entrances_s, sm64_internalloc_to_string, sm64_internalloc_to_regionid +from .Regions import create_regions, sm64_level_to_entrances from BaseClasses import Item, Tutorial, ItemClassification from ..AutoWorld import World, WebWorld @@ -55,8 +55,8 @@ def set_rules(self): # Write area_connections to spoiler log for entrance, destination in self.area_connections.items(): self.multiworld.spoiler.set_entrance( - sm64_internalloc_to_string[entrance] + " Entrance", - sm64_internalloc_to_string[destination], + sm64_level_to_entrances[entrance] + " Entrance", + sm64_level_to_entrances[destination], 'entrance', self.player) def create_item(self, name: str) -> Item: @@ -182,8 +182,7 @@ def modify_multidata(self, multidata): if self.topology_present: er_hint_data = {} for entrance, destination in self.area_connections.items(): - regionid = sm64_internalloc_to_regionid[destination] - region = self.multiworld.get_region(sm64courses[regionid], self.player) + region = self.multiworld.get_region(sm64_level_to_entrances[destination], self.player) for location in region.locations: - er_hint_data[location.address] = sm64_internalloc_to_string[entrance] + er_hint_data[location.address] = sm64_level_to_entrances[entrance] multidata['er_hint_data'][self.player] = er_hint_data From 49e1fd0b79f9efc053929203a0e32c68b2b8c0a0 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Wed, 6 Dec 2023 11:20:18 -0600 Subject: [PATCH 23/56] The Messenger: ease rule on key of strength a bit (#2541) Makes the logic for accessing key of strength just a tiny bit easier since a few players said it was really difficult. --- worlds/messenger/rules.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/worlds/messenger/rules.py b/worlds/messenger/rules.py index 793de50afb70..b13a453f7f59 100644 --- a/worlds/messenger/rules.py +++ b/worlds/messenger/rules.py @@ -63,7 +63,10 @@ def __init__(self, world: "MessengerWorld") -> None: "Searing Crags Seal - Triple Ball Spinner": self.has_vertical, "Searing Crags - Astral Tea Leaves": lambda state: state.can_reach("Ninja Village - Astral Seed", "Location", self.player), - "Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player), + "Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player) + and (self.has_dart(state) + or (self.has_wingsuit(state) + and self.can_destroy_projectiles(state))), # glacial peak "Glacial Peak Seal - Ice Climbers": self.has_dart, "Glacial Peak Seal - Projectile Spike Pit": self.can_destroy_projectiles, From 597f94dc22f37f532a3f5d61b87af888812a4870 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:22:11 +0100 Subject: [PATCH 24/56] The Witness: Add all the Challenge panels to Challenge exclusion list (#2564) Just a small cleanup where right now, the logic still considers the entirety of the challenge "solvable" except for Challenge Vault Box --- .../settings/Postgame/Challenge_Vault_Box.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt b/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt index d65900418c61..8b431694b3b4 100644 --- a/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt +++ b/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt @@ -1,3 +1,22 @@ Disabled Locations: 0x0356B (Challenge Vault Box) 0x04D75 (Vault Door) +0x0A332 (Start Timer) +0x0088E (Small Basic) +0x00BAF (Big Basic) +0x00BF3 (Square) +0x00C09 (Maze Map) +0x00CDB (Stars and Dots) +0x0051F (Symmetry) +0x00524 (Stars and Shapers) +0x00CD4 (Big Basic 2) +0x00CB9 (Choice Squares Right) +0x00CA1 (Choice Squares Middle) +0x00C80 (Choice Squares Left) +0x00C68 (Choice Squares 2 Right) +0x00C59 (Choice Squares 2 Middle) +0x00C22 (Choice Squares 2 Left) +0x034F4 (Maze Hidden 1) +0x034EC (Maze Hidden 2) +0x1C31A (Dots Pillar) +0x1C319 (Squares Pillar) From d8004f82ef0ebb340231fcf1c1977981b4f6b443 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Wed, 6 Dec 2023 09:23:43 -0800 Subject: [PATCH 25/56] Zillion: some typing fixes (#2534) `colorama` has type stubs when it didn't before `ZillionDeltaPatch.hash` annotated type could be `None` but md5s doesn't allow `None` type of `CollectionState.prog_items` changed `WorldTestBase` moved all of the following are related to this issue: https://github.com/python/typing/discussions/1486 CommonContext for `command_processor` (is invalid without specifying immutable - but I don't need it anyway) ZillionWorld options and settings (is invalid without specifying immutable - but I do need it) --- ZillionClient.py | 6 +++--- worlds/zillion/__init__.py | 7 +++++-- worlds/zillion/logic.py | 2 +- worlds/zillion/test/__init__.py | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ZillionClient.py b/ZillionClient.py index 7d32a722615e..30f4f600a672 100644 --- a/ZillionClient.py +++ b/ZillionClient.py @@ -1,7 +1,7 @@ import asyncio import base64 import platform -from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, Type, cast +from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, cast # CommonClient import first to trigger ModuleUpdater from CommonClient import CommonContext, server_loop, gui_enabled, \ @@ -10,7 +10,7 @@ import Utils from Utils import async_start -import colorama # type: ignore +import colorama from zilliandomizer.zri.memory import Memory from zilliandomizer.zri import events @@ -45,7 +45,7 @@ def __call__(self, rooms: List[List[int]]) -> None: ... class ZillionContext(CommonContext): game = "Zillion" - command_processor: Type[ClientCommandProcessor] = ZillionCommandProcessor + command_processor = ZillionCommandProcessor items_handling = 1 # receive items from other players known_name: Optional[str] diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index a5e1bfe1ad5f..3f441d12ab34 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -33,6 +33,7 @@ class RomFile(settings.UserFilePath): """File name of the Zillion US rom""" description = "Zillion US ROM File" copy_to = "Zillion (UE) [!].sms" + assert ZillionDeltaPatch.hash md5s = [ZillionDeltaPatch.hash] class RomStart(str): @@ -70,9 +71,11 @@ class ZillionWorld(World): web = ZillionWebWorld() options_dataclass = ZillionOptions - options: ZillionOptions + options: ZillionOptions # type: ignore + + settings: typing.ClassVar[ZillionSettings] # type: ignore + # these type: ignore are because of this issue: https://github.com/python/typing/discussions/1486 - settings: typing.ClassVar[ZillionSettings] topology_present = True # indicate if world type has any meaningful layout/pathing # map names to their IDs diff --git a/worlds/zillion/logic.py b/worlds/zillion/logic.py index 12f1875b4047..305546c78b62 100644 --- a/worlds/zillion/logic.py +++ b/worlds/zillion/logic.py @@ -41,7 +41,7 @@ def item_counts(cs: CollectionState, p: int) -> Tuple[Tuple[str, int], ...]: return tuple((item_name, cs.count(item_name, p)) for item_name in item_name_to_id) -LogicCacheType = Dict[int, Tuple[_Counter[Tuple[str, int]], FrozenSet[Location]]] +LogicCacheType = Dict[int, Tuple[Dict[int, _Counter[str]], FrozenSet[Location]]] """ { hash: (cs.prog_items, accessible_locations) } """ diff --git a/worlds/zillion/test/__init__.py b/worlds/zillion/test/__init__.py index 3b7edebef804..93c0512fb045 100644 --- a/worlds/zillion/test/__init__.py +++ b/worlds/zillion/test/__init__.py @@ -1,5 +1,5 @@ from typing import cast -from test.TestBase import WorldTestBase +from test.bases import WorldTestBase from worlds.zillion import ZillionWorld From 56ac6573f128048bd957e246ecf85bdf502805e2 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Wed, 6 Dec 2023 18:24:13 +0100 Subject: [PATCH 26/56] WebHost: fix room shutdown (#2554) Currently when a room shuts down while clients are connected it instantly spins back up. This fixes that behaviour categorically. I still don't know why or when this problem started, but it's certainly wreaking havok on prod. --- WebHostLib/customserver.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index 998fec5e738d..fb3b314753cf 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -205,6 +205,12 @@ async def main(): ctx.auto_shutdown = Room.get(id=room_id).timeout ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [])) await ctx.shutdown_task + + # ensure auto launch is on the same page in regard to room activity. + with db_session: + room: Room = Room.get(id=ctx.room_id) + room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(seconds=room.timeout + 60) + logging.info("Shutting down") with Locker(room_id): From 87252c14aae238ead03ec76ed9b7ca71f4920428 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:24:59 -0500 Subject: [PATCH 27/56] FFMQ: Update to FFMQR 1.5 (#2568) FFMQR was just updated to 1.5, adding a number of new options. This brings these updates to AP. --- worlds/ffmq/Items.py | 1 + worlds/ffmq/Options.py | 102 +++++++++++++++++++++++++++++++- worlds/ffmq/Output.py | 80 ++++++++++++++----------- worlds/ffmq/__init__.py | 4 +- worlds/ffmq/data/entrances.yaml | 35 +++++++++-- worlds/ffmq/data/rooms.yaml | 32 +++++----- worlds/ffmq/data/settings.yaml | 43 ++++++++++++++ 7 files changed, 239 insertions(+), 58 deletions(-) diff --git a/worlds/ffmq/Items.py b/worlds/ffmq/Items.py index 7660bd5d52f3..3eab5dd532a6 100644 --- a/worlds/ffmq/Items.py +++ b/worlds/ffmq/Items.py @@ -187,6 +187,7 @@ def __init__(self, item_id, classification, groups=(), data_name=None): "Pazuzu 5F": ItemData(None, ItemClassification.progression), "Pazuzu 6F": ItemData(None, ItemClassification.progression), "Dark King": ItemData(None, ItemClassification.progression), + "Tristam Bone Item Given": ItemData(None, ItemClassification.progression), #"Barred": ItemData(None, ItemClassification.progression), } diff --git a/worlds/ffmq/Options.py b/worlds/ffmq/Options.py index 2746bb197743..eaf309749494 100644 --- a/worlds/ffmq/Options.py +++ b/worlds/ffmq/Options.py @@ -1,4 +1,4 @@ -from Options import Choice, FreeText, Toggle +from Options import Choice, FreeText, Toggle, Range class Logic(Choice): @@ -131,6 +131,21 @@ class EnemizerAttacks(Choice): default = 0 +class EnemizerGroups(Choice): + """Set which enemy groups will be affected by Enemizer.""" + display_name = "Enemizer Groups" + option_mobs_only = 0 + option_mobs_and_bosses = 1 + option_mobs_bosses_and_dark_king = 2 + default = 1 + + +class ShuffleResWeakType(Toggle): + """Resistance and Weakness types are shuffled for all enemies.""" + display_name = "Shuffle Resistance/Weakness Types" + default = 0 + + class ShuffleEnemiesPositions(Toggle): """Instead of their original position in a given map, enemies are randomly placed.""" display_name = "Shuffle Enemies' Positions" @@ -231,6 +246,81 @@ class BattlefieldsBattlesQuantities(Choice): option_random_one_through_ten = 6 +class CompanionLevelingType(Choice): + """Set how companions gain levels. + Quests: Complete each companion's individual quest for them to promote to their second version. + Quests Extended: Each companion has four exclusive quests, leveling each time a quest is completed. + Save the Crystals (All): Each time a Crystal is saved, all companions gain levels. + Save the Crystals (Individual): Each companion will level to their second version when a specific Crystal is saved. + Benjamin Level: Companions' level tracks Benjamin's.""" + option_quests = 0 + option_quests_extended = 1 + option_save_crystals_individual = 2 + option_save_crystals_all = 3 + option_benjamin_level = 4 + option_benjamin_level_plus_5 = 5 + option_benjamin_level_plus_10 = 6 + default = 0 + display_name = "Companion Leveling Type" + + +class CompanionSpellbookType(Choice): + """Update companions' spellbook. + Standard: Original game spellbooks. + Standard Extended: Add some extra spells. Tristam gains Exit and Quake and Reuben gets Blizzard. + Random Balanced: Randomize the spellbooks with an appropriate mix of spells. + Random Chaos: Randomize the spellbooks in total free-for-all.""" + option_standard = 0 + option_standard_extended = 1 + option_random_balanced = 2 + option_random_chaos = 3 + default = 0 + display_name = "Companion Spellbook Type" + + +class StartingCompanion(Choice): + """Set a companion to start with. + Random Companion: Randomly select one companion. + Random Plus None: Randomly select a companion, with the possibility of none selected.""" + display_name = "Starting Companion" + default = 0 + option_none = 0 + option_kaeli = 1 + option_tristam = 2 + option_phoebe = 3 + option_reuben = 4 + option_random_companion = 5 + option_random_plus_none = 6 + + +class AvailableCompanions(Range): + """Select randomly which companions will join your party. Unavailable companions can still be reached to get their items and complete their quests if needed. + Note: If a Starting Companion is selected, it will always be available, regardless of this setting.""" + display_name = "Available Companions" + default = 4 + range_start = 0 + range_end = 4 + + +class CompanionsLocations(Choice): + """Set the primary location of companions. Their secondary location is always the same. + Standard: Companions will be at the same locations as in the original game. + Shuffled: Companions' locations are shuffled amongst themselves. + Shuffled Extended: Add all the Temples, as well as Phoebe's House and the Rope Bridge as possible locations.""" + display_name = "Companions' Locations" + default = 0 + option_standard = 0 + option_shuffled = 1 + option_shuffled_extended = 2 + + +class KaelisMomFightsMinotaur(Toggle): + """Transfer Kaeli's requirements (Tree Wither, Elixir) and the two items she's giving to her mom. + Kaeli will be available to join the party right away without the Tree Wither.""" + display_name = "Kaeli's Mom Fights Minotaur" + default = 0 + + option_definitions = { "logic": Logic, "brown_boxes": BrownBoxes, @@ -238,12 +328,21 @@ class BattlefieldsBattlesQuantities(Choice): "shattered_sky_coin_quantity": ShatteredSkyCoinQuantity, "starting_weapon": StartingWeapon, "progressive_gear": ProgressiveGear, + "leveling_curve": LevelingCurve, + "starting_companion": StartingCompanion, + "available_companions": AvailableCompanions, + "companions_locations": CompanionsLocations, + "kaelis_mom_fight_minotaur": KaelisMomFightsMinotaur, + "companion_leveling_type": CompanionLevelingType, + "companion_spellbook_type": CompanionSpellbookType, "enemies_density": EnemiesDensity, "enemies_scaling_lower": EnemiesScalingLower, "enemies_scaling_upper": EnemiesScalingUpper, "bosses_scaling_lower": BossesScalingLower, "bosses_scaling_upper": BossesScalingUpper, "enemizer_attacks": EnemizerAttacks, + "enemizer_groups": EnemizerGroups, + "shuffle_res_weak_types": ShuffleResWeakType, "shuffle_enemies_position": ShuffleEnemiesPositions, "progressive_formations": ProgressiveFormations, "doom_castle_mode": DoomCastle, @@ -253,6 +352,5 @@ class BattlefieldsBattlesQuantities(Choice): "crest_shuffle": CrestShuffle, "shuffle_battlefield_rewards": ShuffleBattlefieldRewards, "map_shuffle_seed": MapShuffleSeed, - "leveling_curve": LevelingCurve, "battlefields_battles_quantities": BattlefieldsBattlesQuantities, } diff --git a/worlds/ffmq/Output.py b/worlds/ffmq/Output.py index c4c4605c8512..98ecd28986df 100644 --- a/worlds/ffmq/Output.py +++ b/worlds/ffmq/Output.py @@ -35,46 +35,58 @@ def output_item_name(item): "item_name": location.item.name}) def cc(option): - return option.current_key.title().replace("_", "").replace("OverworldAndDungeons", "OverworldDungeons") + return option.current_key.title().replace("_", "").replace("OverworldAndDungeons", + "OverworldDungeons").replace("MobsAndBosses", "MobsBosses").replace("MobsBossesAndDarkKing", + "MobsBossesDK").replace("BenjaminLevelPlus", "BenPlus").replace("BenjaminLevel", "BenPlus0").replace( + "RandomCompanion", "Random") def tf(option): return True if option else False options = deepcopy(settings_template) options["name"] = self.multiworld.player_name[self.player] - option_writes = { - "enemies_density": cc(self.multiworld.enemies_density[self.player]), - "chests_shuffle": "Include", - "shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle", - "npcs_shuffle": "Include", - "battlefields_shuffle": "Include", - "logic_options": cc(self.multiworld.logic[self.player]), - "shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]), - "enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]), - "enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]), - "bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]), - "bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]), - "enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]), - "leveling_curve": cc(self.multiworld.leveling_curve[self.player]), - "battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if - self.multiworld.battlefields_battles_quantities[self.player].value < 5 else - "RandomLow" if - self.multiworld.battlefields_battles_quantities[self.player].value == 5 else - "RandomHigh", - "shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]), - "random_starting_weapon": True, - "progressive_gear": tf(self.multiworld.progressive_gear[self.player]), - "tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]), - "doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]), - "doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]), - "sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]), - "sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]), - "enable_spoilers": False, - "progressive_formations": cc(self.multiworld.progressive_formations[self.player]), - "map_shuffling": cc(self.multiworld.map_shuffle[self.player]), - "crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]), - } + "enemies_density": cc(self.multiworld.enemies_density[self.player]), + "chests_shuffle": "Include", + "shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle", + "npcs_shuffle": "Include", + "battlefields_shuffle": "Include", + "logic_options": cc(self.multiworld.logic[self.player]), + "shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]), + "enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]), + "enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]), + "bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]), + "bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]), + "enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]), + "leveling_curve": cc(self.multiworld.leveling_curve[self.player]), + "battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if + self.multiworld.battlefields_battles_quantities[self.player].value < 5 else + "RandomLow" if + self.multiworld.battlefields_battles_quantities[self.player].value == 5 else + "RandomHigh", + "shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]), + "random_starting_weapon": True, + "progressive_gear": tf(self.multiworld.progressive_gear[self.player]), + "tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]), + "doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]), + "doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]), + "sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]), + "sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]), + "enable_spoilers": False, + "progressive_formations": cc(self.multiworld.progressive_formations[self.player]), + "map_shuffling": cc(self.multiworld.map_shuffle[self.player]), + "crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]), + "enemizer_groups": cc(self.multiworld.enemizer_groups[self.player]), + "shuffle_res_weak_type": tf(self.multiworld.shuffle_res_weak_types[self.player]), + "companion_leveling_type": cc(self.multiworld.companion_leveling_type[self.player]), + "companion_spellbook_type": cc(self.multiworld.companion_spellbook_type[self.player]), + "starting_companion": cc(self.multiworld.starting_companion[self.player]), + "available_companions": ["Zero", "One", "Two", + "Three", "Four"][self.multiworld.available_companions[self.player].value], + "companions_locations": cc(self.multiworld.companions_locations[self.player]), + "kaelis_mom_fight_minotaur": tf(self.multiworld.kaelis_mom_fight_minotaur[self.player]), + } + for option, data in option_writes.items(): options["Final Fantasy Mystic Quest"][option][data] = 1 @@ -83,7 +95,7 @@ def tf(option): 'utf8') self.rom_name_available_event.set() - setup = {"version": "1.4", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed": + setup = {"version": "1.5", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed": hex(self.multiworld.per_slot_randoms[self.player].randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]] diff --git a/worlds/ffmq/__init__.py b/worlds/ffmq/__init__.py index b6f19a77fb53..b995cc427c9b 100644 --- a/worlds/ffmq/__init__.py +++ b/worlds/ffmq/__init__.py @@ -108,8 +108,10 @@ def stage_generate_early(cls, multiworld): map_shuffle = multiworld.map_shuffle[world.player].value crest_shuffle = multiworld.crest_shuffle[world.player].current_key battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key + companion_shuffle = multiworld.companions_locations[world.player].value + kaeli_mom = multiworld.kaelis_mom_fight_minotaur[world.player].current_key - query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}" + query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}&cs={companion_shuffle}&km={kaeli_mom}" if query in rooms_data: world.rooms = rooms_data[query] diff --git a/worlds/ffmq/data/entrances.yaml b/worlds/ffmq/data/entrances.yaml index 15bcd02bf623..1dfef2655c37 100644 --- a/worlds/ffmq/data/entrances.yaml +++ b/worlds/ffmq/data/entrances.yaml @@ -827,12 +827,12 @@ id: 164 area: 47 coordinates: [14, 6] - teleporter: [16, 2] + teleporter: [98, 8] # Script for reuben, original value [16, 2] - name: Fireburg - Hotel id: 165 area: 47 coordinates: [20, 8] - teleporter: [17, 2] + teleporter: [96, 8] # It's a script now for tristam, original value [17, 2] - name: Fireburg - GrenadeMan House Script id: 166 area: 47 @@ -1178,6 +1178,16 @@ area: 60 coordinates: [2, 7] teleporter: [123, 0] +- name: Lava Dome Pointless Room - Visit Quest Script 1 + id: 490 + area: 60 + coordinates: [4, 4] + teleporter: [99, 8] +- name: Lava Dome Pointless Room - Visit Quest Script 2 + id: 491 + area: 60 + coordinates: [4, 5] + teleporter: [99, 8] - name: Lava Dome Lower Moon Helm Room - Left Entrance id: 235 area: 60 @@ -1568,6 +1578,11 @@ area: 79 coordinates: [2, 45] teleporter: [174, 0] +- name: Mount Gale - Visit Quest + id: 494 + area: 79 + coordinates: [44, 7] + teleporter: [101, 8] - name: Windia - Main Entrance 1 id: 312 area: 80 @@ -1613,11 +1628,11 @@ area: 80 coordinates: [21, 39] teleporter: [30, 5] -- name: Windia - INN's Script # Change to teleporter +- name: Windia - INN's Script # Change to teleporter / Change back to script! id: 321 area: 80 coordinates: [18, 34] - teleporter: [31, 2] # Original value [79, 8] + teleporter: [97, 8] # Original value [79, 8] > [31, 2] - name: Windia - Vendor House id: 322 area: 80 @@ -1697,7 +1712,7 @@ id: 337 area: 82 coordinates: [45, 24] - teleporter: [215, 0] + teleporter: [102, 8] # Changed to script, original value [215, 0] - name: Windia Inn Lobby - Exit id: 338 area: 82 @@ -1998,6 +2013,16 @@ area: 95 coordinates: [29, 37] teleporter: [70, 8] +- name: Light Temple - Visit Quest Script 1 + id: 492 + area: 95 + coordinates: [34, 39] + teleporter: [100, 8] +- name: Light Temple - Visit Quest Script 2 + id: 493 + area: 95 + coordinates: [35, 39] + teleporter: [100, 8] - name: Ship Dock - Mobius Teleporter Script id: 397 area: 96 diff --git a/worlds/ffmq/data/rooms.yaml b/worlds/ffmq/data/rooms.yaml index 4343d785eb7d..e0c2e8d7f9fc 100644 --- a/worlds/ffmq/data/rooms.yaml +++ b/worlds/ffmq/data/rooms.yaml @@ -309,13 +309,13 @@ location: "WindiaBattlefield01" location_slot: "WindiaBattlefield01" type: "BattlefieldXp" - access: [] + access: ["SandCoin", "RiverCoin"] - name: "South of Windia Battlefield" object_id: 0x14 location: "WindiaBattlefield02" location_slot: "WindiaBattlefield02" type: "BattlefieldXp" - access: [] + access: ["SandCoin", "RiverCoin"] links: - target_room: 9 # Focus Tower Windia location: "FocusTowerWindia" @@ -739,7 +739,7 @@ object_id: 0x2E type: "Box" access: [] - - name: "Kaeli 1" + - name: "Kaeli Companion" object_id: 0 type: "Trigger" on_trigger: ["Kaeli1"] @@ -838,7 +838,7 @@ - name: Sand Temple id: 24 game_objects: - - name: "Tristam Sand Temple" + - name: "Tristam Companion" object_id: 0 type: "Trigger" on_trigger: ["Tristam"] @@ -883,6 +883,11 @@ object_id: 2 type: "NPC" access: ["Tristam"] + - name: "Tristam Bone Dungeon Item Given" + object_id: 0 + type: "Trigger" + on_trigger: ["TristamBoneItemGiven"] + access: ["Tristam"] links: - target_room: 25 entrance: 59 @@ -1080,7 +1085,7 @@ object_id: 0x40 type: "Box" access: [] - - name: "Phoebe" + - name: "Phoebe Companion" object_id: 0 type: "Trigger" on_trigger: ["Phoebe1"] @@ -1846,11 +1851,11 @@ access: [] - target_room: 77 entrance: 164 - teleporter: [16, 2] + teleporter: [98, 8] # original value [16, 2] access: [] - target_room: 82 entrance: 165 - teleporter: [17, 2] + teleporter: [96, 8] # original value [17, 2] access: [] - target_room: 208 access: ["Claw"] @@ -1875,7 +1880,7 @@ object_id: 14 type: "NPC" access: ["ReubenDadSaved"] - - name: "Reuben" + - name: "Reuben Companion" object_id: 0 type: "Trigger" on_trigger: ["Reuben1"] @@ -1951,12 +1956,7 @@ - name: "Fireburg - Tristam" object_id: 10 type: "NPC" - access: [] - - name: "Tristam Fireburg" - object_id: 0 - type: "Trigger" - on_trigger: ["Tristam"] - access: [] + access: ["Tristam", "TristamBoneItemGiven"] links: - target_room: 76 entrance: 177 @@ -3183,7 +3183,7 @@ access: [] - target_room: 163 entrance: 321 - teleporter: [31, 2] + teleporter: [97, 8] access: [] - target_room: 165 entrance: 322 @@ -3292,7 +3292,7 @@ access: [] - target_room: 164 entrance: 337 - teleporter: [215, 0] + teleporter: [102, 8] access: [] - name: Windia Inn Beds id: 164 diff --git a/worlds/ffmq/data/settings.yaml b/worlds/ffmq/data/settings.yaml index aa973ee22b0b..ff03ed26e63b 100644 --- a/worlds/ffmq/data/settings.yaml +++ b/worlds/ffmq/data/settings.yaml @@ -73,6 +73,13 @@ Final Fantasy Mystic Quest: Chaos: 0 SelfDestruct: 0 SimpleShuffle: 0 + enemizer_groups: + MobsOnly: 0 + MobsBosses: 0 + MobsBossesDK: 0 + shuffle_res_weak_type: + true: 0 + false: 0 leveling_curve: Half: 0 Normal: 0 @@ -81,6 +88,42 @@ Final Fantasy Mystic Quest: DoubleHalf: 0 Triple: 0 Quadruple: 0 + companion_leveling_type: + Quests: 0 + QuestsExtended: 0 + SaveCrystalsIndividual: 0 + SaveCrystalsAll: 0 + BenPlus0: 0 + BenPlus5: 0 + BenPlus10: 0 + companion_spellbook_type: + Standard: 0 + StandardExtended: 0 + RandomBalanced: 0 + RandomChaos: 0 + starting_companion: + None: 0 + Kaeli: 0 + Tristam: 0 + Phoebe: 0 + Reuben: 0 + Random: 0 + RandomPlusNone: 0 + available_companions: + Zero: 0 + One: 0 + Two: 0 + Three: 0 + Four: 0 + Random14: 0 + Random04: 0 + companions_locations: + Standard: 0 + Shuffled: 0 + ShuffledExtended: 0 + kaelis_mom_fight_minotaur: + true: 0 + false: 0 battles_quantity: Ten: 0 Seven: 0 From 3fa01a41cde73e47512592b4b513514f21b00513 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 7 Dec 2023 06:36:46 +0100 Subject: [PATCH 28/56] The Witness: Fix unreachable locations on certain settings (Keep PP2 EP, Theater Flowers EP) (#2499) Basically, the function for "checking entrances both ways" only checked one way. This resulted in unreachable locations. This affects Expert seeds with (non-remote doors and specific types of EP Shuffle), as well as seeds with non-remote doors + specific types of disabled panels + specific types of EP Shuffle. Also includes two changes that makes spoiler logs nicer (not creating unnecessary events). --- worlds/witness/player_logic.py | 4 ++-- worlds/witness/regions.py | 2 +- worlds/witness/rules.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index a4a5b04d896a..73253efc6e61 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -375,7 +375,7 @@ def make_options_adjustments(self, world: "WitnessWorld"): if lasers: adjustment_linesets_in_order.append(get_laser_shuffle()) - if world.options.shuffle_EPs: + if world.options.shuffle_EPs == "obelisk_sides": ep_gen = ((ep_hex, ep_obj) for (ep_hex, ep_obj) in self.REFERENCE_LOGIC.ENTITIES_BY_HEX.items() if ep_obj["entityType"] == "EP") @@ -489,7 +489,7 @@ def make_event_panel_lists(self): self.EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory" for event_hex, event_name in self.EVENT_NAMES_BY_HEX.items(): - if event_hex in self.COMPLETELY_DISABLED_ENTITIES: + if event_hex in self.COMPLETELY_DISABLED_ENTITIES or event_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES: continue self.EVENT_PANELS.add(event_hex) diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py index 2187010bac07..e09702480515 100644 --- a/worlds/witness/regions.py +++ b/worlds/witness/regions.py @@ -71,7 +71,7 @@ def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, r source_region.exits.append(connection) connection.connect(target_region) - self.created_entrances[(source, target)].append(connection) + self.created_entrances[source, target].append(connection) # Register any necessary indirect connections mentioned_regions = { diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py index 07fea23b14ba..75c662ac0f26 100644 --- a/worlds/witness/rules.py +++ b/worlds/witness/rules.py @@ -66,8 +66,8 @@ def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logi def _can_move_either_direction(state: CollectionState, source: str, target: str, regio: WitnessRegions) -> bool: - entrance_forward = regio.created_entrances[(source, target)] - entrance_backward = regio.created_entrances[(source, target)] + entrance_forward = regio.created_entrances[source, target] + entrance_backward = regio.created_entrances[target, source] return ( any(entrance.can_reach(state) for entrance in entrance_forward) From 57001ced0f7ac33241e1ff0b64b7c3c0fe179b0d Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 7 Dec 2023 01:22:12 -0600 Subject: [PATCH 29/56] The Messenger: remove old links and update relevant ones (#2542) --- worlds/messenger/docs/en_The Messenger.md | 6 ++---- worlds/messenger/docs/setup_en.md | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/worlds/messenger/docs/en_The Messenger.md b/worlds/messenger/docs/en_The Messenger.md index 4ffe04183073..374753b487a0 100644 --- a/worlds/messenger/docs/en_The Messenger.md +++ b/worlds/messenger/docs/en_The Messenger.md @@ -1,12 +1,10 @@ # The Messenger ## Quick Links -- [Setup](../../../../tutorial/The%20Messenger/setup/en) -- [Settings Page](../../../../games/The%20Messenger/player-settings) +- [Setup](/tutorial/The%20Messenger/setup/en) +- [Options Page](/games/The%20Messenger/player-options) - [Courier Github](https://github.com/Brokemia/Courier) -- [The Messenger Randomizer Github](https://github.com/minous27/TheMessengerRandomizerMod) - [The Messenger Randomizer AP Github](https://github.com/alwaysintreble/TheMessengerRandomizerModAP) -- [Jacksonbird8237's Item Tracker](https://github.com/Jacksonbird8237/TheMessengerItemTracker) - [PopTracker Pack](https://github.com/alwaysintreble/TheMessengerTrackPack) ## What does randomization do in this game? diff --git a/worlds/messenger/docs/setup_en.md b/worlds/messenger/docs/setup_en.md index d93d13b27483..9617baf3e007 100644 --- a/worlds/messenger/docs/setup_en.md +++ b/worlds/messenger/docs/setup_en.md @@ -1,16 +1,15 @@ # The Messenger Randomizer Setup Guide ## Quick Links -- [Game Info](../../../../games/The%20Messenger/info/en) -- [Settings Page](../../../../games/The%20Messenger/player-settings) +- [Game Info](/games/The%20Messenger/info/en) +- [Options Page](/games/The%20Messenger/player-options) - [Courier Github](https://github.com/Brokemia/Courier) - [The Messenger Randomizer AP Github](https://github.com/alwaysintreble/TheMessengerRandomizerModAP) -- [Jacksonbird8237's Item Tracker](https://github.com/Jacksonbird8237/TheMessengerItemTracker) - [PopTracker Pack](https://github.com/alwaysintreble/TheMessengerTrackPack) ## Installation -1. Read the [Game Info Page](../../../../games/The%20Messenger/info/en) for how the game works, caveats and known issues +1. Read the [Game Info Page](/games/The%20Messenger/info/en) for how the game works, caveats and known issues 2. Download and install Courier Mod Loader using the instructions on the release page * [Latest release is currently 0.7.1](https://github.com/Brokemia/Courier/releases) 3. Download and install the randomizer mod From 69ae12823a09f3b407370370c7dc5d04e44d0326 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 7 Dec 2023 01:23:05 -0600 Subject: [PATCH 30/56] The Messenger: bump required client version (#2544) Co-authored-by: Fabian Dill --- worlds/messenger/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py index d569dd754278..b0d031905c92 100644 --- a/worlds/messenger/__init__.py +++ b/worlds/messenger/__init__.py @@ -62,7 +62,7 @@ class MessengerWorld(World): "Money Wrench", ], base_offset)} - required_client_version = (0, 4, 1) + required_client_version = (0, 4, 2) web = MessengerWeb() From 5bd022138bff13347452016e30dd95996b7ea08e Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Thu, 7 Dec 2023 11:15:38 -0800 Subject: [PATCH 31/56] Pokemon Emerald: Fix missing rule for 2 items on Route 120 (#2570) Two items on Route 120 are on the other side of a pond but were considered accessible in logic without Surf. Creates a new separate region for these two items and adds a rule for being able to Surf to get to this region. Also adds the items to the existing surf test. --- worlds/pokemon_emerald/data/regions/routes.json | 13 +++++++++++-- worlds/pokemon_emerald/rules.py | 4 ++++ worlds/pokemon_emerald/test/test_accessibility.py | 8 +++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/worlds/pokemon_emerald/data/regions/routes.json b/worlds/pokemon_emerald/data/regions/routes.json index 029aa85c3cdc..f4b8d935c349 100644 --- a/worlds/pokemon_emerald/data/regions/routes.json +++ b/worlds/pokemon_emerald/data/regions/routes.json @@ -1106,21 +1106,30 @@ "parent_map": "MAP_ROUTE120", "locations": [ "ITEM_ROUTE_120_NUGGET", - "ITEM_ROUTE_120_FULL_HEAL", "ITEM_ROUTE_120_REVIVE", "ITEM_ROUTE_120_HYPER_POTION", - "HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2", "HIDDEN_ITEM_ROUTE_120_ZINC" ], "events": [], "exits": [ "REGION_ROUTE120/NORTH", + "REGION_ROUTE120/SOUTH_PONDS", "REGION_ROUTE121/WEST" ], "warps": [ "MAP_ROUTE120:0/MAP_ANCIENT_TOMB:0" ] }, + "REGION_ROUTE120/SOUTH_PONDS": { + "parent_map": "MAP_ROUTE120", + "locations": [ + "HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2", + "ITEM_ROUTE_120_FULL_HEAL" + ], + "events": [], + "exits": [], + "warps": [] + }, "REGION_ROUTE121/WEST": { "parent_map": "MAP_ROUTE121", "locations": [ diff --git a/worlds/pokemon_emerald/rules.py b/worlds/pokemon_emerald/rules.py index 97110746fb5d..564bf5af8d16 100644 --- a/worlds/pokemon_emerald/rules.py +++ b/worlds/pokemon_emerald/rules.py @@ -626,6 +626,10 @@ def get_location(location: str): get_entrance("REGION_ROUTE120/NORTH_POND_SHORE -> REGION_ROUTE120/NORTH_POND"), can_surf ) + set_rule( + get_entrance("REGION_ROUTE120/SOUTH -> REGION_ROUTE120/SOUTH_PONDS"), + can_surf + ) # Route 121 set_rule( diff --git a/worlds/pokemon_emerald/test/test_accessibility.py b/worlds/pokemon_emerald/test/test_accessibility.py index da3ca058beba..853a92ffb82c 100644 --- a/worlds/pokemon_emerald/test/test_accessibility.py +++ b/worlds/pokemon_emerald/test/test_accessibility.py @@ -44,13 +44,17 @@ def test_with_both(self) -> None: class TestSurf(PokemonEmeraldTestBase): options = { - "npc_gifts": Toggle.option_true + "npc_gifts": Toggle.option_true, + "hidden_items": Toggle.option_true, + "require_itemfinder": Toggle.option_false } def test_inaccessible_with_no_surf(self) -> None: self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER"))) self.assertFalse(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL"))) self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL"))) + self.assertFalse(self.can_reach_location(location_name_to_label("HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2"))) + self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_ROUTE_120_FULL_HEAL"))) self.assertFalse(self.can_reach_entrance("REGION_ROUTE118/WATER -> REGION_ROUTE118/EAST")) self.assertFalse(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN")) self.assertFalse(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0")) @@ -60,6 +64,8 @@ def test_accessible_with_surf_only(self) -> None: self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER"))) self.assertTrue(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL"))) self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL"))) + self.assertTrue(self.can_reach_location(location_name_to_label("HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2"))) + self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_ROUTE_120_FULL_HEAL"))) self.assertTrue(self.can_reach_entrance("REGION_ROUTE118/WATER -> REGION_ROUTE118/EAST")) self.assertTrue(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN")) self.assertTrue(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0")) From bf801a1efe83cc894acb5e140a24dc962c02a3d9 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:40:44 +0100 Subject: [PATCH 32/56] The Witness: Fix Symmetry Island Upper Panel logic (2nd try) I got lazy and didn't properly test the last fix. Big apologies, I got a bit panicked with all the logic errors that were being found. --- worlds/witness/player_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 73253efc6e61..e1ef1ae4319e 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -77,7 +77,7 @@ def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]: these_items = all_options # Another dependency that is not power-based: The Symmetry Island Upper Panel latches - elif panel_hex == 0x18269: + elif panel_hex == "0x1C349": these_items = all_options # For any other door entity, we just return a set with the item that opens it & disregard power dependencies From abfc2ddfed783f30d3cf7880d6db8881d1de2432 Mon Sep 17 00:00:00 2001 From: beauxq Date: Thu, 7 Dec 2023 13:17:07 -0800 Subject: [PATCH 33/56] Zillion: fix retrieved packet processing --- ZillionClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ZillionClient.py b/ZillionClient.py index 30f4f600a672..5f3cbb943faa 100644 --- a/ZillionClient.py +++ b/ZillionClient.py @@ -278,7 +278,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None: logger.warning(f"invalid Retrieved packet to ZillionClient: {args}") return keys = cast(Dict[str, Optional[str]], args["keys"]) - doors_b64 = keys[f"zillion-{self.auth}-doors"] + doors_b64 = keys.get(f"zillion-{self.auth}-doors", None) if doors_b64: logger.info("received door data from server") doors = base64.b64decode(doors_b64) From 9351fb45caab39a76e2773258a0816ce59aeb9a7 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Fri, 8 Dec 2023 01:17:12 -0500 Subject: [PATCH 34/56] SA2B: Fix KeyError on Unexpected Characters in Slot Names (#2571) There were no safeguards on characters being used as keys into a conversion dict. Now there are. --- worlds/sa2b/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py index 4ee03dce9dc0..7d77aebc4caf 100644 --- a/worlds/sa2b/__init__.py +++ b/worlds/sa2b/__init__.py @@ -619,7 +619,7 @@ def generate_chao_name_data(self) -> typing.Dict[int, int]: for name in name_list_base: for char_idx in range(7): if char_idx < len(name): - name_list_s.append(chao_name_conversion[name[char_idx]]) + name_list_s.append(chao_name_conversion.get(name[char_idx], 0x5F)) else: name_list_s.append(0x00) From a9a6c72d2c909a502c76d5b114ca6b0cf353bd6b Mon Sep 17 00:00:00 2001 From: JaredWeakStrike <96694163+JaredWeakStrike@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:39:24 -0500 Subject: [PATCH 35/56] KH2: Fix events in datapackage (#2576) --- worlds/kh2/Regions.py | 3 +-- worlds/kh2/__init__.py | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/worlds/kh2/Regions.py b/worlds/kh2/Regions.py index aceab97f37ce..6dd8313107fe 100644 --- a/worlds/kh2/Regions.py +++ b/worlds/kh2/Regions.py @@ -1020,10 +1020,9 @@ def create_regions(self): multiworld.regions += [create_region(multiworld, player, active_locations, region, locations) for region, locations in KH2REGIONS.items()] # fill the event locations with events - multiworld.worlds[player].item_name_to_id.update({event_name: None for event_name in Events_Table}) for location, item in event_location_to_item.items(): multiworld.get_location(location, player).place_locked_item( - multiworld.worlds[player].create_item(item)) + multiworld.worlds[player].create_event_item(item)) def connect_regions(self): diff --git a/worlds/kh2/__init__.py b/worlds/kh2/__init__.py index 69f844f45a68..dd57f5e759d9 100644 --- a/worlds/kh2/__init__.py +++ b/worlds/kh2/__init__.py @@ -119,11 +119,15 @@ def create_item(self, name: str) -> Item: item_classification = ItemClassification.useful else: item_classification = ItemClassification.filler - created_item = KH2Item(name, item_classification, self.item_name_to_id[name], self.player) return created_item + def create_event_item(self, name: str) -> Item: + item_classification = ItemClassification.progression + created_item = KH2Item(name, item_classification, None, self.player) + return created_item + def create_items(self) -> None: """ Fills ItemPool and manages schmovement, random growth, visit locking and random starting visit locking. From f10431779b9525644b649c92e67977df03359d80 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:33:51 -0500 Subject: [PATCH 36/56] ALTTP: Ensure all Hyrule Castle keys are local in Standard (#2582) --- worlds/alttp/ItemPool.py | 2 -- worlds/alttp/__init__.py | 17 +++++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 88a2d899fc60..1c3f3e44f72c 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -682,8 +682,6 @@ def place_item(loc, item): key_location = world.random.choice(key_locations) place_item(key_location, "Small Key (Universal)") pool = pool[:-3] - if world.key_drop_shuffle[player]: - pass # pool.extend([item_to_place] * (len(key_drop_data) - 1)) return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, additional_pieces_to_place) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 32667249f225..3f380d0037a2 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -289,12 +289,17 @@ def generate_early(self): self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options) self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options) - if multiworld.mode[player] == 'standard' \ - and multiworld.smallkey_shuffle[player] \ - and multiworld.smallkey_shuffle[player] != smallkey_shuffle.option_universal \ - and multiworld.smallkey_shuffle[player] != smallkey_shuffle.option_own_dungeons \ - and multiworld.smallkey_shuffle[player] != smallkey_shuffle.option_start_with: - self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1 + if multiworld.mode[player] == 'standard': + if multiworld.smallkey_shuffle[player]: + if (multiworld.smallkey_shuffle[player] not in + (smallkey_shuffle.option_universal, smallkey_shuffle.option_own_dungeons, + smallkey_shuffle.option_start_with)): + self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1 + self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)") + self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)") + if multiworld.bigkey_shuffle[player]: + self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)") + self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)") # system for sharing ER layouts self.er_seed = str(multiworld.random.randint(0, 2 ** 64)) From 3214cef6cf64451de1d4d9305f1ac82085f02125 Mon Sep 17 00:00:00 2001 From: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Sat, 9 Dec 2023 22:23:40 -0500 Subject: [PATCH 37/56] TLOZ: Fix starting weapon possibly getting overwritten by triforce fragments (#2578) As discovered by this bug report https://discord.com/channels/731205301247803413/1182522267687731220 it's currently possible to accidentally have the starting weapon of a player overwritten by a triforce fragment if TriforceLocations is set to dungeons and StartingPosition is set to dangerous. This fix makes sure to remove the location of a placed starting weapon if said location is in a dungeon from the pool of possible locations that triforce fragments can be placed in this circumstance. --- worlds/tloz/ItemPool.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/worlds/tloz/ItemPool.py b/worlds/tloz/ItemPool.py index 1d33336172d8..7773accd8d7c 100644 --- a/worlds/tloz/ItemPool.py +++ b/worlds/tloz/ItemPool.py @@ -117,6 +117,9 @@ def get_pool_core(world): else: possible_level_locations = [location for location in standard_level_locations if location not in level_locations[8]] + for location in placed_items.keys(): + if location in possible_level_locations: + possible_level_locations.remove(location) for level in range(1, 9): if world.multiworld.TriforceLocations[world.player] == TriforceLocations.option_vanilla: placed_items[f"Level {level} Triforce"] = fragment From c3184e7b19c65a7ae649d5878ea97e068080967b Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 10 Dec 2023 06:10:01 +0100 Subject: [PATCH 38/56] Factorio: fix wrong parent class for FactorioStartItems (#2587) --- worlds/factorio/Options.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py index 18eee67e036f..b72d57ad9bfe 100644 --- a/worlds/factorio/Options.py +++ b/worlds/factorio/Options.py @@ -2,7 +2,7 @@ import typing import datetime -from Options import Choice, OptionDict, OptionSet, ItemDict, Option, DefaultOnToggle, Range, DeathLink, Toggle, \ +from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \ StartInventoryPool from schema import Schema, Optional, And, Or @@ -207,10 +207,9 @@ class RecipeIngredientsOffset(Range): range_end = 5 -class FactorioStartItems(ItemDict): +class FactorioStartItems(OptionDict): """Mapping of Factorio internal item-name to amount granted on start.""" display_name = "Starting Items" - verify_item_name = False default = {"burner-mining-drill": 19, "stone-furnace": 19} From b0a09f67f4f38fac15f90f57e4b9bae78e37f357 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sat, 9 Dec 2023 21:43:17 -0800 Subject: [PATCH 39/56] Core: some typing and documentation in BaseClasses.py (#2589) --- BaseClasses.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7965eb8b0d0d..c0a77708c016 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -491,7 +491,7 @@ def has_beaten_game(self, state: CollectionState, player: Optional[int] = None) else: return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1))) - def can_beat_game(self, starting_state: Optional[CollectionState] = None): + def can_beat_game(self, starting_state: Optional[CollectionState] = None) -> bool: if starting_state: if self.has_beaten_game(starting_state): return True @@ -504,7 +504,7 @@ def can_beat_game(self, starting_state: Optional[CollectionState] = None): and location.item.advancement and location not in state.locations_checked} while prog_locations: - sphere = set() + sphere: Set[Location] = set() # build up spheres of collection radius. # Everything in each sphere is independent from each other in dependencies and only depends on lower spheres for location in prog_locations: @@ -524,12 +524,19 @@ def can_beat_game(self, starting_state: Optional[CollectionState] = None): return False - def get_spheres(self): + def get_spheres(self) -> Iterator[Set[Location]]: + """ + yields a set of locations for each logical sphere + + If there are unreachable locations, the last sphere of reachable + locations is followed by an empty set, and then a set of all of the + unreachable locations. + """ state = CollectionState(self) locations = set(self.get_filled_locations()) while locations: - sphere = set() + sphere: Set[Location] = set() for location in locations: if location.can_reach(state): From 6b0eb7da7989bd7e1516118bcac84b1cc0984452 Mon Sep 17 00:00:00 2001 From: JaredWeakStrike <96694163+JaredWeakStrike@users.noreply.github.com> Date: Sun, 10 Dec 2023 12:58:52 -0500 Subject: [PATCH 40/56] KH2: RC1 Bug Fixes (#2530) Changes the finished_game to new variable so now it only checks the game's memory and if it has sent the finished flag before Fixed ag2 not requiring 1 of each black magic Fix hitlist if you exclude summon level 7 and have summon levels option turned off --- worlds/kh2/Client.py | 5 +++-- worlds/kh2/Rules.py | 2 +- worlds/kh2/__init__.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/worlds/kh2/Client.py b/worlds/kh2/Client.py index be85dc6907be..a5be06c7fb16 100644 --- a/worlds/kh2/Client.py +++ b/worlds/kh2/Client.py @@ -29,6 +29,7 @@ def __init__(self, server_address, password): self.kh2_local_items = None self.growthlevel = None self.kh2connected = False + self.kh2_finished_game = False self.serverconneced = False self.item_name_to_data = {name: data for name, data, in item_dictionary_table.items()} self.location_name_to_data = {name: data for name, data, in all_locations.items()} @@ -833,9 +834,9 @@ async def kh2_watcher(ctx: KH2Context): await asyncio.create_task(ctx.verifyItems()) await asyncio.create_task(ctx.verifyLevel()) message = [{"cmd": 'LocationChecks', "locations": ctx.sending}] - if finishedGame(ctx, message): + if finishedGame(ctx, message) and not ctx.kh2_finished_game: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) - ctx.finished_game = True + ctx.kh2_finished_game = True await ctx.send_msgs(message) elif not ctx.kh2connected and ctx.serverconneced: logger.info("Game Connection lost. waiting 15 seconds until trying to reconnect.") diff --git a/worlds/kh2/Rules.py b/worlds/kh2/Rules.py index 18375231a5a6..41207c6cb3d0 100644 --- a/worlds/kh2/Rules.py +++ b/worlds/kh2/Rules.py @@ -224,7 +224,7 @@ def __init__(self, kh2world: KH2World) -> None: RegionName.Pl2: lambda state: self.pl_unlocked(state, 2), RegionName.Ag: lambda state: self.ag_unlocked(state, 1), - RegionName.Ag2: lambda state: self.ag_unlocked(state, 2), + RegionName.Ag2: lambda state: self.ag_unlocked(state, 2) and self.kh2_has_all([ItemName.FireElement,ItemName.BlizzardElement,ItemName.ThunderElement],state), RegionName.Bc: lambda state: self.bc_unlocked(state, 1), RegionName.Bc2: lambda state: self.bc_unlocked(state, 2), diff --git a/worlds/kh2/__init__.py b/worlds/kh2/__init__.py index dd57f5e759d9..2bddbd5ec30e 100644 --- a/worlds/kh2/__init__.py +++ b/worlds/kh2/__init__.py @@ -465,7 +465,7 @@ def hitlist_verify(self): if location in self.random_super_boss_list: self.random_super_boss_list.remove(location) - if not self.options.SummonLevelLocationToggle: + if not self.options.SummonLevelLocationToggle and LocationName.Summonlvl7 in self.random_super_boss_list: self.random_super_boss_list.remove(LocationName.Summonlvl7) # Testing if the player has the right amount of Bounties for Completion. From 6cd5abdc117328fb327168bae2d43a379b64e8f5 Mon Sep 17 00:00:00 2001 From: lordlou <87331798+lordlou@users.noreply.github.com> Date: Sun, 10 Dec 2023 13:07:56 -0500 Subject: [PATCH 41/56] SMZ3: KeyTH check fix (#2574) --- worlds/smz3/TotalSMZ3/Patch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/smz3/TotalSMZ3/Patch.py b/worlds/smz3/TotalSMZ3/Patch.py index 049b200c46b0..c137442d9bd0 100644 --- a/worlds/smz3/TotalSMZ3/Patch.py +++ b/worlds/smz3/TotalSMZ3/Patch.py @@ -319,7 +319,7 @@ def GetSMItemPLM(location:Location): def WriteZ3Locations(self, locations: List[Location]): for location in locations: if (location.Type == LocationType.HeraStandingKey): - self.patches.append((Snes(0x9E3BB), [0xE4] if location.APLocation.item.game == "SMZ3" and location.APLocation.item.item.Type == ItemType.KeyTH else [0xEB])) + self.patches.append((Snes(0x9E3BB), [0xEB])) elif (location.Type in [LocationType.Pedestal, LocationType.Ether, LocationType.Bombos]): text = Texts.ItemTextbox(location.APLocation.item.item if location.APLocation.item.game == "SMZ3" else Item(ItemType.Something)) if (location.Type == LocationType.Pedestal): From 1312884fa2deb33546a500fe743e371492b67806 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 10 Dec 2023 13:10:09 -0500 Subject: [PATCH 42/56] =?UTF-8?q?Pok=C3=A9mon=20R/B:=20Fix=20Silph=20Co=20?= =?UTF-8?q?6F=20Hostage=20(#2524)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes an issue where the Silph Co 6F hostage check becomes unavailable if Giovanni has been defeated on 11F. This is due to the NPC having separate scripts depending on whether Giovanni was defeated. The code for the check has been moved to before the branch. --- worlds/pokemon_rb/basepatch_blue.bsdiff4 | Bin 45946 -> 45872 bytes worlds/pokemon_rb/basepatch_red.bsdiff4 | Bin 45892 -> 45839 bytes worlds/pokemon_rb/rom_addresses.py | 12 ++++++------ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/worlds/pokemon_rb/basepatch_blue.bsdiff4 b/worlds/pokemon_rb/basepatch_blue.bsdiff4 index bee5a8d2f4996ee1db54d2b7812938cc66dcb922..5ccf4e9bbaf80f0167471e02dd64384b5bb0e622 100644 GIT binary patch literal 45872 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 AWB>pF literal 45946 zcmZsCbyOTa@aLjk7I&w3aak6Z;<~s)aa-ISTHM{;-5m z7_LyA3T!@C$T7zW=~JeU3@Xo36oxL|FWxJ1a(+stkEPF+^nocejK|UgXVXTp<(fI$FY^=6+^T4v59cUd5HxJ&;S@19XS@A1Z++W@X0Hd z5FHvbN6ti+js=h)$7CUg#jDtStyIY?EM9~=g`ktb6#$6;XExx96-fXv0CE5cU>0F< z7BKsdI*X3}kAy&(NE-{OJc$^{D+@fuR9TRi7yiflp#lN}K+r2-=wJXuSd^jw;FAf4 z{A-1&@-H%n`GiRapoLdB%*@vqtV0dqky1^V?B;8-@f)0Da|wp~qjK!+pr{6Aniy53 zYkR6p(=5PlZqqt$lcFUq+sJii8f+t6U|*Qw6*eNye)Yc2sEq2~H##?+%=KS75yubq zQ$dspaqWqKG3FE=DPmKcg--npZ-0Uwjg`xk<26NEHYjO|5gg_DXwql+?cy}~&uDeh zM6tc!btuQ-XxM;T;-P5b7&_EI&&MHFY%dl=Th7+Q5xUCxj#d-iS(`STx7MrJ{iuD- zG!qUhs`T@T`%+9R@2+a z8wA!%(0Kh`rPlsr&Xg@_yQ^PvAu1(l2v8G95C{MSafl8?V4?99u7?0XE_@WFSp_;m z3b-r^Rbu!+kW-TEuS*tLU;jZJBjyd_jhQ(2x<8ZvHh5$cB4c)7-xmreF%c}Jxb9(I z0?U`YQrxR#UY{0J%uuV6J6-*{PT!Ssz5HGbKA1Rw;UBk*8}rQ=@fIzHDQPF_W!gjS zqvOw=cItJ6QocukF&-Y*7~5dLn@J`GT$FFMy4XKnbXJ@NGBaK@lMpG88*x24LH13(Qo&>6u^ECWwG&8&|1~8n=FD%Oh(5vI=wKbM zTt49sWC26lVp1V1B(2ScYroxAn@gE4W=^JBBDf`3PzMabuWv?dk9^F=w?2jg!28;P(E}qn2u@)x=#% zf{Kp51vk}2gsP*snw+I-EL|{2L@cI=@S>?46|=@^Q8sqAnqih~%;-=_9dc%Dddga` zy~8grw}7LI{@_ zf05l{Ys(u5ev~v9nz3$~Sz77Y`;MM8l-{X5A%t1e_mvqIb!2Y9Q?Z3} ziKLKMV4cJeDzDujSnU&H)RB!kH=c)j=*_gH9X=AfedJY^F~~9LL+131*Aa7n9Wr+0 zvaECr3jV~L9S>Ldj?ZA#8|BR9c$K+p>#@#Sr}Yh%@{mf-=4&oF^~v+Rsz$b?CUg%> zuqAiRy3mKKxpJbM@^^%NPclhVUwRxMyf{=KO?$7Zii;CnVO17SQhD zevD%ofFzs$t?CEMF}(ZT6Vq*Gto({LX{q<5?Xtb)Xc>2f-&Y^Sv`6HT>W^w`XDIw|=R4Q|#}11T@E z^}OTx$}jT}BUse56oEQ$&W9(YG_eo)v6@Jo73aVyixfR}Y~mCRz6_gLk8kBCpl zZCl!sX$^k}%FvnVQ_HTZoX;|N^j%T7>mONpmB!PW@jY;si{^PMy9klOX)xkF8lsPg zDs23`xDCL#1Vc>S=pF26Xnh9)bDX_5krut z054LFd3C20jnJX`p-4>QmH~+N~D$}W6CZ+bAAxl3DQCle~X$EKJaTz%;d)&r5b8dB36re*T`trD}>8tbXST<_)#q4YM#-+fWUXb~a zC?ysOpbriOh)4ww2QQk2UTe&Xb` zM(gmRw0I#vR045bH1>7A=Zx!%_$na3vi@l$j(@t=kS#LMCN8)di!~9OqQ@ zsJ2Qbs4YvDvk;MS+ODDAa1bCnGAg_6d?ysP{cezF$lOPTRnR{?mKZP+2!x~Sb`q+W ztMdQL!`n8w^xb|l^~dMYg6}x8$yi9iSb+^F>!`?y=m1JPEIZ%$@T^&DpP0`i8cjkN z>bz~Xd>40J?d|{7?;0F?s6KX}4f&p3Lh}L=sDg-q#{B_k$n>94c)5_!L?!r?_4N|V zDY0-HfpBVCgs%nDIk{)H^#eJFV2dL$u{W0D|{k6!D57P*5Qr{*+ zn7VK)J_G>RJ^j}eTJYN=yA^mb3N9iv)ZZb0cr9JffQSG}IG_X^0*;wv#w7+jE+#29 zM~iT!;GKEho@u?ZFp<*m)}=W5LOQ!F9Hm^XZDA}?9u^N9y&S7|756~Eg}U&jZ+;ua zKV9Zb(-!MG64Q`uUpC(Y3kz%jYiEC|kD59T`AraU>(FjQFb{r({Pr&u9htODHa$MO zNnH?R$Xm9n_#=oiC!p8>P{K-7_oP;(Ek{R}Pn)An>TiprO->QxB1F$Fmqi3wjwCM- zZ~V~j>j}y^w~IjgeSkQ`M{kK3NGT~Htg$67ixWywMu=jrs#J`uJ%KE@luDIMnVIgu z6v6?zqzA~NaLDnbqJtje+DdiT%`u2F3WugKWD&2lGqi933jPCHK4tJo;Q16u7s-{H z)JP&)RiGoA_nj1IVj-4^S{A0pfWnByOUtt>z`bhRd|Wvk_s!3-I5Wf1rV z>X#k*!=W-LtWwSVsy9)4rR}~@6*t`1uW9bcQOFn(EGiD?Gj9QzwAND=9%#r2){=g< z0%1`acrH!io^7y4aYqt3Gif2p7N1$}vda;W5QEx{&x0n z18v6ZS8lrM!f(bB+v3hpGfhj!VG!l=C8?m9?o>u9@)LtY;Mo>azB=p4!T@}x6x_6} z)ccKIYb9+%m)SL?;i^BTOs#-i5d&GK-g;_9v}&qk$JcAnY92%uB!Y#@mm>vEqT+Tk zOS@9K4U!;yLMo1c;gS()c=JR_h6lGQt$=*nn*6Qk=tyHqM2FtnCF}vzwe%S!*5_Pj zB?1%4B7S??i6U;2QL4-qOB%a-?^J~doT>UnsGCk}fy`#cryxMjUux)=;_8v3L%Gtx z%ZVm4LEn1a{rfMFKhhN@wzJacDGe&-c@o6#Z{}>LxHS?#Y9>~uBEh3vkTT*m1U{{B^!rDxDJXmD{*s6y(VSPa5ksTV1#1L9uVTrg0oG zcL59$Jv+}oTfZoOwg@c2Z38FO45rXY+Q{*}PgjGEOr?<}#I>lH`ME>Al?iisd;I$S z+R^XA?;gB{vCzg87~NA#+iG9D49nhAogJRQ1ii(zTJwLTP+*DFF|v!UM#?4%ipypk zEzeUkj)G-r?ER~zr7md`YP&20A)TCwWFzs6jip29|1FMCKkI1|A0ae5S2v^Pj<>Ro z?9X!Z^Ti54wtNjZ8jiW-erQ~p`uXZ+g7lH5rV>izcr} z|8_4m9DoW<0v1+8h7yQ@YbFsyv_omLAIe}4Z(qm*K(j8}kTA}nLh#6>Od_#v+k9q+ zz4+F4s`nRNs5Y*iS-ub{<11&l3sW}Cw5%pct?}aO`Q)O0Vyvd#J7QI{Hz>FM+kWOL ze24xtr$-yhU$>D1Yqz$A`3}&=g&QtkD3JGbr&-n5tIX9Br4EnE+g8y~c^5Jfo}O&( z2j@*jZ2qRMxlXHe&Es(rp&!JcNPE3!jxVTdP;4)eGCTdthS`XPfV6Cbwc8 zmmk<-<694}+WO>7T`**B9R{CxlN;B7N$T2@Y!9^j@nq{oQsmkU?x)Ny(gJiF7594ZW+!w z&Z#mbNZ{KkQ(F>kwozZ&xUF7x5eJMl6Iui%hSx@crsmWI*;@B2oFlQL|6Krrv+?m* z&3_jaUX$ZJ(vg`8^enK=6lVAnJo>Rz!yu@)iD653T{E~J-wej_cEu&ccz6!}ABM6e zwKob#3HkpZDF2(Z{eNqA^!PRCo=jEBYmPm+MYY=s=%he+0ET~WcJ!Qk8yzGSx_{XM z5IQVEUi2A&5dh!bBljziU={9`N|K=fpaNjP0RTV%#-~2Nm%f*?S;YN|I?Mu8Cx?=T z=RS{xB%jF(!>jUC(d@N_nk#wNA6FjpjlU)reFYQJKXC@twSLGb{!n+@?O`m~v$iV| z=Ge4flyY)h>j^1Kv9ys#CRB+%1o^z z#>;3Ql$AktA=wb?g5>+eZgF3E^9SN6Z<=Yjwv-2oe^^UVUU`yGnM_%hHD|>-r71;X zw{*l*!QucQfD#BmDY8ttwT?_#htbRXeyXhcT8wL+m#iognc~fjO!>7kuVS^l0#h!5 zqIeiUQ5INWeMlly0D~+{Sf3(?m%tSu03oxIhzsHn68bFY;z*fId9xxFPb^q{TJEGW z`93RK{Q;6qRb>2c6zUT`io(*|ko!FKMF^>A=0BtaHUxn{U?gZ+5E#T9Y%j4mmnhpPXWhuf{K8mNo ziV%Bx(Xm-!WyV7Q?4Q^bYl{Eo4mkk*Lj_>GAW;riPAIHcdUlaWSTxH_#k#z#42(Mf zv4%TPgkaLbgo>5bX{gG=MCZz^3-ZchiNP?K`Rv#~EDKi@VIE8ZnO#ss&f-K4qD_z- z!v)ALJ_wgZo`MxI8SbUy#q)|4i@an?vUrlY(b2Ni>1nWp#S+Yg|KUut=E5|KkXfGq zVgzYX0105OEHJTn4(=n(Lt1@0eW^ywRrmp)-g;kvD(Y3PjQgt_*oziA>tyP<`&s&4 zu_zLpOubEIN0hI(G^YU4<#<3n)JpXhiaR>?djTsciNU)&)S$B(UJ{5*c{}GU z$@hKSZxJ6X0us{p%Zy;fKX>{$8V_^k{Kt3en}J0e5vtyJBQySFs0PcI-=@w=KfZ*N zlImlBfe z+zXv1F96%E6*#yz$QeZ9xkfyy~bYFztxpGQnNZCfHbxD{|qO_u%e-*^(?A5bo@sqN1KIz|asySR|;W@WJ z$-tWNhtCMPgB6P%9Pia;=^vTBA$7wE*_};3=W@&peIw$~>-lf6zxGL^|AAM}|ez|xHsJ4ut|T5@xITh-xZ45DujpacEihh{A$x_1nenI5J2 z2wHn~Ru!{n^ajD&ImhdTgQ}M$MNy3A!qpcaT#>5_f@)VTxl=o$gy}9z)j_|}b(OZA z>>4r`DQBjXVVPCMoJO3{gc zg~hx&2Fg%|ETs-vJh_I)V+1)4rLwk!ra=(S*Gx;wflk>BvRV~jMo>U#y^BUTO2)5G zY2WGP{_6Y)pg)G-@N$bIASMTMX{~e?ddpbjYZwlwXK~G>aGQveN9xUpjpCUBBZA{P zxF&-BE-b*j;a3q8g5$Ra>+CI;RqDFtb&zPh_@@gXF@eB2+P_KXc%n~Sce8>ff<2kW zuWV|`p&0Oq3U05a?Y;Q-ogE%JU)(=n2vmJ&0p5Q`h__?mjc`r?UT!+jv+m3n>^wRo zN3M{oqE+;VC8Z7>R{@T_{bEy!@ec)_t)ZdMb03DkO3c4_i|58JI1J@XJ$*za4%f*O zzQI_b%VkHlBl7thq}a~=gJ|NfVnDA`&BObMrdTV%qFyznth79)6e+op_Z?$l3x(>_ z=M96kKA%HSG#%k3Gv%)e9Kpe^paTTeUuyp(omDUC6+{n+NW9nSirp-(Hn^1Xablz` zs71Zi=RXRDI39D;D}VH#ocx;B*V?M&fLbp|Ep7~dBE_{_nlQHAm0V;9jMB?=89*p7 zH*KY<9?dDt@7~cvHmnuT9aiBy6!Z`O^wNoyyI4=XUfFHgdid+$+^qVV_nmOzX`4BVR4e!U z2UTYh^mNF^@WM1p;1GLeV-O-8pWA85=6g0iKqfsTlYCw#X%hB|E4UdbFz?8HaVqVMeBlTQGN`eT`JuCfG%u+&v^ z>%@6eqnX%6QDr9@SW?iVVbr78S}yDuoupO4G`dwhcYv~+Sq}KwDS{NMMwz}*ApJx7 zO}IcfCb0$o@h=_y%$>fPXP{(IICippf&Q1I9jtRcH|qSXSL@9Sq6BKSM7RX!M*wW@ zcaQJ{?};!eV)hTglu)UW;MC>zG666JvG({UTAeH49tgS~Sk3xw^55^XFQBkK<4mf+ zEMh9Sw4J}b$6`i=J}i%>4yS^o*Ing&h1m-~+<{=k0ZQ#p5;Ai0iHVtsw+f~@zZ&g) zx}C7*$$Kh#Pn0kYOXNq0KB0Q$*ruQ7++>$|dz^}{<~orO5gYS<49j7zW3$gy)L7Tn z&f-vCIr~LG?b_%`>V(fQE&Y%xa`ic4yZM)Q@-SmZtEHxaTG{4fpov&KcjX0IODc8x zD!a5{0yP}pQaDy%qYsWW6{fhQBo>y?NCJ-$E_fiPVj@ZQ!TS({TQd<|?JpfqIXO|0 zYOp{|qXRg5>tfy$Q&Dw~DpJCW8u;4ZT^wHtErW6l=cQRoibn>)u^~D_{y0e_%V57S zRbIf|;!+yCFGm-S(fu^IgWogJ#WRzQ14Mx+j+P0CBzy*qaMCl2CXY4-f&GEOsGixl zi79#cwxoZOZRLu1P|#`)&6DugITISPy3EJmT?y-BsrGx~ZF!we#J;1lZPKLE!5<18 zFqdLamsp2G3{OeE>1xOx8u?#OU@Y=!Y)(c>{;_J{Mb*qC`tv|MXj}a)C0?CVzqal$ zphAq>lb4W26QeMZ_NEdiUh3hSjNk6CVi;_h-K{je*Rf%?zBCJ&1l6+pN2WhqT-xLW zXOI4az9maPk&@CuC+V}pW@5owEb7HjB?$9N8tfKjy(wAExsOP^p|Z7JV{?-*s^-gC z`@UOOY%|+pSDU6xy2iPsTxJkHR;F!HSHMmjFqitz5UB^vx0FSg^C-ZbVP00(P}k&i zlIgcqs1d&uTfO(NxmeANbXCD*lP>P?Mm2{Eur7u}>X3#P?F&(yZnhqy;9i>T3PW&9 z%igt2DsJWcNo@BdC-kozf3`S!K*OeW_^`;Ky}cu!0}^`UV71;~cv9(X4;j9q*rg1? zqdfAAj&FpVSqdUAjS6Kc%a+aM^W_Ex62VUAWvuE#G;?ijw|abedh;DxoriwwoC>s( z2Q+*f5qsE_qEm@dV^<#}ve*L{-;-I=YC?4}n(vfI>kAE4iE}8l+YvuQ-io$a4j9xJ z@!v)kPs}PvZ5gYP99I^Selbc738f~pFC@}K|2Rc(l1WiM&~X@62FyB^%K0v}+;x}| zZ@zT5bo=};|D@yk8|zim*7+`n1SPXfhNFrFmlQTedC=xW8k#;a177yH>sjs zt>cr7;DLoOsN2%q*={F7`uRiWeW>qs6`L(a5$n!e^L}6C<3@z3cH;V>4H7lF3#97d z#-%oxUhweC3Ujlu?_Zx@R>dT|jhP%>?t(=qag*^pquDAQtG9%_2FZQ49K7uDwHFYZ zQU0C47QGY4P|30#(~^eZT!F6V$$4Qs?o{b)xIrmgRBxxy8_KXUYxg>{zHkapC)SU)V}6Q;24WdFh5(BZj}Hv60r9@>7-w+h5AP zKq_r)N1{6d^s1#0k}X`3pz_@g0qKuZW0^pMl1^?6H;Q=1Cf_DYi%U99)VE8mWfT@h zJq7I99Ccu@)Z4;;-fbo}cJ;2F9yO;c*;g^iYG+$M2$h`6W5){|mC&r~P*zkVQhQS? zh9^psPS%5-3G5^HIad*?ZFA*$Cu_a+n(t^F^}o=Fh9unP2@_+KLu1qTmRW-xtUA(@ z_1#m~Qt28cKjNePn4a$1r|KFdfeb~LrO^}k%DY>nzD=*?xH8#$+vd5P7nt#MC3j!! z(x`EUIj2-?Ye*SS#=2-V@nrc`mFzw5t#n3>Hf9pqy zucM>GnE~%h^=idmL>bZicROIKXq??ckiAd5*h`A2eMUZ-gHO7TZXNQePQJOgMjg&E z1kV;XY|FnR(^ZMtV)VWndUX#@*~gVp24|F>61?Q;bs~wcX1<%Fbq(wBI#}saG6byT zWQOX#&W@j`0sZCC=hCco4I&F=aSB!A!S<*852W`ia;W6Fq$?OFL+AHCA8y9$TLy6i zT~!!baaY9jBUh!)i|hE#EoxRb|5+x|Y}cnFb1BQ*+_)kg;=Sz9)ZECYiMy2 zh&U0H(&9ss##JE=F~4rajUlf{s#nwr2h8Yw(%{eaPWUe8YAmhu@dvHJO`3}q)%2}c z8FRHUOEsGE_k5RR9?pxe@!}GtiF#Z70woulY2t2E>d}?OAe;-_o%EGKgI)n36{5+e zxT5)mK8n8Pn&?m4VdON|m}um~f(n^{HZs01?1QNs>?G~#DI~IdQyv0Qt>f6gGi3(u z(X1@P!aqwXFWPUOld{*etFq37R$C=lZ@bl|dRosv<9ht?Yks?S;UiXFri|0)3g#>g zY?$A7IsA%k+f;5&?+|L{$*T*-Q43}`6nrIPj=il%!!MHUY$_;FGqL=t05*hmDL{qV z#V#4uKx__k{8g{=t0EeMovNpj$~eB((QwnnZ*HqY%By=@SgFn8bJebb*V$?{He1b6 zqxQEl?7wvs5#38Ykh)kiBz0ykmqmD4Ws%`I!eD}jmVE=e1yzVF%;0=pH<)Qp@l=$RXvgtZ#StKbfUH+3) zk9=JNGdJBbMk+RXmn$5&<`ypV#caC!1}7HDMb0Z0m+=b|xR{vaF~;~oCI-6l+kH2U z&LXX9dE#ZJI;E~*@wv5j^p>eacq=Lm_jjgCVJ#h?*Q%8`+Tw;Tv+?j?byAS40l=Eq zi%R{-!^fH?(XpBEQ@efT8h`odc$S(av{Qb3aURW^vl}g)=gU{Bi!ZODGIK6JPAxRg z*cMF{*N5>~z0lSFnQwP3TC2b8fmfPQ_gkYptSCoNUaZTyK1AOcWqLk>?9v5)R-<0o znr`v#lb9<+Mm zEWhRH&QF5e;^B;aEQLj2o0ysqrvlz^hV43s4EHqeAitxY60e<6i7znSTfiH=HDT2b z;r>8f>+nzfX@T(wPy*RE3Q>EN?dNB2D!$S2l^l8Ss~$NnBqg~xn~np#5_EFDxX_qM zgtTTs&~4x`RGX97z)TW>z$r@CmyMYE)j$V9F$Nwqk9g6r)>EyA5vFGZ@^U!nUp|}j zq@U7Yb`=wTk~%apOUkACwHBR3(-U=ZJ8Omy-&xT{!pVgA&|*?6OYiWL`TDYxJz|!9 zcJ&9^^_{0W&}Li?V*wh1(OD_vu6nJy(9?k5-g2R zR48Cv4=v!IHc+HSqQ#uyq7jW3RZ9vALY8wRmHQ!mOkea~>Yi7gnr&#F)%5+(wh0o% z9D~1juGRqrx28>>DtO~YRfzPW^Un!~Ngt%Riqb_<$LuFMSA~sf1jocgKBRGBS`0(& zhvSmul2oZ2j=e@qkXi}aqF_Plq?^_5jQS+cE4myjB~u=;k4Hg^zZ=0$0N0Nt(#i0z zn1|AeBzd)0nFgRQQQ;Ef*;!|u^VloM>0k#l#lfPi0!~MTR7DjH%b>IPUA)RxKL#4R zls5A8QUK)M`ySx^WLp4x5InMxgkh1(8X#mnnI)VzD8_%9^h&-u3u3ldO`7t@w%Ps5 zGHZLmB;F0=1Wt0~yT>7v2o#t`MFop0Q|7%4>gQ_d6s2T* z89jKiKkhNuGbIszoboS@{RAZhy$TEzyZ2zz{WP6&7ar zR1!F9({I|3B+pK=$&jABbzX!fgdHQh1wIOX6}-(4kQ~7Q@qmr{g?O1xDc6C z$8oFnId1QcX;3CHw7kQwOeOl4jp2H;kHdzVI`3WycE~4Qq4PDZ?-hRS>4FA2q%!ow zSbBjWTv>&9K5V-Sx(#@nNelj}Rq(bNJQHFJmGZR1sTcF7syV8ua<3t*yn6*vV-;gt z_7bJtKQPk#K@5|4RT+vg?D#xp6+LP0v`3H}#AWSXwca`@p)8t6{ zs=BSI8Xv9KE!8z}ee4x++3J!O#e%3S_6A*3<}o;@O)j^v&1abURStKVb#dMagNb?UP64ikhn zD`#qOOptJLl_qB#)lhY1ba#QfZltu<@neV>VuFHN%|>nl|9j=pYhWw^xm6+Qx+}=Z z59_q0+zm_UPD=AScgf@YjVdF3MrTn!>$Zu~b8BWNkQP!4m5m)v`eOqVO)(O9J+{O< zY~|gvf69G0$qeofMQ{G@{!!b0ko3LTuWqy@C9n;RzCRj|2V5+orulQMH@xv*R9q5MOEl$q*^_=HZT>uNx}bGi&GPHMFvP~*Ub0bq`k86dkB5AvTSAAl;Sg` zGi%7*NQu$CD;3>R9HIQeGxiEE_s#~)rdF{ECN>=I1-gyx@$H~Y^|1oR$d#)<;omk+ z$|jPdF*He(+T0#N6h8Sbp3K2?pKOJZ!C*tyyU_<#L;^*Hi$6wd90$_U#}~axo?iO$kPzQjy>GNm z{(S#?QgkIR$DPk3<%eV9z~h%ru$PvVC_WL2AyZ{vuXApxaeV8;zSN8sF5uYK#?qa< zzu&d~PSEj-zQ4Grt;M+nK)Mt1Ta8sr6eBkvh@y(Jgj=zho9UI%w?U_E1UQ=dLqsY} zk-vsE;j%~1FAOW;it=~IN^e!fx)4L5hB=<`tz-8O7w_DfaOQF?@)k~>3rRKSLKxTpT_`#JyuE{3yV-pSe2Oa?coaOb48nWa*}mV*3;UzfThKYsnFNq~bK+ER{}ZBrn=aF+oUDW;15 zS?pM6f*JWb@udiZts<=g9|a*9p*gHZz4=d^m9C46aGS%gUB#JL`^n#n+VWK2A$#Ac zc4blpNbk=c{~E?Vh3TNs5Dn=tQV;XDcN_FlSS5rlBM)n+eJn~S3F3&RdVBwzY_enm z-PI^Q0qbKfnum8NS&8BQ^Xq8QZ}pX0lWihp!mhonrB+>QDrpqRL6a@J>ASZOP7L!-FOZ1EZ$#(2`HfnvqCmm~WM$Yqch=H)udR zzGDsb?sK4}K$VlRykZ|PC%Ww`U_8@-1VPQYE7WmWld64HZ5dFXGFv~Wu;qW zZ81^QO4Cni`CDX@iIdZ?fCMaxC`%QuY^<(XRS!~S$sVq&?dW_`rLq z2#lD3tOz7*TT$hASJ%99`wx^!Jb#^)>(8uzS|}(V`h6m*lW)QBIq=KMm(IE2OvRa+ zy>WTA}U2j3d-v*GhaqEk!luzz~i&F(n#u{eS5&f#4xZ_dZR$ARi!0rM? z=T=?~PrTa_8hGk5EcB6SYfM%4n%=fAq%3{*Kds6IV3%^dF`7{&((wI=1RryM&+Whc z=k5ZVvF}{f#?-GrwIt!Fg=KZ+##NsECni?hU6LrCfP_t3ax5>~;1efjN2JGs$De;( zPkiztoEc;s^I6H`)X~20!A->iFX#L-`dqkmG7W2@W$aMkv%6Rb9d*BH5j9g3MX2To zN_Nu=lbjtk!mpzO7$(ZWaAc#&31S7Q}C%Gzd~+@R4s<`yda{C6XG^9OjuLF5v#G$|Ia`TZmHZfx#BOyA0AGg507mY`FFe8DOlTR>2&PZfG%a6zfKLB4rD z`HvY@jR=aX?Vs~wbLfE9(T@TmRr{?p9q|XsA=y(~MH%P1-5=a-WVE}-IrpGhyyr2wb zgETVK`c+pKd{Vu3$9|$`Z10h#)4OVpZ6p>iN7qY#hJBi>%|>*R-fQpMzz*i&=!=e)k3Nb# zGn3GBaN04qNsj8#oJ`?nDspuGXZVOvjZ(lI+%yvA_L7`Yq1uAvn+6`^{kwzjd5u

BW;M%ZsETl%?a+!9*+Clv?n+$|BNr(~Y^8k;r^p?oI%VZmm!b=y2sm z^IGipq)h^@s7?!Lu&ANJjcU^xQ;Cr$VofkHdEn)ERIgSl;Mw?gE)ew!b8OIY3UDv> zj(U@$8&rCAVfHwL&)i|sCy&#+j@`#fXa8|#@S_j%Bwc2>1&aBx-2;5<7R;yp)m`Lx zvvm~_#hNxU7*W72wRZzBtG%%2EeolGyf~p6CnFiPdCzr?^7>4DBwY~01oM`~BZD2#|n3}Wx}$u1vuoThjEF?J*1>z#$o-D94g?Sco|I&o8bfnWWGe{8@DxOu7>jv~kz*W#{cH#Uq9K~Gsgk((|YDCJCB=~@+1e9U;@|+09Qamza_aa7= z7;90U7%Qel(bWjJ-=Y_n|4;4jBq{zrOfy&cmG19RN9AT){#ve!v(gINNcV*Vz zdxGpOhr(iXXb_aWZ=IuDEGrY-n)*BGm?FhhQkKL^Si~6_u~oHXW0F_Px-||INXXK^Qt%As#YrqljNjq8)OK3))8*DzYx^Sq; zIBbyAb0*i5q7tYR#^r0>20ll=usj*MlUh#XYiMNl{$LluO;Y>ypdl5(LW~`vwkB_2 zusBevpelu@SEWf0Uo|dY1zrVJRaF<7s4+s(_NY{cEdwUK3phP!qGw!WFe1D^=3e#E z_7|h@G>NUrMyU~85}B|D50rod%&m3Lo1@`}Mx{K+8>Jh?Bm`q1a>^VW0TyVKpgp`4 zDJ@aFfl7i1dKesxK~_>n9~#7rh#!V><=bTsRaMns?(5r`vO8$@5yka4U95!P1S=kh z5nzb}hjp=a^AxJI_}w^JtNIfis62ShG_Au#@sOgbKpIkvZ$gU!XKGro)xA?x?yn1| zZla7xp!sul0nKR~b90r8*NOD|io!WvIrI}dR*(sNL^2w$WsCt~7FASVwZTq=*G&q)QngsMX!<_M}KgO5oJn&(aGtOOD) zAO#}4Nn)m(maJc8L8 z)I++c}F>agHy?oP~v@erLy7-+6?*Bn(=p(?;(`N_=^Y5qi#P#hhbR>lXksCNFHMYrl} z602y7O2T3ol3Hr^=kgBck@f;71($W`ON%lmg74J$Wu_TznnyxP5cQ~NVNRiJmQmQjPJVWlTcJ2xH}~p>rY5J}m*&8+FF)qA zGtJ!4(eX+-pbAvbMyAW*a`eWg7%nvUn;1xjsx6*!w*)~7z9yY&phFnCYIau}0TWWu zMhK@SnyDr|R$G`Uw+HS@Cvn!+LDK zXh8z=}Hupb{(DCrqqO_Sy&F~_SAzkTerKZIecw2Pi8cXe_`X=(6l8Du;DmGCIZ{6Y{3?>IGa`*zpC(-c zS|k)0R1ln)^c9`}43fmAvcUybBvF0{7W^9j(_t%Fw5XIAgFED_4TdE`TEvP}QXM{g z27JGSj3$0QU8u05bJ~eUStQ6H^g~nfqs(mOAK0171YDK8kwrFQ^Vd{61Xu2UweT>u zK_tyNi%K|RflQDbxNiVNKq)Vt^ilk{B~TWr496sGH%h?-3KEysC}ZWuC&2T>DUBK) zR;k@}$oymK1FzwmAm+S z|I!#>h6jJ2q=D2+hXZ0^Y{O9p7WF->8?z9mhW$Gw`l0}iP=>q3q~46M@lw&U#3~&K zyyIgmWehwjJ-lSZ<+qWcV|RzM(W7b_)2Y+i6Ex?KVMcHd1=H~8KSi0#qs*Q@8>L7H zp^j~*UIb@+Bklcp=crF9;Hdpyk^9YQ%M-z-^P1TnxjK%x-`&SwEt-Ziu@Vt(gHAyeHvCE}k!=#zDUTmeVu#<;E6 z6`MkEj6UPecnb%iT1yKNtz2421V9eyub6d7YoXHf6r}juovyp#B27_5Twt2d+kS6T zvgcrj>m}3WFfNI|%xeeQIb#^Pe&3xC>G_b^HqN1XpK&5v`ZM#@#qDh~sXfRTFM~OC zhX^V3M9f&mAS<=t9C|6UEh4So_9cvtaxMTfVduNfU&ECFyoPh0SCnS*-_-Ap8$$KW zTJNadlCTOJhj?;Slxq3^V?$T3{ovKW`j}0w`%8a_`NS>R3N0vpb9>y#yz_OjQ#x9E*^WY_4^*7bE`p zD?jTZvN*A`_L;ucCSTVfkkx)X*Y2%=w{he78_-3K?+k6-U|sx=`SB6WP1M6S*cbUf zv|`NRRPR1^rT3njCZaIa7)$KLkEOegi5@$93BnTYJOXclpgw>f8gvP40bO6F68q)c zR;PsKL$Gz5PRv;JCBW-#5h=?Ig+9IZL*x#p@ClCA$z?iZJpoJtfS?7!!O#L@jkSu~ zA2?eNI|Ym+oPIVR)OKD~YYDF) zSn)~MkgY8>?~dV1D=Y6@OEKreMnTwxcZh8fi`?)XZvR~(vAKCw6_mrSo$RR5vv#>>Dc`qJtBDDo{w zX=C=y-*fZ6)5vyfzNe?B{igfM(f?57!7GtkG7@*`=`~JzX2o9VKYBN4t;I~ZGOj5W zIwg-m$F!|@NglrNJ3EF}{^VD7?`Q7#F>_}vqKPx3OJgV%2soL#_VWS_V|A2}75>lL zH=eBSm@`5*ndyni@NVE1?AQ3q&C^!AuvUk_u|rJtE0MIr&(ILEgVX2WR!iJ{lV1$x zj}P_dHVR7GuuBmFWdd402eXjaZ5OZ~kpbyX)jz#dmQ$R;i90hQ3rWNlM)!yH)az&; z5s(ZPH~!llAm4yTA<#`3 zG!sAa)z}Rul}~T*`9;&4_?^E=60|RMlt&t(RvH%S>{z`fI%V0mrhBFZkM=sc`1*hc zI))>cL>t#Aqh#ivxzIw5C_?!6(_~Zx3PWH+CqI*&Z;4vggIHsgk}^+b0U`@TU9Xum zJaHrGzy}RlXxLxoWXJuzQ_B>+t2>-4v>EH- z9Mj36ON2wo#ZHKHYY!%K`znbm z6*)4|?L4I-o;$7dTRpYz9j;qlEt2bNj72?UN>C-Bkbv{rRun}oB_DTTBjeL*w)7Yz zmYFJPS}hWwpz-K>-?lZIVcLH0-FtiO;OxG;x?4v4lD6ZCi}?7$L>nU1#?P@D=ePM?!qzu@bAr9k#&BK%FN>s_Y>S{;{ddDqVh4;CS`=-$`~ z3cqG*d2Sipxl$r^kfH3Mz`lfyt}7EC^A&{{L? zm;yi$6U{lK00uby?pOFTI5XBs@RhbXyBX>z&&vR=77R|mA8V#9%N|2P{hZPhL}(_@ zGXLD)HX`Pk!0WBoeH3X6H=*$AjLo9}!Gi!g?(1fynFc|bgKa+;@bRzuL4Sddy`sXE z{X5Ui?(?zv-0pUsx83+X9S@a^8p2r1fp{E>DRq+Ag|64*DQ~XbaCPDK43!(Q`CLrT z6FYwr?|XB4wqwQEO~=RAxr+#Zjw%%RB-xL>>`>wj#iSY|O58b4n3B*!98lZYl^D){ zAjeghlNC)x3Vk%;WN zc-whuy890r#tnv@IjFpxd}Hf$n@)V5cc0S|fo7_*-Bsq#BU#8?jkghOLabm08K5CI z07ST$saTGm;?i`;LxD4prlf>p5U7Y$ss!RYb>ybCtA;2X!6C+i^dhfS4te9~#BSr+ z(}CB<(s>$G@h}sV#N~BiN}WJRPER2Et|ih7AP2X6H(6t2X!`m%~pl4EG{9Z|f*Tg$J?diPN7IV6_ z9EeSQi-zH2Zqs&dluW8*>z)FgYDDDOcH=zHjH zP8ccGq~;`#QWXwUTre3C#1a5-1O((O$&`b^Ar(6j4fv}ibGR!I#&9-Z<2gY|k_&Pu z`zUgIA8YG<)#qi|^>#dbx=~GKhb>{0kFhPZqibTu_r!O@@+fX`P!Gwnf#EDyQrAQg z>sgr=g_<40pJm_gSv&7hLsUSdP7Z%J9+;?l3`JiFvz-XNtDUO z^oymREfxo=H2cePU)7e?ZYaz#dlf$kw908TohfhD{QWKsh1uu2v(_XN5-Qw3!~}qw#v-9FB^o4W9OBVKMl7% zS;+k|Jz@C$7`8#FE}5GZUYR-Ek?<5q80dC7e23x2^pn#ivUL$MfU>6Z~MB{h! zg6axOVQqzgMBP=&p&?BL1RO?XNNve80sxrYq98}TnhQUoAd&$J1`1L?MQco)6<&u# zXaiDj5aZgd>mfh|wd(1NP=_|P8}5ZOLLuFFN(QD|&lnS7OYceHmM?Sm3-n)51N zOqlxT>H5Xhi^y*-Yh}fWN1P+ubM9%7lqgYg4BX<#F7pZ`L*rEmb`_;y(Iy0#DK5!k zW{Eg3gCn&yH*xsOzoP@n?_qw1g(XEFd(ET}KE?ofFX=O4PM-Y`GQbFS6A3#n$$czy z*nK8=d|h<7;%X@1949r3BGuT+xw|F2wvzh`mCMzyfzc#Ua;Oy7KQ0WU<*Tt_l)cms z>(L5%SQurYxjizVjE_Sp6snSl06@47vr3g1<`I1C@9(qoMCJz;%G2rOfJ~?M`9A)9 zzm0rH*f$`xMdJ`{I}jis0AT177!a*(OB0W;*m1DUg=6F*et$o{@4cn&gjI7z0|>tI z1t8-QC$ zK(l0YDZKMI|(dB8YL|gN(}#{wMxN33Np~v;^m6q*BpBkz^tG z%;^1kk{_hNJ=#wmzJ2m4^wC@^jn{vxTJT>D0A09>YDElUB!*e<;iCY1*d9`Os08(O zsW^4s>4>plu~sUm#TGFIVgmN>I%hkoks{$CO(dj(i%eNnQjucFD#eggSTR9hq*w~8 z0Xy^*!DLvICy=!O*vN7JCV~H|jfs!S^(^}d!7|R`5^hO2xp<@k)5T>TW8C5KJ@s{D zKYoY(s1s{Clb_P5fnJp~A2SV@sc0kJzzUDY&zy(d!7UIsI&WQPi5~_48DN9ydNd87 zZS}>hS9e9HQ7$tQlopwQ3R0lWScgxlqP$2Az^3yiT6!%hcKQlXGdGYUdM4Jk5l$Ww zu}*Ou8eIN6I&6@UVt}Sc_3v9qe9XdS1+afk2z(eCy*B6DYx#YAOZRJ!H??V3i5@pi zMwIc<^N~+Y6UI&1BE!wrmfdg1z=+`uc+e1vlA|I5%Z{MeW2wRJ( zy=%96B`%o-GJRedb!A^rHe$bD6WLDl4|?(0N0N}j9@z_W?`e?Y`tPH+y!?z!Q*UJH zZ-39AJ&kG@*VAmkhs|%tTX$CE3fH}GP3H;FE<%w(kq3CFC6Q?;+UwGhJJ%*FTA~Et zU`fQpNI|B?bVxWUzs#gm`)xoXKw*}RtGD>i4E4qNq+}$pt_oH2tymqzCeeNOdfXbj zW*=O1^sHf5KUIPUAOK1TB3b*U(<4(js1>^HNkUO{tF9^aDl3^uQs8S4KK93;Ht?XQ z1YD!WAn;6P+c*p`aKu+0yvF>Qlt-9FLnmxoOW*X){|K$BqtY^fE;TSxLJk|NNl-D3 z)}~yM1f7-5k{~8h4Oz^I$lj>;gnS>LV_re{s)4~BqJ=;_m@dYTH-9bv6M;dN3+Jw* ztkEg9wrekK6G9oL5Zsx<5XKeo>b-?gvjOuF!XDx)_H&D=vDn-pvk5$g9SE{~w^kon z$Hm=q^f=VYd&W)=*UwGEgUNuhGbK&CT9z0zCk{144BVcsa>WTyE5*pv*S8PqHk)U5 z2Yot6skoaW6-Y=mSK&&kW8h8Ts0ncb92}V$&N~^f1d6C4o&^*p=ow26OizaXpn*X^ zD8VUGAcEu$SyBR;Oo&&p0T+ACeu(gL*Dvn)ldOV*BF9Oy^-5q|Mhci5${>NMm_sBH zkjp70Bq(IYeda#juls90?_Hi8ZpCjQYgq^i0P*=s1Cc1{=m5W%!unk99E0j8`9=q4 ztUBEp`mT7ehY^x!=mxX8g~8?d>px`=x`@QI@tn}aD|T?MEkvUP*oiv?f}CQBx+pQ9 z<@8FEwM=5CiMz+k`_NK<>4W#2%e}G1}fLa)=YpP?wyYEHJS}8SLr(%f;0e zf+$@g9BWJ#wS?npxRim&IK8s6B_fggL>}vg0TR6`8v#uX-gVE!y>XgI%ebjBlvi7L zil7pO0YFJ0$i(tCUvnM1=I&zpDf_!I`yAhUyVx5k{qv9(5nw1N2+0%_0Z2svHl(CP zfTW5nBTmkaj}ARV4?);OKV3cxJ-gYCf|$ufFa%R1J1!vwMnIGi42VkHq^e0;jLM5q zlmpLIAWYV=@E}xfzKap?qFOHZnWL)eDRDLFrbGX)$ZBt2UV@MCtCfeedDGBCP5sNX z{tFA}sB}G^VxtHBwvtcPO{jY5@a~ih_x;V2zMMEzkqlq}mCW;2{D@j#?Wop=-$PSl zuKQcFd#YWS3^RNUU4DzXk4y4xNg8B^U?+!xpz)2QLILthAH%fMrfdQOmiJkK!WfX785%qy}xu;mTu)yv)?;lc60AO`~hIE z36$-R=ukt_F)kbJRx^;XE6u$HuM-j>ZAx@-;&3(VSkzSCw4u&|(ohXau2)$b3NbBB z4#-Io>gQD*t<9t(IrGBrX}EP@Z}q(kClYQz&)87$4SEr7pUVQZt4D5=Vkx5(1i~QS zu|_ZsXNM(1Nj2P*3kk6U{ylWfV0(Oa96HZKxE>vA-TZzFQ+5WrW8q=>XFe02S{sO;*wHGD#B-d{NZw za*_rIRGOzZ@2Kpvy25`t>~&6_Te?=8N!@J&n`_Z?OxDgqfC}M85W1?WhUC8e8{1A@ zDABHp4cwgf*6n;~K+}fOa6)Ij9;$He#5coQLl&NDw(0;(r0`RB+~C9H^=&Ob`X) z>J64ky7fBJn%hSQ=`se+ike>@Tua$eE9c!M@=>`4wSI0Tncmp#I0^yLUl`!6V4*4L zR7|u#ipT z>}I*EWt}@Zti#rtAqci4ujKH*MV|?oIW@BI-TQ}gX!0~F#K!`I+((8t+-q9XJjoAT z8~&v5Eb!%|Jy91U6tq_?{>ZLr@Vy-dInB%}7P`HjhPB3Nd|xT-X!sC-W~F>{sppj} zFi_|lAy=KL@5vZyV z>w&aQrBgfmKI>)V}=Fo-W=j z5M#?z>#}%E@pk;asn>x>=!$U*CM2BfIXNmOV?lEixRbAyQnxFRg~SRZ4;r69*0Qo- zdu+8|W`ty1Vv^xRyUR+dC{rDBP38rOEEnt*1=PDr#S{^Yz#t1NQ9&3=TvF=aa&UbX z8g1KQ<7DH2hyTAHn~krXy1zQB2~jym)@SWf zV}*1fXx$ZQLi()zZNI(!w{xpbwnM4po8#zQCGaR%zs9S$8$Es41(-1cf|M}K5)xl? zAocVb0wZV&(&B=Ew2FYNq7C!#I_0-(5F6Mm3pM{!vsfnaT%{e3ZPH=4yO6^JuRB=i zSeg^s+9g|fRC~ILS@*pKxpIZZ-=@>i8m-}*k7`0u@D@g@*LthYcP{Da*;B705djc% zq;K;o0l~+#)%-<*kt^(U0)LSVZl$~i zxw9DHYd0W{Zj|+$aW3^b4kvo1-+hs>dkIbc2OlNwDIUjk)sgiDGL#Bvt z)l7D53KRB8gpz&21$Zc?0nExp{Z37Vs)rP6A?|0Yiem-wQ3@~OTG5k2B}IMj=TIAR z6I9QRid5jxiS?PS*Vv$_ni_7iu;mkWWPp~2pM2}_uT&VoKqcVId2a|WMXcjBon@u! zp<7(X+d+wgfxpFNSmfo)m6+p>D+?v$Khag)DeKJfGt1+7u66fOKk+SI{`$3Pg+l$G zbK14GwX)_88wE`22oS_z-)#Db;2b^bRS)`m?(hb~)$S07=+_%BKSDB;8)}1$7zXa8> zn=99%n+?s0sRF21L3XCKW?QqXrl<~6(v(XE1wo@_{Iekdcm2Bje^CmSTP6ob+#DLdi~k30{5lSGZTgXGHb`6WYFqgqPLFM+0;MB>bVKI#8Jf=jY?za>u*~E%cWlvGmc37!d6X`)zF^kIr|erkf9I2yLlJ_+|83CPcEdgr!8P4mvy9ku|cprVu@LhJ23}NpX=#~z{UPZ44 zY%#RY8cd!ZM@%N0rDIQT9o%Ugj~KlLd^tCYSs=)%90{^>Nw;wK5Pur_5b#tZLbEN4 zS!qw&W@uPMAfKK>J7#!eqM-$%IY+Gn^)wr=*_D=zZGsC(-NVEqXH+t8|=Cd<-- z1-niK9>nhq0lz`pE7dZ#h=mC1sUw(|74<(Zt&VHBvYVqP&+X%jHZ-4oWiTF;DHVQa zy8FMA$J%SEhub^V|Hj7xUiSs<)oJ36F|?&YtR&PC^5I}iyp8q)sSRGSn0jL^I{`>WIJSHxlP9x)g5YwqipO*9E^9dOtr zsZ2?VD%wy|Pzezu)|3Irw7e>mB_9)Icqv&)5G{EIM6PN`b4m8(aw<@Vx!^DfqF!n; zo2pRqRF_1*YEW;kTe^)-wZN}3+t{iOlQM+nlmf{rX%wJ|ACsk9O$}5lbxIS^5v+Md zKBd>Ju9sn&4jqc_J|xr6(&2yM)HXf_2eb5QYxFqT-e%=``s=)(iRahiJ(t1g@xIpn zi&MziN0h@a27DAbnLQ*A9pGcJb6w{jr_1oRu{ZT7cOpDObRd_&zPE`}gG#SEZzWVQ zbVEld9k$XR9e_(zfO_3@N#vme4At7eIUF)?cc2OdF=r0_+1+@s=Gj59?<6|YXIEX* z-F_+?QU??!Q08CNz?2d3y6#QY?n7Ss*ALzhh5hev-R?z8XEJ5_3ef-?~u5 zRN+vYNReWtBSl?4_^kYChCqp7MLrj6=rL0Ul{yTik#*Ovuig9?q>h`BE%3_#B5t@k zC5L|h4`8%zM{cp?Q&1CjZN_>SQNMk7-Zgmr$|_1L3-tYZum_OsyqbL_&`b{IJ4&Kt z${Gw>Tr2X>12`yDEEh&toyOs~gyHI_zq8FoSQJ#LX%-S>QXz3iP_@feRGLUK>f00r z^|@rhXWMA&*DEDcy%wrwDQz9wCgb9eAz4nN_6Q8LPcIIaDK2*e4Q)4L;^5ba!ORHG zAEJJn(udS^cCt=)E)^GwdvyL&hIYG^-+Z4Yyr;-Xzr8{2ke*h&?esiT_G}Nq@bB*(wGJGU-{;aOb;7gcvd&0&tk6p=qspUkk>gg-$3&YoK0&2*-U z1+R9Wdf$}H%_^F4V86I*?qj>nT>SNM&=D0ettsfT+t^@v>1-+;)S%C$O|PeyHppCi z_&3Em7a(?wmZ(gjmUBGM;Z=U={HiL=EA-fYHw>0er_!pnZ)fFQFU?Mef0g=HVdhSZ z*Ye%Hd@QjO^_u=asdjurNxX#iMFSs$>6d~SC_c}}a2$`fUzzcOf#&Ly3D19VYc}qqp}cPFtl6B{ z$~cL2SKw+nk}U+t7I|3+eQ3N+CYescJBY@`Q>RgWFtiEyWHt=@0EZMM}lnpD;k3Nbj`S64sh~n zGsqZ7sDeiD_oW4z(uYfEuRXv5W@JH%vctiG974k_1v#~;21HLltM>Vklgwj(L(sjs zgP^fDNE)9ic_=C>!r2h4_OSjA{M&Hler@dNHHgm_T-n5Kh|xIn)?{F=&OgI=_oV>J zKo=09s|~5t&BC)|Go?Voah#q-O*NtlD1nD3trP3Fe3*h4n3)A2C=04g2!a7)8pH@)bxFdHfs4hv7&V(-h=8@F$H-VY}q63)K-veD!JFuL3b;brI_`aMhpVb zV;NJ7g9kz^m)A*zUE&c>{Ly2r&|u7f2w@MF(z=>aFPb8;j`zu2V}t}=)kHOohtfi& z#zaBR+EC}A_GikJ2pjaj+u}9@U^f)QZmSNM&!7E;Vc6(~%4!pb>v9Xf9&@9}&c@pA zZ_vNuAxwkQteqnE(F}wmh=@=P3}3)VAn;_Jg>ikK?jIMh>h!ivvQbDEe6yp>qH`nD zC@%$HMS9Gh>*IYdd4PtgK92}f-mW=sB&m2hnV+62gg9#Acu*@boW03Sa(ksfIK=2E zv;7v1f2(1uhHpaNl=8m!HhxP&A9mY@;WU(P97W=cWgQ( zywYcLi3N}_Fu>#&OZ+%K%RzkT)z0$;U0tr4#Xv^9_N`i@S}4m_=Tm@!hA(Clzd~<`nHt~Utt0~e@?^}VsRT!ryfqn$(i7p65K18U_Z&0 z9wI5X1U%@4_f`YK4DleyVBG!{fjL5>ZEX1doP`h04lAY*jVZeMFbDFOK67q12nLMn z?{#|4b0L%6pvF+wEuaY=5=AOwg&ow*)(fW$o~6K%Y6Jf~SMl=TK@nxgp^ z(l2Z0wKO+ruT?>!6!T+iX)|Z3yl4u^yA@K%P86InJv~?M7QZmZSmHMuz)%y;PuJq_ z8++)q{gjULN2#+$7bGi%7OOupiW7RYM&SeY(Lc#z*ABF*cMauC+JXl(3a_?8w} z#^imp*^}J;ud1Hj!n3jxPobfgp|XuY*DMahH<9nqV${r2H0XVfmtVc&$!l4< zO??#Oc^@(L_R#?mV0tJ1bIWOFAprqh6Zp0OEyos+rK(xzw2Ym7#1zDf9WfKT<>s&x z+-r(goV~-CYu{xvFH9t>#T7kkq@kE&O-WHQ)C1dPtxM7Av)NeMYg*dtnPdYp3q@WC zTMptMY5Gr3@qSIiUs{fCo}L=NBS81b#q4}4lj|q)5>M~;*AHuUBJ_Vra^L)!()dvF z7#g#V%{*V{1&zI)7tJgAUqjY2`hORw=Rkn73Y{u=l`u~l6^9f7jhjZL_cx&Q%ITkf z>Bu1XU4)4SEDmUSfdEKB_PCq(!0{NYdfEX#;JFh4WI^-mZ9+0S@;XcPOan#@Uq=`S z(kMId5`W^z5K@>ZyA_#clPUZM85uRT7e5MNRM!Yn+A+LcIOSnoXXBC)3HUDqK@Rg+ z9hdbstE-j4MY_Kw5Eg|te;2XZKqB%62VX(yv!2BsO~c7UCRsCO2g*ce!

^aKX)H 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 O;_gVN3K9a`DoQ|XZiR;c literal 45892 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 UboM_(lt27k$rRy2K>Qy9z{TtP>;M1& diff --git a/worlds/pokemon_rb/rom_addresses.py b/worlds/pokemon_rb/rom_addresses.py index cd57e317bdeb..ffb89a4dfcdf 100644 --- a/worlds/pokemon_rb/rom_addresses.py +++ b/worlds/pokemon_rb/rom_addresses.py @@ -168,12 +168,12 @@ "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a61c, "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a62a, "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a638, - "Event_SKC6F": 0x1a666, - "Warps_SilphCo6F": 0x1a741, - "Missable_Silph_Co_6F_Item_1": 0x1a791, - "Missable_Silph_Co_6F_Item_2": 0x1a798, - "Path_Pallet_Oak": 0x1a91e, - "Path_Pallet_Player": 0x1a92b, + "Event_SKC6F": 0x1a659, + "Warps_SilphCo6F": 0x1a737, + "Missable_Silph_Co_6F_Item_1": 0x1a787, + "Missable_Silph_Co_6F_Item_2": 0x1a78e, + "Path_Pallet_Oak": 0x1a914, + "Path_Pallet_Player": 0x1a921, "Warps_CinnabarIsland": 0x1c026, "Warps_Route1": 0x1c0e9, "Option_Extra_Key_Items_B": 0x1ca46, From 19b8624818212739199a50bb73275649d99c9d9d Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 10 Dec 2023 19:11:57 +0100 Subject: [PATCH 43/56] Factorio: remove staging folder for mod assembly (#2519) --- worlds/factorio/Mod.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index c897e72dcd11..21a8c684f939 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -5,7 +5,7 @@ import shutil import threading import zipfile -from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple +from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple, Union import jinja2 @@ -63,7 +63,7 @@ class FactorioModFile(worlds.Files.APContainer): game = "Factorio" compression_method = zipfile.ZIP_DEFLATED # Factorio can't load LZMA archives - writing_tasks: List[Callable[[], Tuple[str, str]]] + writing_tasks: List[Callable[[], Tuple[str, Union[str, bytes]]]] def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) @@ -164,9 +164,7 @@ def flop_random(low, high, base=None): template_data["free_sample_blacklist"].update({item: 1 for item in multiworld.free_sample_blacklist[player].value}) template_data["free_sample_blacklist"].update({item: 0 for item in multiworld.free_sample_whitelist[player].value}) - mod_dir = os.path.join(output_directory, versioned_mod_name) - - zf_path = os.path.join(mod_dir + ".zip") + zf_path = os.path.join(output_directory, versioned_mod_name + ".zip") mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player]) if world.zip_path: @@ -177,7 +175,13 @@ def flop_random(low, high, base=None): mod.writing_tasks.append(lambda arcpath=versioned_mod_name+"/"+path_part, content=zf.read(file): (arcpath, content)) else: - shutil.copytree(os.path.join(os.path.dirname(__file__), "data", "mod"), mod_dir, dirs_exist_ok=True) + basepath = os.path.join(os.path.dirname(__file__), "data", "mod") + for dirpath, dirnames, filenames in os.walk(basepath): + base_arc_path = versioned_mod_name+"/"+os.path.relpath(dirpath, basepath) + for filename in filenames: + mod.writing_tasks.append(lambda arcpath=base_arc_path+"/"+filename, + file_path=os.path.join(dirpath, filename): + (arcpath, open(file_path, "rb").read())) mod.writing_tasks.append(lambda: (versioned_mod_name + "/data.lua", data_template.render(**template_data))) @@ -197,5 +201,3 @@ def flop_random(low, high, base=None): # write the mod file mod.write() - # clean up - shutil.rmtree(mod_dir) From 01d0c052595ecd61b61d51b2b3b766b3e4cd22a1 Mon Sep 17 00:00:00 2001 From: Rjosephson Date: Sun, 10 Dec 2023 11:12:46 -0700 Subject: [PATCH 44/56] RoR2: Remove begin with loop (#2518) --- worlds/ror2/__init__.py | 10 ++++------ worlds/ror2/options.py | 9 --------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/worlds/ror2/__init__.py b/worlds/ror2/__init__.py index 8735ce81fd5d..6574a176dc2d 100644 --- a/worlds/ror2/__init__.py +++ b/worlds/ror2/__init__.py @@ -103,12 +103,10 @@ def create_items(self) -> None: if self.options.dlc_sotv: environment_offset_table = shift_by_offset(environment_sotv_table, environment_offset) environments_pool = {**environments_pool, **environment_offset_table} - environments_to_precollect = 5 if self.options.begin_with_loop else 1 - # percollect environments for each stage (or just stage 1) - for i in range(environments_to_precollect): - unlock = self.random.choices(list(environment_available_orderedstages_table[i].keys()), k=1) - self.multiworld.push_precollected(self.create_item(unlock[0])) - environments_pool.pop(unlock[0]) + # percollect starting environment for stage 1 + unlock = self.random.choices(list(environment_available_orderedstages_table[0].keys()), k=1) + self.multiworld.push_precollected(self.create_item(unlock[0])) + environments_pool.pop(unlock[0]) # Generate item pool itempool: List[str] = ["Beads of Fealty", "Radar Scanner"] diff --git a/worlds/ror2/options.py b/worlds/ror2/options.py index 7daf8a844666..abb8e91da25e 100644 --- a/worlds/ror2/options.py +++ b/worlds/ror2/options.py @@ -142,14 +142,6 @@ class FinalStageDeath(Toggle): display_name = "Final Stage Death is Win" -class BeginWithLoop(Toggle): - """ - Enable to precollect a full loop of environments. - Only has an effect with Explore Mode. - """ - display_name = "Begin With Loop" - - class DLC_SOTV(Toggle): """ Enable if you are using SOTV DLC. @@ -385,7 +377,6 @@ class ROR2Options(PerGameCommonOptions): total_revivals: TotalRevivals start_with_revive: StartWithRevive final_stage_death: FinalStageDeath - begin_with_loop: BeginWithLoop dlc_sotv: DLC_SOTV death_link: DeathLink item_pickup_step: ItemPickupStep From d3b09bde1274fae368ebb2965d6c40a1524970ab Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 10 Dec 2023 13:15:42 -0500 Subject: [PATCH 45/56] Lingo: Fix entrance checking being broken on default settings (#2506) The most serious issue this PR addresses is that entrances that use doors without items (a small subset of doors when door shuffle is on, but *every* door when door shuffle is off, which is the default) underestimate the requirements needed to use that entrance. The logic would calculate the panels needed to open the door, but would neglect to keep track of the rooms those panels were in, meaning that doors would be considered openable if you had the colors needed to solve a panel that's in a room you have no access to. Another issue is that, previously, logic would always consider the "ANOTHER TRY" panel accessible for the purposes of the LEVEL 2 panel hunt. This could result in seeds where the player is expected to have exactly the correct number of solves to reach LEVEL 2, but in reality is short by one because ANOTHER TRY itself is not revealed until the panel hunt is complete. This change marks ANOTHER TRY as non-counting, because even though it is technically a counting panel in-game, it can never contribute to the LEVEL 2 panel hunt. This issue could also apply to THE MASTER, since it is the only other counting panel with special access rules, although it is much less likely. This change adds special handling for counting THE MASTER. These issues were possible to manifest whenever the LEVEL 2 panel hunt was enabled, which it is by default. Smaller logic issues also fixed in this PR: * The Orange Tower Basement MASTERY panel was marked as requiring the mastery doors to be opened, when it was actually possible to get it without them by using a painting to get into the room. * The Pilgrim Room painting item was incorrectly being marked as a filler item, despite it being progression. * There has been another update to the game that adds connections between areas that were previously not connected. These changes were additive, which is why they are not critical. * The panel stacks in the rhyme room now require both colours on each panel. --- worlds/lingo/__init__.py | 6 +- worlds/lingo/data/LL1.yaml | 112 ++++++++++++++++++++------ worlds/lingo/data/ids.yaml | 5 +- worlds/lingo/player_logic.py | 31 +++++-- worlds/lingo/regions.py | 15 +--- worlds/lingo/rules.py | 23 ++---- worlds/lingo/utils/validate_config.rb | 2 +- 7 files changed, 131 insertions(+), 63 deletions(-) diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index a8dac8622162..f22d344c8f51 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -104,9 +104,11 @@ def create_item(self, name: str) -> Item: classification = item.classification if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0\ and len(item.door_ids) == 0 and all(painting_id not in self.player_logic.painting_mapping - for painting_id in item.painting_ids): + for painting_id in item.painting_ids)\ + and "pilgrim_painting2" not in item.painting_ids: # If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings - # go nowhere, then this item should not be progression. + # go nowhere, then this item should not be progression. The Pilgrim Room painting is special and needs to be + # excluded from this. classification = ItemClassification.filler return LingoItem(name, classification, item.code, self.player) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 8a4f831f94cf..ea5886fea00e 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -373,6 +373,7 @@ ANOTHER TRY: id: Entry Room/Panel_advance tag: topwhite + non_counting: True # This is a counting panel in-game, but it can never count towards the LEVEL 2 panel hunt. LEVEL 2: # We will set up special rules for this in code. id: EndPanel/Panel_level_2 @@ -1033,6 +1034,8 @@ Hallway Room (3): True Hallway Room (4): True Hedge Maze: True # through the door to the sectioned-off part of the hedge maze + Cellar: + door: Lookout Entrance panels: MASSACRED: id: Palindrome Room/Panel_massacred_sacred @@ -1168,11 +1171,21 @@ - KEEP - BAILEY - TOWER + Lookout Entrance: + id: Cross Room Doors/Door_missing + location_name: Outside The Agreeable - Lookout Panels + panels: + - NORTH + - WINTER + - DIAMONDS + - FIRE paintings: - id: panda_painting orientation: south - id: eyes_yellow_painting orientation: east + - id: pencil_painting7 + orientation: north progression: Progressive Hallway Room: - Hallway Door @@ -2043,7 +2056,7 @@ door: Sixth Floor Cellar: room: Room Room - door: Shortcut to Fifth Floor + door: Cellar Exit Welcome Back Area: door: Welcome Back Art Gallery: @@ -2302,9 +2315,6 @@ id: Master Room/Panel_mastery_mastery3 tag: midwhite hunt: True - required_door: - room: Orange Tower Seventh Floor - door: Mastery THE LIBRARY: id: EndPanel/Panel_library check: True @@ -2675,6 +2685,10 @@ Outside The Undeterred: True Outside The Agreeable: True Outside The Wanderer: True + The Observant: True + Art Gallery: True + The Scientific: True + Cellar: True Orange Tower Fifth Floor: room: Orange Tower Fifth Floor door: Welcome Back @@ -2991,8 +3005,7 @@ PATS: id: Rhyme Room/Panel_wrath_path colors: purple - tag: midpurp and rhyme - copy_to_sign: sign15 + tag: forbid KNIGHT: id: Rhyme Room/Panel_knight_write colors: purple @@ -3158,6 +3171,8 @@ door: Painting Shortcut painting: True Room Room: True # trapdoor + Outside The Agreeable: + painting: True panels: UNOPEN: id: Truncate Room/Panel_unopened_open @@ -6299,17 +6314,22 @@ SKELETON: id: Double Room/Panel_bones_syn tag: syn rhyme + colors: purple subtag: bot link: rhyme BONES REPENTANCE: id: Double Room/Panel_sentence_rhyme - colors: purple + colors: + - purple + - blue tag: whole rhyme subtag: top link: rhyme SENTENCE WORD: id: Double Room/Panel_sentence_whole - colors: blue + colors: + - purple + - blue tag: whole rhyme subtag: bot link: rhyme SENTENCE @@ -6321,6 +6341,7 @@ link: rhyme DREAM FANTASY: id: Double Room/Panel_dream_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme DREAM @@ -6332,6 +6353,7 @@ link: rhyme MYSTERY SECRET: id: Double Room/Panel_mystery_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme MYSTERY @@ -6386,25 +6408,33 @@ door: Nines FERN: id: Double Room/Panel_return_rhyme - colors: purple + colors: + - purple + - black tag: ant rhyme subtag: top link: rhyme RETURN STAY: id: Double Room/Panel_return_ant - colors: black + colors: + - purple + - black tag: ant rhyme subtag: bot link: rhyme RETURN FRIEND: id: Double Room/Panel_descend_rhyme - colors: purple + colors: + - purple + - black tag: ant rhyme subtag: top link: rhyme DESCEND RISE: id: Double Room/Panel_descend_ant - colors: black + colors: + - purple + - black tag: ant rhyme subtag: bot link: rhyme DESCEND @@ -6416,6 +6446,7 @@ link: rhyme JUMP BOUNCE: id: Double Room/Panel_jump_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme JUMP @@ -6427,6 +6458,7 @@ link: rhyme FALL PLUNGE: id: Double Room/Panel_fall_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme FALL @@ -6456,13 +6488,17 @@ panels: BIRD: id: Double Room/Panel_word_rhyme - colors: purple + colors: + - purple + - blue tag: whole rhyme subtag: top link: rhyme WORD LETTER: id: Double Room/Panel_word_whole - colors: blue + colors: + - purple + - blue tag: whole rhyme subtag: bot link: rhyme WORD @@ -6474,6 +6510,7 @@ link: rhyme HIDDEN CONCEALED: id: Double Room/Panel_hidden_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme HIDDEN @@ -6485,6 +6522,7 @@ link: rhyme SILENT MUTE: id: Double Room/Panel_silent_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme SILENT @@ -6531,6 +6569,7 @@ link: rhyme BLOCKED OBSTRUCTED: id: Double Room/Panel_blocked_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme BLOCKED @@ -6542,6 +6581,7 @@ link: rhyme RISE SWELL: id: Double Room/Panel_rise_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme RISE @@ -6553,6 +6593,7 @@ link: rhyme ASCEND CLIMB: id: Double Room/Panel_ascend_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme ASCEND @@ -6564,6 +6605,7 @@ link: rhyme DOUBLE DUPLICATE: id: Double Room/Panel_double_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme DOUBLE @@ -6642,6 +6684,7 @@ link: rhyme CHILD KID: id: Double Room/Panel_child_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme CHILD @@ -6653,6 +6696,7 @@ link: rhyme CRYSTAL QUARTZ: id: Double Room/Panel_crystal_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme CRYSTAL @@ -6664,6 +6708,7 @@ link: rhyme CREATIVE INNOVATIVE (Bottom): id: Double Room/Panel_creative_syn + colors: purple tag: syn rhyme subtag: bot link: rhyme CREATIVE @@ -6882,7 +6927,7 @@ event: True panels: - WALL (1) - Shortcut to Fifth Floor: + Cellar Exit: id: - Tower Room Area Doors/Door_panel_basement - Tower Room Area Doors/Door_panel_basement2 @@ -6895,7 +6940,10 @@ door: Excavation Orange Tower Fifth Floor: room: Room Room - door: Shortcut to Fifth Floor + door: Cellar Exit + Outside The Agreeable: + room: Outside The Agreeable + door: Lookout Entrance Outside The Wise: entrances: Orange Tower Sixth Floor: @@ -7319,49 +7367,65 @@ link: change GRAVITY PART: id: Chemistry Room/Panel_physics_2 - colors: blue + colors: + - blue + - red tag: blue mid red bot subtag: mid link: xur PARTICLE MATTER: id: Chemistry Room/Panel_physics_1 - colors: red + colors: + - blue + - red tag: blue mid red bot subtag: bot link: xur PARTICLE ELECTRIC: id: Chemistry Room/Panel_physics_6 - colors: purple + colors: + - purple + - red tag: purple mid red bot subtag: mid link: xpr ELECTRON ATOM (1): id: Chemistry Room/Panel_physics_3 - colors: red + colors: + - purple + - red tag: purple mid red bot subtag: bot link: xpr ELECTRON NEUTRAL: id: Chemistry Room/Panel_physics_7 - colors: purple + colors: + - purple + - red tag: purple mid red bot subtag: mid link: xpr NEUTRON ATOM (2): id: Chemistry Room/Panel_physics_4 - colors: red + colors: + - purple + - red tag: purple mid red bot subtag: bot link: xpr NEUTRON PROPEL: id: Chemistry Room/Panel_physics_8 - colors: purple + colors: + - purple + - red tag: purple mid red bot subtag: mid link: xpr PROTON ATOM (3): id: Chemistry Room/Panel_physics_5 - colors: red + colors: + - purple + - red tag: purple mid red bot subtag: bot link: xpr PROTON diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 1a1ceca24adc..3239f21854c4 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -1064,6 +1064,9 @@ doors: Hallway Door: item: 444459 location: 445214 + Lookout Entrance: + item: 444579 + location: 445271 Dread Hallway: Tenacious Entrance: item: 444462 @@ -1402,7 +1405,7 @@ doors: item: 444570 location: 445266 Room Room: - Shortcut to Fifth Floor: + Cellar Exit: item: 444571 location: 445076 Outside The Wise: diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index a0b33d1dbe58..b046f1cfe3e2 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -190,6 +190,25 @@ def __init__(self, world: "LingoWorld"): if item.should_include(world): self.real_items.append(name) + # Calculate the requirements for the fake pilgrimage. + fake_pilgrimage = [ + ["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"], + ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], + ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], + ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], + ["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], + ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"], + ["Outside The Agreeable", "Tenacious Entrance"] + ] + pilgrimage_reqs = AccessRequirements() + for door in fake_pilgrimage: + door_object = DOORS_BY_ROOM[door[0]][door[1]] + if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none: + pilgrimage_reqs.merge(self.calculate_door_requirements(door[0], door[1], world)) + else: + pilgrimage_reqs.doors.add(RoomAndDoor(door[0], door[1])) + self.door_reqs.setdefault("Pilgrim Antechamber", {})["Pilgrimage"] = pilgrimage_reqs + # Create the paintings mapping, if painting shuffle is on. if painting_shuffle: # Shuffle paintings until we get something workable. @@ -369,11 +388,9 @@ def calculate_door_requirements(self, room: str, door: str, world: "LingoWorld") door_object = DOORS_BY_ROOM[room][door] for req_panel in door_object.panels: - if req_panel.room is not None and req_panel.room != room: - access_reqs.rooms.add(req_panel.room) - - sub_access_reqs = self.calculate_panel_requirements(room if req_panel.room is None else req_panel.room, - req_panel.panel, world) + panel_room = room if req_panel.room is None else req_panel.room + access_reqs.rooms.add(panel_room) + sub_access_reqs = self.calculate_panel_requirements(panel_room, req_panel.panel, world) access_reqs.merge(sub_access_reqs) self.door_reqs[room][door] = access_reqs @@ -397,8 +414,8 @@ def create_panel_hunt_events(self, world: "LingoWorld"): unhindered_panels_by_color: dict[Optional[str], int] = {} for panel_name, panel_data in room_data.items(): - # We won't count non-counting panels. - if panel_data.non_counting: + # We won't count non-counting panels. THE MASTER has special access rules and is handled separately. + if panel_data.non_counting or panel_name == "THE MASTER": continue # We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will diff --git a/worlds/lingo/regions.py b/worlds/lingo/regions.py index c24144a1609e..bdc42f42f5ec 100644 --- a/worlds/lingo/regions.py +++ b/worlds/lingo/regions.py @@ -4,7 +4,7 @@ from .items import LingoItem from .locations import LingoLocation from .player_logic import LingoPlayerLogic -from .rules import lingo_can_use_entrance, lingo_can_use_pilgrimage, make_location_lambda +from .rules import lingo_can_use_entrance, make_location_lambda from .static_logic import ALL_ROOMS, PAINTINGS, Room, RoomAndDoor if TYPE_CHECKING: @@ -25,15 +25,6 @@ def create_region(room: Room, world: "LingoWorld", player_logic: LingoPlayerLogi return new_region -def handle_pilgrim_room(regions: Dict[str, Region], world: "LingoWorld", player_logic: LingoPlayerLogic) -> None: - target_region = regions["Pilgrim Antechamber"] - source_region = regions["Outside The Agreeable"] - source_region.connect( - target_region, - "Pilgrimage", - lambda state: lingo_can_use_pilgrimage(state, world, player_logic)) - - def connect_entrance(regions: Dict[str, Region], source_region: Region, target_region: Region, description: str, door: Optional[RoomAndDoor], world: "LingoWorld", player_logic: LingoPlayerLogic): connection = Entrance(world.player, description, source_region) @@ -91,7 +82,9 @@ def create_regions(world: "LingoWorld", player_logic: LingoPlayerLogic) -> None: connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, entrance.door, world, player_logic) - handle_pilgrim_room(regions, world, player_logic) + # Add the fake pilgrimage. + connect_entrance(regions, regions["Outside The Agreeable"], regions["Pilgrim Antechamber"], "Pilgrimage", + RoomAndDoor("Pilgrim Antechamber", "Pilgrimage"), world, player_logic) if early_color_hallways: regions["Starting Room"].connect(regions["Outside The Undeterred"], "Early Color Hallways") diff --git a/worlds/lingo/rules.py b/worlds/lingo/rules.py index ee9dcc41929f..481fab18b5a1 100644 --- a/worlds/lingo/rules.py +++ b/worlds/lingo/rules.py @@ -17,23 +17,6 @@ def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, return _lingo_can_open_door(state, effective_room, door.door, world, player_logic) -def lingo_can_use_pilgrimage(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic): - fake_pilgrimage = [ - ["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"], - ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], - ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], - ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], - ["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], - ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"], - ["Outside The Agreeable", "Tenacious Entrance"] - ] - for entrance in fake_pilgrimage: - if not _lingo_can_open_door(state, entrance[0], entrance[1], world, player_logic): - return False - - return True - - def lingo_can_use_location(state: CollectionState, location: PlayerLocation, world: "LingoWorld", player_logic: LingoPlayerLogic): return _lingo_can_satisfy_requirements(state, location.access, world, player_logic) @@ -56,6 +39,12 @@ def lingo_can_use_level_2_location(state: CollectionState, world: "LingoWorld", counted_panels += panel_count if counted_panels >= world.options.level_2_requirement.value - 1: return True + # THE MASTER has to be handled separately, because it has special access rules. + if state.can_reach("Orange Tower Seventh Floor", "Region", world.player)\ + and lingo_can_use_mastery_location(state, world, player_logic): + counted_panels += 1 + if counted_panels >= world.options.level_2_requirement.value - 1: + return True return False diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb index bed5188e3163..3ac49dc220ce 100644 --- a/worlds/lingo/utils/validate_config.rb +++ b/worlds/lingo/utils/validate_config.rb @@ -40,7 +40,7 @@ door_groups = {} directives = Set["entrances", "panels", "doors", "paintings", "progression"] -panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting"] +panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt"] door_directives = Set["id", "painting_id", "panels", "item_name", "location_name", "skip_location", "skip_item", "group", "include_reduce", "junk_item", "event"] painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] From 3a096773331a6a701c1f57402d9a5c778887760e Mon Sep 17 00:00:00 2001 From: Yussur Mustafa Oraji Date: Sun, 10 Dec 2023 20:31:43 +0100 Subject: [PATCH 46/56] sm64ex: Fix generations (#2583) --- worlds/sm64ex/Regions.py | 86 +++++++++++++++++++++++++++------------- worlds/sm64ex/Rules.py | 37 ++++++++--------- 2 files changed, 77 insertions(+), 46 deletions(-) diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index d0e767e7ecde..d426804c30f3 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -1,4 +1,7 @@ import typing + +from enum import Enum + from BaseClasses import MultiWorld, Region, Entrance, Location from .Locations import SM64Location, location_table, locBoB_table, locWhomp_table, locJRB_table, locCCM_table, \ locBBH_table, \ @@ -7,36 +10,63 @@ locPSS_table, locSA_table, locBitDW_table, locTotWC_table, locCotMC_table, \ locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table -# sm64paintings is dict of entrances, format LEVEL | AREA -sm64_level_to_paintings = { - 91: "Bob-omb Battlefield", - 241: "Whomp's Fortress", - 121: "Jolly Roger Bay", - 51: "Cool, Cool Mountain", - 41: "Big Boo's Haunt", - 71: "Hazy Maze Cave", - 221: "Lethal Lava Land", - 81: "Shifting Sand Land", - 231: "Dire, Dire Docks", - 101: "Snowman's Land", - 111: "Wet-Dry World", - 361: "Tall, Tall Mountain", - 132: "Tiny-Huge Island (Tiny)", - 131: "Tiny-Huge Island (Huge)", - 141: "Tick Tock Clock", - 151: "Rainbow Ride" +class SM64Levels(int, Enum): + BOB_OMB_BATTLEFIELD = 91 + WHOMPS_FORTRESS = 241 + JOLLY_ROGER_BAY = 121 + COOL_COOL_MOUNTAIN = 51 + BIG_BOOS_HAUNT = 41 + HAZY_MAZE_CAVE = 71 + LETHAL_LAVA_LAND = 221 + SHIFTING_SAND_LAND = 81 + DIRE_DIRE_DOCKS = 231 + SNOWMANS_LAND = 101 + WET_DRY_WORLD = 111 + TALL_TALL_MOUNTAIN = 361 + TINY_HUGE_ISLAND_TINY = 132 + TINY_HUGE_ISLAND_HUGE = 131 + TICK_TOCK_CLOCK = 141 + RAINBOW_RIDE = 151 + THE_PRINCESS_SECRET_SLIDE = 271 + THE_SECRET_AQUARIUM = 201 + BOWSER_IN_THE_DARK_WORLD = 171 + TOWER_OF_THE_WING_CAP = 291 + CAVERN_OF_THE_METAL_CAP = 281 + VANISH_CAP_UNDER_THE_MOAT = 181 + BOWSER_IN_THE_FIRE_SEA = 191 + WING_MARIO_OVER_THE_RAINBOW = 311 + +# sm64paintings is a dict of entrances, format LEVEL | AREA +sm64_level_to_paintings: typing.Dict[SM64Levels, str] = { + SM64Levels.BOB_OMB_BATTLEFIELD: "Bob-omb Battlefield", + SM64Levels.WHOMPS_FORTRESS: "Whomp's Fortress", + SM64Levels.JOLLY_ROGER_BAY: "Jolly Roger Bay", + SM64Levels.COOL_COOL_MOUNTAIN: "Cool, Cool Mountain", + SM64Levels.BIG_BOOS_HAUNT: "Big Boo's Haunt", + SM64Levels.HAZY_MAZE_CAVE: "Hazy Maze Cave", + SM64Levels.LETHAL_LAVA_LAND: "Lethal Lava Land", + SM64Levels.SHIFTING_SAND_LAND: "Shifting Sand Land", + SM64Levels.DIRE_DIRE_DOCKS: "Dire, Dire Docks", + SM64Levels.SNOWMANS_LAND: "Snowman's Land", + SM64Levels.WET_DRY_WORLD: "Wet-Dry World", + SM64Levels.TALL_TALL_MOUNTAIN: "Tall, Tall Mountain", + SM64Levels.TINY_HUGE_ISLAND_TINY: "Tiny-Huge Island (Tiny)", + SM64Levels.TINY_HUGE_ISLAND_HUGE: "Tiny-Huge Island (Huge)", + SM64Levels.TICK_TOCK_CLOCK: "Tick Tock Clock", + SM64Levels.RAINBOW_RIDE: "Rainbow Ride" } sm64_paintings_to_level = { painting: level for (level,painting) in sm64_level_to_paintings.items() } -# sm64secrets is list of secret areas, same format -sm64_level_to_secrets = { - 271: "The Princess's Secret Slide", - 201: "The Secret Aquarium", - 171: "Bowser in the Dark World", - 291: "Tower of the Wing Cap", - 281: "Cavern of the Metal Cap", - 181: "Vanish Cap under the Moat", - 191: "Bowser in the Fire Sea", - 311: "Wing Mario over the Rainbow" + +# sm64secrets is a dict of secret areas, same format as sm64paintings +sm64_level_to_secrets: typing.Dict[SM64Levels, str] = { + SM64Levels.THE_PRINCESS_SECRET_SLIDE: "The Princess's Secret Slide", + SM64Levels.THE_SECRET_AQUARIUM: "The Secret Aquarium", + SM64Levels.BOWSER_IN_THE_DARK_WORLD: "Bowser in the Dark World", + SM64Levels.TOWER_OF_THE_WING_CAP: "Tower of the Wing Cap", + SM64Levels.CAVERN_OF_THE_METAL_CAP: "Cavern of the Metal Cap", + SM64Levels.VANISH_CAP_UNDER_THE_MOAT: "Vanish Cap under the Moat", + SM64Levels.BOWSER_IN_THE_FIRE_SEA: "Bowser in the Fire Sea", + SM64Levels.WING_MARIO_OVER_THE_RAINBOW: "Wing Mario over the Rainbow" } sm64_secrets_to_level = { secret: level for (level,secret) in sm64_level_to_secrets.items() } diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index d21ac30004e0..5f85bcdafd68 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -1,5 +1,5 @@ from ..generic.Rules import add_rule -from .Regions import connect_regions, sm64_level_to_paintings, sm64_paintings_to_level, sm64_level_to_secrets, sm64_entrances_to_level, sm64_level_to_entrances +from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level, sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances def shuffle_dict_keys(world, obj: dict) -> dict: keys = list(obj.keys()) @@ -7,12 +7,16 @@ def shuffle_dict_keys(world, obj: dict) -> dict: world.random.shuffle(keys) return dict(zip(keys,values)) -def fix_reg(entrance_ids, entrance, destination, swapdict, world): - if entrance_ids[entrance] == destination: # Unlucky :C - rand = world.random.choice(swapdict.keys()) - entrance_ids[entrance], entrance_ids[swapdict[rand]] = rand, entrance_ids[entrance] - swapdict[rand] = entrance_ids[entrance] - swapdict.pop(entrance) +def fix_reg(entrance_map: dict, entrance: SM64Levels, invalid_regions: set, + swapdict: dict, world): + if entrance_map[entrance] in invalid_regions: # Unlucky :C + replacement_regions = [(rand_region, rand_entrance) for rand_region, rand_entrance in swapdict.items() + if rand_region not in invalid_regions] + rand_region, rand_entrance = world.random.choice(replacement_regions) + old_dest = entrance_map[entrance] + entrance_map[entrance], entrance_map[rand_entrance] = rand_region, old_dest + swapdict[rand_region] = entrance + swapdict.pop(entrance_map[entrance]) # Entrance now fixed to rand_region def set_rules(world, player: int, area_connections: dict): randomized_level_to_paintings = sm64_level_to_paintings.copy() @@ -24,19 +28,16 @@ def set_rules(world, player: int, area_connections: dict): randomized_entrances = { **randomized_level_to_paintings, **randomized_level_to_secrets } if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool randomized_entrances = shuffle_dict_keys(world,randomized_entrances) + swapdict = { entrance: level for (level,entrance) in randomized_entrances.items() } # Guarantee first entrance is a course - swapdict = { entrance: level for (level,entrance) in randomized_entrances } - if randomized_entrances[91] not in sm64_paintings_to_level.keys(): # Unlucky :C (91 -> BoB Entrance) - rand = world.random.choice(sm64_paintings_to_level.values()) - randomized_entrances[91], randomized_entrances[swapdict[rand]] = rand, randomized_entrances[91] - swapdict[rand] = randomized_entrances[91] - swapdict.pop("Bob-omb Battlefield") - # Guarantee COTMC is not mapped to HMC, cuz thats impossible - fix_reg(randomized_entrances, "Cavern of the Metal Cap", "Hazy Maze Cave", swapdict, world) + fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, world) # Guarantee BITFS is not mapped to DDD - fix_reg(randomized_entrances, "Bowser in the Fire Sea", "Dire, Dire Docks", swapdict, world) - if randomized_entrances[191] == "Hazy Maze Cave": # If BITFS is mapped to HMC... - fix_reg(randomized_entrances, "Cavern of the Metal Cap", "Dire, Dire Docks", swapdict, world) # ... then dont allow COTMC to be mapped to DDD + fix_reg(randomized_entrances, SM64Levels.BOWSER_IN_THE_FIRE_SEA, {"Dire, Dire Docks"}, swapdict, world) + # Guarantee COTMC is not mapped to HMC, cuz thats impossible. If BitFS -> HMC, also no COTMC -> DDD. + if randomized_entrances[SM64Levels.BOWSER_IN_THE_FIRE_SEA] == "Hazy Maze Cave": + fix_reg(randomized_entrances, SM64Levels.CAVERN_OF_THE_METAL_CAP, {"Hazy Maze Cave", "Dire, Dire Docks"}, swapdict, world) + else: + fix_reg(randomized_entrances, SM64Levels.CAVERN_OF_THE_METAL_CAP, {"Hazy Maze Cave"}, swapdict, world) # Destination Format: LVL | AREA with LVL = LEVEL_x, AREA = Area as used in sm64 code area_connections.update({entrance_lvl: sm64_entrances_to_level[destination] for (entrance_lvl,destination) in randomized_entrances.items()}) From e2109dba507d97d45fe4d23e3416b894a709ea16 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sun, 10 Dec 2023 20:35:46 +0100 Subject: [PATCH 47/56] The Witness: Fix Logic Error for Keep Pressure Plates 2 EP in puzzle_randomization: none (#2515) --- worlds/witness/WitnessLogicVanilla.txt | 2 +- worlds/witness/items.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/WitnessLogicVanilla.txt index 8591a30d1fbb..779ead6bde4b 100644 --- a/worlds/witness/WitnessLogicVanilla.txt +++ b/worlds/witness/WitnessLogicVanilla.txt @@ -430,7 +430,7 @@ Door - 0x04F8F (Tower Shortcut) - 0x0361B 158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers Laser - 0x014BB (Laser) - 0x0360E | 0x03317 159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True 159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True 159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True 159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True diff --git a/worlds/witness/items.py b/worlds/witness/items.py index 15c693b25dd4..a8c889de937a 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/items.py @@ -115,6 +115,7 @@ def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: Witn # Adjust item classifications based on game settings. eps_shuffled = self._world.options.shuffle_EPs come_to_you = self._world.options.elevators_come_to_you + difficulty = self._world.options.puzzle_randomization for item_name, item_data in self.item_data.items(): if not eps_shuffled and item_name in {"Monastery Garden Entry (Door)", "Monastery Shortcuts", @@ -130,10 +131,12 @@ def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: Witn "Monastery Laser Shortcut (Door)", "Orchard Second Gate (Door)", "Jungle Bamboo Laser Shortcut (Door)", - "Keep Pressure Plates 2 Exit (Door)", "Caves Elevator Controls (Panel)"}: # Downgrade doors that don't gate progress. item_data.classification = ItemClassification.useful + elif item_name == "Keep Pressure Plates 2 Exit (Door)" and not (difficulty == "none" and eps_shuffled): + # PP2EP requires the door in vanilla puzzles, otherwise it's unnecessary + item_data.classification = ItemClassification.useful # Build the mandatory item list. self._mandatory_items: Dict[str, int] = {} From 81425641561cf2efc0e57d46e46367400c70e3eb Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sun, 10 Dec 2023 20:36:55 +0100 Subject: [PATCH 48/56] The Witness: Fix non-deterministic hints (#2514) --- worlds/witness/hints.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 1e54ec352cb6..d238aa4adfb6 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -161,7 +161,7 @@ ] -def get_always_hint_items(world: "WitnessWorld"): +def get_always_hint_items(world: "WitnessWorld") -> List[str]: always = [ "Boat", "Caves Shortcuts", @@ -187,17 +187,17 @@ def get_always_hint_items(world: "WitnessWorld"): return always -def get_always_hint_locations(_: "WitnessWorld"): - return { +def get_always_hint_locations(_: "WitnessWorld") -> List[str]: + return [ "Challenge Vault Box", "Mountain Bottom Floor Discard", "Theater Eclipse EP", "Shipwreck Couch EP", "Mountainside Cloud Cycle EP", - } + ] -def get_priority_hint_items(world: "WitnessWorld"): +def get_priority_hint_items(world: "WitnessWorld") -> List[str]: priority = { "Caves Mountain Shortcut (Door)", "Caves Swamp Shortcut (Door)", @@ -246,11 +246,11 @@ def get_priority_hint_items(world: "WitnessWorld"): lasers.append("Desert Laser") priority.update(world.random.sample(lasers, 6)) - return priority + return sorted(priority) -def get_priority_hint_locations(_: "WitnessWorld"): - return { +def get_priority_hint_locations(_: "WitnessWorld") -> List[str]: + return [ "Swamp Purple Underwater", "Shipwreck Vault Box", "Town RGB Room Left", @@ -264,7 +264,7 @@ def get_priority_hint_locations(_: "WitnessWorld"): "Tunnels Theater Flowers EP", "Boat Shipwreck Green EP", "Quarry Stoneworks Control Room Left", - } + ] def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]): @@ -365,8 +365,8 @@ def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item] remaining_hints = hint_amount - len(hints) priority_hint_amount = int(max(0.0, min(len(priority_hint_pairs) / 2, remaining_hints / 2))) - prog_items_in_this_world = sorted(list(prog_items_in_this_world)) - locations_in_this_world = sorted(list(loc_in_this_world)) + prog_items_in_this_world = sorted(prog_items_in_this_world) + locations_in_this_world = sorted(loc_in_this_world) world.random.shuffle(prog_items_in_this_world) world.random.shuffle(locations_in_this_world) From 1a05bad612040ba734e40f80539ae444afa07cd8 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 10 Dec 2023 20:38:49 +0100 Subject: [PATCH 49/56] Core: update modules (#2551) --- WebHostLib/requirements.txt | 2 +- requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WebHostLib/requirements.txt b/WebHostLib/requirements.txt index 654104252cec..62707d78cf1f 100644 --- a/WebHostLib/requirements.txt +++ b/WebHostLib/requirements.txt @@ -5,5 +5,5 @@ Flask-Caching>=2.1.0 Flask-Compress>=1.14 Flask-Limiter>=3.5.0 bokeh>=3.1.1; python_version <= '3.8' -bokeh>=3.2.2; python_version >= '3.9' +bokeh>=3.3.2; python_version >= '3.9' markupsafe>=2.1.3 diff --git a/requirements.txt b/requirements.txt index 7d93928bb5fc..0db55a803591 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ colorama>=0.4.5 -websockets>=11.0.3 +websockets>=12.0 PyYAML>=6.0.1 jellyfish>=1.0.3 jinja2>=3.1.2 schema>=0.7.5 -kivy>=2.2.0 +kivy>=2.2.1 bsdiff4>=1.2.4 platformdirs>=4.0.0 certifi>=2023.11.17 -cython>=3.0.5 +cython>=3.0.6 cymem>=2.0.8 orjson>=3.9.10 \ No newline at end of file From e8f96dabe80bfd7c3d89195e69aad3c3be906581 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 10 Dec 2023 20:42:07 +0100 Subject: [PATCH 50/56] Core: faster prog balance (#2586) * Core: rename world to multiworld in balance_multiworld_progression * Core: small optimization to progression balance speed --- Fill.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/Fill.py b/Fill.py index 342c155079dd..525d27d3388e 100644 --- a/Fill.py +++ b/Fill.py @@ -550,7 +550,7 @@ def flood_items(world: MultiWorld) -> None: break -def balance_multiworld_progression(world: MultiWorld) -> None: +def balance_multiworld_progression(multiworld: MultiWorld) -> None: # A system to reduce situations where players have no checks remaining, popularly known as "BK mode." # Overall progression balancing algorithm: # Gather up all locations in a sphere. @@ -558,28 +558,28 @@ def balance_multiworld_progression(world: MultiWorld) -> None: # If other players are below the threshold value, swap progression in this sphere into earlier spheres, # which gives more locations available by this sphere. balanceable_players: typing.Dict[int, float] = { - player: world.worlds[player].options.progression_balancing / 100 - for player in world.player_ids - if world.worlds[player].options.progression_balancing > 0 + player: multiworld.worlds[player].options.progression_balancing / 100 + for player in multiworld.player_ids + if multiworld.worlds[player].options.progression_balancing > 0 } if not balanceable_players: logging.info('Skipping multiworld progression balancing.') else: logging.info(f'Balancing multiworld progression for {len(balanceable_players)} Players.') logging.debug(balanceable_players) - state: CollectionState = CollectionState(world) + state: CollectionState = CollectionState(multiworld) checked_locations: typing.Set[Location] = set() - unchecked_locations: typing.Set[Location] = set(world.get_locations()) + unchecked_locations: typing.Set[Location] = set(multiworld.get_locations()) total_locations_count: typing.Counter[int] = Counter( location.player - for location in world.get_locations() + for location in multiworld.get_locations() if not location.locked ) reachable_locations_count: typing.Dict[int, int] = { player: 0 - for player in world.player_ids - if total_locations_count[player] and len(world.get_filled_locations(player)) != 0 + for player in multiworld.player_ids + if total_locations_count[player] and len(multiworld.get_filled_locations(player)) != 0 } balanceable_players = { player: balanceable_players[player] @@ -658,7 +658,7 @@ def item_percentage(player: int, num: int) -> float: balancing_unchecked_locations.remove(location) if not location.locked: balancing_reachables[location.player] += 1 - if world.has_beaten_game(balancing_state) or all( + if multiworld.has_beaten_game(balancing_state) or all( item_percentage(player, reachables) >= threshold_percentages[player] for player, reachables in balancing_reachables.items() if player in threshold_percentages): @@ -675,7 +675,7 @@ def item_percentage(player: int, num: int) -> float: locations_to_test = unlocked_locations[player] items_to_test = list(candidate_items[player]) items_to_test.sort() - world.random.shuffle(items_to_test) + multiworld.random.shuffle(items_to_test) while items_to_test: testing = items_to_test.pop() reducing_state = state.copy() @@ -687,8 +687,8 @@ def item_percentage(player: int, num: int) -> float: reducing_state.sweep_for_events(locations=locations_to_test) - if world.has_beaten_game(balancing_state): - if not world.has_beaten_game(reducing_state): + if multiworld.has_beaten_game(balancing_state): + if not multiworld.has_beaten_game(reducing_state): items_to_replace.append(testing) else: reduced_sphere = get_sphere_locations(reducing_state, locations_to_test) @@ -696,33 +696,32 @@ def item_percentage(player: int, num: int) -> float: if p < threshold_percentages[player]: items_to_replace.append(testing) - replaced_items = False + old_moved_item_count = moved_item_count # sort then shuffle to maintain deterministic behaviour, # while allowing use of set for better algorithm growth behaviour elsewhere replacement_locations = sorted(l for l in checked_locations if not l.event and not l.locked) - world.random.shuffle(replacement_locations) + multiworld.random.shuffle(replacement_locations) items_to_replace.sort() - world.random.shuffle(items_to_replace) + multiworld.random.shuffle(items_to_replace) # Start swapping items. Since we swap into earlier spheres, no need for accessibility checks. while replacement_locations and items_to_replace: old_location = items_to_replace.pop() - for new_location in replacement_locations: + for i, new_location in enumerate(replacement_locations): if new_location.can_fill(state, old_location.item, False) and \ old_location.can_fill(state, new_location.item, False): - replacement_locations.remove(new_location) + replacement_locations.pop(i) swap_location_item(old_location, new_location) logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, " f"displacing {old_location.item} into {old_location}") moved_item_count += 1 state.collect(new_location.item, True, new_location) - replaced_items = True break else: logging.warning(f"Could not Progression Balance {old_location.item}") - if replaced_items: + if old_moved_item_count < moved_item_count: logging.debug(f"Moved {moved_item_count} items so far\n") unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]} for location in get_sphere_locations(state, unlocked): @@ -736,7 +735,7 @@ def item_percentage(player: int, num: int) -> float: state.collect(location.item, True, location) checked_locations |= sphere_locations - if world.has_beaten_game(state): + if multiworld.has_beaten_game(state): break elif not sphere_locations: logging.warning("Progression Balancing ran out of paths.") From 13122ab4668681772261195b88bf4c496152b55f Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 10 Dec 2023 20:42:41 +0100 Subject: [PATCH 51/56] Core: remove start_inventory_from_pool from early_items (#2579) --- Main.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Main.py b/Main.py index b64650478bfe..8dac8f7d20eb 100644 --- a/Main.py +++ b/Main.py @@ -117,6 +117,17 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for item_name, count in world.start_inventory_from_pool.setdefault(player, StartInventoryPool({})).value.items(): for _ in range(count): world.push_precollected(world.create_item(item_name, player)) + # remove from_pool items also from early items handling, as starting is plenty early. + early = world.early_items[player].get(item_name, 0) + if early: + world.early_items[player][item_name] = max(0, early-count) + remaining_count = count-early + if remaining_count > 0: + local_early = world.early_local_items[player].get(item_name, 0) + if local_early: + world.early_items[player][item_name] = max(0, local_early - remaining_count) + del local_early + del early logger.info('Creating World.') AutoWorld.call_all(world, "create_regions") From d9d282c9259f79abc0e029d27075b45c3a631fbd Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Mon, 11 Dec 2023 19:14:44 -0600 Subject: [PATCH 52/56] Tests: test that the datapackage after generation is still valid (#2575) --- test/general/test_ids.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/test/general/test_ids.py b/test/general/test_ids.py index 4edfb8d994ef..98c41b67b176 100644 --- a/test/general/test_ids.py +++ b/test/general/test_ids.py @@ -1,5 +1,8 @@ import unittest -from worlds.AutoWorld import AutoWorldRegister + +from Fill import distribute_items_restrictive +from worlds.AutoWorld import AutoWorldRegister, call_all +from . import setup_solo_multiworld class TestIDs(unittest.TestCase): @@ -66,3 +69,34 @@ def test_duplicate_location_ids(self): for gamename, world_type in AutoWorldRegister.world_types.items(): with self.subTest(game=gamename): self.assertEqual(len(world_type.location_id_to_name), len(world_type.location_name_to_id)) + + def test_postgen_datapackage(self): + """Generates a solo multiworld and checks that the datapackage is still valid""" + for gamename, world_type in AutoWorldRegister.world_types.items(): + with self.subTest(game=gamename): + multiworld = setup_solo_multiworld(world_type) + distribute_items_restrictive(multiworld) + call_all(multiworld, "post_fill") + datapackage = world_type.get_data_package_data() + for item_group, item_names in datapackage["item_name_groups"].items(): + self.assertIsInstance(item_group, str, + f"item_name_group names should be strings: {item_group}") + for item_name in item_names: + self.assertIsInstance(item_name, str, + f"{item_name}, in group {item_group} is not a string") + for loc_group, loc_names in datapackage["location_name_groups"].items(): + self.assertIsInstance(loc_group, str, + f"location_name_group names should be strings: {loc_group}") + for loc_name in loc_names: + self.assertIsInstance(loc_name, str, + f"{loc_name}, in group {loc_group} is not a string") + for item_name, item_id in datapackage["item_name_to_id"].items(): + self.assertIsInstance(item_name, str, + f"{item_name} is not a valid item name for item_name_to_id") + self.assertIsInstance(item_id, int, + f"{item_id} for {item_name} should be an int") + for loc_name, loc_id in datapackage["location_name_to_id"].items(): + self.assertIsInstance(loc_name, str, + f"{loc_name} is not a valid item name for location_name_to_id") + self.assertIsInstance(loc_id, int, + f"{loc_id} for {loc_name} should be an int") From e9317d403176723cbf58584b8d597726acb0a6e8 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Mon, 11 Dec 2023 20:39:38 -0500 Subject: [PATCH 53/56] FFMQR: Fix Empty Kaeli Companion Event Location and Spellbook option (#2591) --- worlds/ffmq/Regions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py index aac8289a3600..61f70864c0b4 100644 --- a/worlds/ffmq/Regions.py +++ b/worlds/ffmq/Regions.py @@ -67,10 +67,10 @@ def create_regions(self): self.multiworld.regions.append(create_region(self.multiworld, self.player, room["name"], room["id"], [FFMQLocation(self.player, object["name"], location_table[object["name"]] if object["name"] in location_table else None, object["type"], object["access"], - self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for - object in room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in - ("BattlefieldGp", "BattlefieldXp") and (object["type"] != "Box" or - self.multiworld.brown_boxes[self.player] == "include")], room["links"])) + self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for object in + room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in ("BattlefieldGp", + "BattlefieldXp") and (object["type"] != "Box" or self.multiworld.brown_boxes[self.player] == "include") and + not (object["name"] == "Kaeli Companion" and not object["on_trigger"])], room["links"])) dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player) dark_king = FFMQLocation(self.player, "Dark King", None, "Trigger", []) From 45fa9a8f9e6c28368968fced26878ddf02524636 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Mon, 11 Dec 2023 20:48:20 -0800 Subject: [PATCH 54/56] BizHawkClient: Add SGB to systems using explicit vblank callback (#2593) --- data/lua/connector_bizhawk_generic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/lua/connector_bizhawk_generic.lua b/data/lua/connector_bizhawk_generic.lua index c4e729300dac..eff400cb032b 100644 --- a/data/lua/connector_bizhawk_generic.lua +++ b/data/lua/connector_bizhawk_generic.lua @@ -585,7 +585,7 @@ else -- misaligned, so for GB and GBC we explicitly set the callback on -- vblank instead. -- https://github.com/TASEmulators/BizHawk/issues/3711 - if emu.getsystemid() == "GB" or emu.getsystemid() == "GBC" then + if emu.getsystemid() == "GB" or emu.getsystemid() == "GBC" or emu.getsystemid() == "SGB" then event.onmemoryexecute(tick, 0x40, "tick", "System Bus") else event.onframeend(tick) From db1d195cb07244e0eceb365d2a6c9c5a6cc11c58 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 12 Dec 2023 20:11:10 -0600 Subject: [PATCH 55/56] Hollow Knight: remove unused option check (#2595) --- worlds/hk/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index f7e7e22e69dd..8b07b34eb0f2 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -419,17 +419,16 @@ def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: def set_rules(self): world = self.multiworld player = self.player - if world.logic[player] != 'nologic': - goal = world.Goal[player] - if goal == Goal.option_hollowknight: - world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) - elif goal == Goal.option_siblings: - world.completion_condition[player] = lambda state: state._hk_siblings_ending(player) - elif goal == Goal.option_radiance: - world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player) - else: - # Any goal - world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player) + goal = world.Goal[player] + if goal == Goal.option_hollowknight: + world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) + elif goal == Goal.option_siblings: + world.completion_condition[player] = lambda state: state._hk_siblings_ending(player) + elif goal == Goal.option_radiance: + world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player) + else: + # Any goal + world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player) set_rules(self) From 0eefe9e93646ef5f7915a4b8962d50f0fed8e54f Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Tue, 12 Dec 2023 20:12:16 -0600 Subject: [PATCH 56/56] WebHost: Some refactors and additional checks when uploading files. (#2549) --- WebHostLib/check.py | 59 +++++++++++++++++++++++--------------------- WebHostLib/upload.py | 58 +++++++++++++++++++++++++------------------ 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/WebHostLib/check.py b/WebHostLib/check.py index 4db2ec2ce35e..e739dda02d79 100644 --- a/WebHostLib/check.py +++ b/WebHostLib/check.py @@ -1,3 +1,4 @@ +import os import zipfile import base64 from typing import Union, Dict, Set, Tuple @@ -6,13 +7,7 @@ from markupsafe import Markup from WebHostLib import app - -banned_zip_contents = (".sfc",) - - -def allowed_file(filename): - return filename.endswith(('.txt', ".yaml", ".zip")) - +from WebHostLib.upload import allowed_options, allowed_options_extensions, banned_file from Generate import roll_settings, PlandoOptions from Utils import parse_yamls @@ -51,33 +46,41 @@ def mysterycheck(): def get_yaml_data(files) -> Union[Dict[str, str], str, Markup]: options = {} for uploaded_file in files: - # if user does not select file, browser also - # submit an empty part without filename - if uploaded_file.filename == '': - return 'No selected file' + if banned_file(uploaded_file.filename): + return ("Uploaded data contained a rom file, which is likely to contain copyrighted material. " + "Your file was deleted.") + # If the user does not select file, the browser will still submit an empty string without a file name. + elif uploaded_file.filename == "": + return "No selected file." elif uploaded_file.filename in options: - return f'Conflicting files named {uploaded_file.filename} submitted' - elif uploaded_file and allowed_file(uploaded_file.filename): + return f"Conflicting files named {uploaded_file.filename} submitted." + elif uploaded_file and allowed_options(uploaded_file.filename): if uploaded_file.filename.endswith(".zip"): - - with zipfile.ZipFile(uploaded_file, 'r') as zfile: - infolist = zfile.infolist() - - if any(file.filename.endswith(".archipelago") for file in infolist): - return Markup("Error: Your .zip file contains an .archipelago file. " - 'Did you mean to host a game?') - - for file in infolist: - if file.filename.endswith(banned_zip_contents): - return ("Uploaded data contained a rom file, " - "which is likely to contain copyrighted material. " - "Your file was deleted.") - elif file.filename.endswith((".yaml", ".json", ".yml", ".txt")): + if not zipfile.is_zipfile(uploaded_file): + return f"Uploaded file {uploaded_file.filename} is not a valid .zip file and cannot be opened." + + uploaded_file.seek(0) # offset from is_zipfile check + with zipfile.ZipFile(uploaded_file, "r") as zfile: + for file in zfile.infolist(): + # Remove folder pathing from str (e.g. "__MACOSX/" folder paths from archives created by macOS). + base_filename = os.path.basename(file.filename) + + if base_filename.endswith(".archipelago"): + return Markup("Error: Your .zip file contains an .archipelago file. " + 'Did you mean to host a game?') + elif base_filename.endswith(".zip"): + return "Nested .zip files inside a .zip are not supported." + elif banned_file(base_filename): + return ("Uploaded data contained a rom file, which is likely to contain copyrighted " + "material. Your file was deleted.") + # Ignore dot-files. + elif not base_filename.startswith(".") and allowed_options(base_filename): options[file.filename] = zfile.open(file, "r").read() else: options[uploaded_file.filename] = uploaded_file.read() + if not options: - return "Did not find a .yaml file to process." + return f"Did not find any valid files to process. Accepted formats: {allowed_options_extensions}" return options diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index e7ac033913e8..8f01294eac9a 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -19,7 +19,22 @@ from . import app from .models import Seed, Room, Slot, GameDataPackage -banned_zip_contents = (".sfc", ".z64", ".n64", ".sms", ".gb") +banned_extensions = (".sfc", ".z64", ".n64", ".nes", ".smc", ".sms", ".gb", ".gbc", ".gba") +allowed_options_extensions = (".yaml", ".json", ".yml", ".txt", ".zip") +allowed_generation_extensions = (".archipelago", ".zip") + + +def allowed_options(filename: str) -> bool: + return filename.endswith(allowed_options_extensions) + + +def allowed_generation(filename: str) -> bool: + return filename.endswith(allowed_generation_extensions) + + +def banned_file(filename: str) -> bool: + return filename.endswith(banned_extensions) + def process_multidata(compressed_multidata, files={}): decompressed_multidata = MultiServer.Context.decompress(compressed_multidata) @@ -61,8 +76,8 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s if not owner: owner = session["_id"] infolist = zfile.infolist() - if all(file.filename.endswith((".yaml", ".yml")) or file.is_dir() for file in infolist): - flash(Markup("Error: Your .zip file only contains .yaml files. " + if all(allowed_options(file.filename) or file.is_dir() for file in infolist): + flash(Markup("Error: Your .zip file only contains options files. " 'Did you mean to generate a game?')) return @@ -73,7 +88,7 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s # Load files. for file in infolist: handler = AutoPatchRegister.get_handler(file.filename) - if file.filename.endswith(banned_zip_contents): + if banned_file(file.filename): return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \ "Your file was deleted." @@ -136,35 +151,34 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s flash("No multidata was found in the zip file, which is required.") -@app.route('/uploads', methods=['GET', 'POST']) +@app.route("/uploads", methods=["GET", "POST"]) def uploads(): - if request.method == 'POST': - # check if the post request has the file part - if 'file' not in request.files: - flash('No file part') + if request.method == "POST": + # check if the POST request has a file part. + if "file" not in request.files: + flash("No file part in POST request.") else: - file = request.files['file'] - # if user does not select file, browser also - # submit an empty part without filename - if file.filename == '': - flash('No selected file') - elif file and allowed_file(file.filename): - if zipfile.is_zipfile(file): - with zipfile.ZipFile(file, 'r') as zfile: + uploaded_file = request.files["file"] + # If the user does not select file, the browser will still submit an empty string without a file name. + if uploaded_file.filename == "": + flash("No selected file.") + elif uploaded_file and allowed_generation(uploaded_file.filename): + if zipfile.is_zipfile(uploaded_file): + with zipfile.ZipFile(uploaded_file, "r") as zfile: try: res = upload_zip_to_db(zfile) except VersionException: flash(f"Could not load multidata. Wrong Version detected.") else: - if type(res) == str: + if res is str: return res elif res: return redirect(url_for("view_seed", seed=res.id)) else: - file.seek(0) # offset from is_zipfile check + uploaded_file.seek(0) # offset from is_zipfile check # noinspection PyBroadException try: - multidata = file.read() + multidata = uploaded_file.read() slots, multidata = process_multidata(multidata) except Exception as e: flash(f"Could not load multidata. File may be corrupted or incompatible. ({e})") @@ -182,7 +196,3 @@ def user_content(): rooms = select(room for room in Room if room.owner == session["_id"]) seeds = select(seed for seed in Seed if seed.owner == session["_id"]) return render_template("userContent.html", rooms=rooms, seeds=seeds) - - -def allowed_file(filename): - return filename.endswith(('.archipelago', ".zip"))