diff --git a/README.md b/README.md index a662c26..ea16390 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,50 @@ # Tunic Randomizer Archipelago Client -This is the Archipelago version of the TUNIC Randomizer mod. If you are looking for the standalone, single-player version of the mod, you can find it [here](https://github.com/silent-destroyer/tunic-randomizer). -This mod features randomization of every item in the game through Archipelago, as well as enemy randomization, enhanced fox customization, custom items, and much, much more! +# PLEASE READ! +The Tunic Archipelago mod has been combined with the single-player randomizer into a single mod, and has moved to the [original Tunic Randomizer repository](https://github.com/silent-destroyer/tunic-randomizer). As a result, this repository will no longer be updated, and all future updates for the Tunic Archipelago integration will be released there. -For questions, feedback, or discussion related to the randomizer, please visit the dedicated randomizer channel in the [Tunic Speedrunning Discord](https://discord.gg/HXkztJgQWj)! +This randomizer features item, entrance, and enemy randomization, as well as a variety of additional settings and features to help customize the experience, such as hints, custom items, and custom fox colors! -The Archipelago version of the TUNIC Randomizer is still very much a work-in-progress, so expect bugs to happen and things to not work correctly. The quickest way to report bugs is to message me (silentdestroyer) directly on Discord. +This mod contains both the standalone, single-player randomizer and the Archipelago Multiworld integration. + +For questions, feedback, bug reports, or other discussion related to the randomizer, please visit the dedicated randomizer channel in the [Tunic Speedrunning Discord](https://discord.gg/HXkztJgQWj)! + +For discussion around the Archipelago side of things, please visit the dedicated Tunic thread in the [Archipelago Discord](https://discord.gg/8Z65BR2). ## Installation - Must use a compatible PC version of TUNIC on the latest update. The mod has been tested on Steam and PC Game Pass versions, but should realistically work on any PC version (including Steam Deck). - If playing on Steam Deck, first follow this guide to [setting up BepInEx on Steam Deck via Proton](https://docs.bepinex.dev/articles/advanced/proton_wine.html). -- Download the correct build/version of BepInEx from here: https://builds.bepinex.dev/projects/bepinex_be/572/BepInEx_UnityIL2CPP_x64_9c2b17f_6.0.0-be.572.zip, or alternatively search for it yourself by going to https://builds.bepinex.dev/projects/bepinex_be, finding Artifact #572, and downloading the "BepInEx Unity IL2CPP for Windows (x64) games" build. +- Download the appropriate IL2CPP release of [BepInEx 6](https://github.com/BepInEx/BepInEx/releases/download/v6.0.0-pre.1/BepInEx_UnityIL2CPP_x64_6.0.0-pre.1.zip). - ![image](https://user-images.githubusercontent.com/110704408/188519149-d9476aa9-55f6-4f38-9ce9-93d137fa71af.png) -- Extract the zip folder you downloaded from the previous step into your game's install directory (For example: C:\Program Files (x86)\Steam\steamapps\common\TUNIC) +- Extract the BepInEx zip folder you downloaded from the previous step into your game's install directory (For example: C:\Program Files (x86)\Steam\steamapps\common\TUNIC) - For the PC Game Pass version, extract the zip into the `Content` folder, i.e. C:\XboxGames\Tunic\Content - Launch the game and close it. This will finalize the BepInEx installation. -- [Download and extract the TunicArchipelago.zip file from the latest release.](https://github.com/silent-destroyer/tunic-randomizer-archipelago/releases/latest) - - Copy the `Tunic Archipelago` folder from the release zip into BepInEx/plugins under your game's install directory. - - This folder contains an `ArchipelagoSettings.json` file, which is where you will fill in your PlayerName, Hostname, Port, and Password (if needed). - - The release download contains two files called `tunic.apworld` and `tunic.yaml`. These are not needed for the mod itself but are used in the next section for setting up a multiworld game. See "Generating a Multiworld". -- Launch the game again and you should see `Randomizer + Archipelago Mod Ver. x.y.z` on the top left of the title screen! -- To uninstall the mod, either remove/delete the `Tunic Archipelago` folder from the plugins folder or rename the winhttp.dll file located in the game's root directory (this will disable all installed mods from running). +- [Download and extract the `Tunic Randomizer.zip` file from the latest release.](https://github.com/silent-destroyer/tunic-randomizer/releases/latest) + - Copy the `Tunic Randomizer` folder from the release zip into `BepInEx/plugins` under your game's install directory. + - Your plugins folder should have a `Tunic Randomizer` folder with `three .dll files` inside. +- Launch the game again and you should see `Randomizer Mod Ver. x.y.z` on the top left of the title screen! +- To uninstall the mod, either remove/delete the `Tunic Randomizer` folder from the plugins folder or rename the winhttp.dll file located in the game's root directory (this will disable all installed mods from running). +## Starting a Single Player Randomizer +- On the Title Screen, select `Single Player`, and select any additional settings you wish to play with. Descriptions of all of them can be found below. +- Optionally, clicking Generate Seed will create a seed that you can then share with others by pressing the Copy Seed/Settings button. This will generate a settings string that another player can import by copying and then pressing Paste Seed/Settings. +- Click New Game and have fun! -## Generating a Multiworld +## Generating a Multiworld with Archipelago - In order to setup a multiworld you must first install the latest release of [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest). - When running the Archipelago Setup exe, you'll at least want to install the Generator and Text Client, but can install any other components you may need as well for other games. - After installing, run `Archipelago Launcher` and click on `Browse Files`. - This will open the local file directory for your Archipelago installation. -- Place `tunic.yaml` from the TunicArchipelago release inside of the `Players` folder. +- In the .zip you downloaded from the release page, there should be two files called `tunic.apworld` and `tunic.yaml`. +- Place `tunic.yaml` from the Tunic Randomizer release inside of the `Players` folder. - `tunic.yaml` needs to be configured before you can generate a game. Open the file in a text editor and fill in your player name and choose the settings you want. - You will also need to get the .yaml files for everyone who is joining your multiworld and place them inside of the `Players` folder as well. -- Place `tunic.apworld` from the TunicArchipelago release inside of `lib/worlds`. +- Place `tunic.apworld` from the Tunic Randomizer release inside of `lib/worlds`. - Once all of the .yaml fies have been configured and placed into `Players`, click `Generate` in the Archipelago Launcher. -- A .zip file will be created in the `output` folder containing the information for your multiworld. In order to host your game, go to [https://archipelago.gg/uploads](https://archipelago.gg/uploads) and upload the .zip file, then click `Create New Room`. -- Configure your `ArchipelagoSettings.json` from before with your player name and the hostname/port you see in the newly created room (Ex: archipelago.gg:32517, the hostname is `archipelago.gg` and the port is `32517`). -- All that's left is to launch the game and, if it says `Connected`, start a New Game and being your game! +- A .zip file will be created in the `output` folder containing the information for your multiworld. In order to host your game, go to [https://archipelago.gg/uploads](https://archipelago.gg/uploads) and upload the .zip file, then click `Create New Room`. Once the room is generated, write down/copy the port number that is displayed for the port. +- Launch the game and select `Archipelago` on the Title Screen, then click `Edit AP Config` and fill in your connection details. Alternatively, you can click `Open Settings File` to edit the file with the connection settings directly. +- All that's left is to press Connect, and if it says `Connected`, simply start a New Game and have fun! - For more information, see the official [Archipelago Setup Guide](https://archipelago.gg/tutorial/Archipelago/setup/en). ## Helpful Tips @@ -45,6 +52,7 @@ The Archipelago version of the TUNIC Randomizer is still very much a work-in-pro - West Garden is logically accessible with either the Lantern or Hero's Laurels. - The Eastern Vault Fortress is accessible with either the Lantern (and Prayer) or Hero's Laurels (with or without Prayer). - The Library is accessible with either the Hero's Laurels or the Magic Orb. + - The Swamp is available from the start via a secret path beneath the Overworld fuse, and another through the water to the right of the Swamp save statue. - The Cathedral is accessible during the day by using the Hero's Laurels to access and activate the Overworld fuse near the Swamp entrance. - The Scavenger's Mask is in logic to not be shuffled into the lower areas of Quarry or within the Ziggurat. - The Holy Cross chest from the dancing fox ghost in East Forest can be spawned in during the daytime. @@ -61,117 +69,122 @@ The Archipelago version of the TUNIC Randomizer is still very much a work-in-pro - The portal in the trophy room in the back of the Old House is active from the start. - The elevator in Cathedral is immediately accessible without activating the fuse. Activating the fuse does nothing. - Regarding Hints: - - The Mailbox will hint the general location of the Lantern. - - The Hero's Grave in the Swamp, Monastery, and Library will hint the location of the three Hexagon keys. - - The Hero's Grave in East Forest, West Garden, and the Eastern Vault Fortress are "Path of the Hero" hints, which hint towards a major progression item, such as Grapple, Lantern, Fire Wand, Ice Dagger, and/or the Prayer/Holy Cross pages (if playing with Ability Shuffling). + - The Mailbox will give a "First Steps" hint, pointing you in the direction of a useful/progression item that can be reached from the start of the game. + - The Hero's Graves in the Swamp, Monastery, and Library will hint the location of the three Hexagon keys. + - The Hero's Graves in East Forest, West Garden, and the Eastern Vault Fortress hint towards a major progression item, such as the Magic Orb, Lantern, Magic Wand, Magic Dagger, and/or the Prayer/Holy Cross pages if abilities are shuffled. - The statue in the Sealed Temple will always hint the general location of the Hero's Laurels. -- The "Fairy Seeker" Holy Cross spell (ULURDR) can now be used to seek out all items in an area, instead of just fairies. If all items in an area have been found, the fairy seeker will seek out the closest load zone that has items immediately beyond it. Useful for finding missing checks in areas with lots of obscured or hidden items! -- Save files created by the randomizer will be marked with a "randomizer" tag in the file select screen to help differentiate them from your vanilla save files while the mod is loaded. -- The Randomizer will routinely write to a couple of files in the Randomizer folder under the game's AppData directory (typically C:\Users\You\AppData\LocalLow\Andrew Shouldice\Secret Legend\Randomizer): +- The "Fairy Seeker" Holy Cross spell (ULURDR) can be used to seek out all items in an area, instead of just fairies. If all items in an area have been found, the fairy seeker will seek out the closest load zone that has items immediately beyond it. Useful for finding missing checks in areas with lots of obscured or hidden items! +- Save files created by the randomizer will be marked with a "randomizer" or "archipelago" tag in the file select screen to help differentiate them from your vanilla save files while the mod is loaded. The mod has protections in place to avoid loading vanilla saves on accident as well. +- The mod will routinely write to a couple of files in the Randomizer folder under the game's AppData directory (typically C:\Users\You\AppData\LocalLow\Andrew Shouldice\Secret Legend\Randomizer): - Spoiler.log - This file lists every check in the game and what item they contain. It will also mark off which checks you have collected. - - ItemTracker.json - This file contains information such as the current seed number, what important items have been obtained, and a running list of all checks that have been found. Can be useful for creating external programs that interface with the randomizer, such as this [Tunic Rando Tracker](https://github.com/radicoon/tunic-rando-tracker) by Radicoon. -- Custom seeds can be set on the title screen before starting a New Game, or can be changed by modifying the seed value in the save file created after starting a New Game. Pressing '2' on keyboard while in-game will display the current seed and settings being used. -- The Randomizer will notify you on the title screen if a new update is available for the mod. -- An [EmoTracker Package](https://github.com/SapphireSapphic/TunicTracker) exists for this game with a full map tracker, created by SapphireSapphic and ScoutJD. + - ItemTracker.json - This file contains information such as the current seed number, what important items have been obtained, and a running list of all checks that have been found. Can be useful for creating external programs that interface with the randomizer, such as this [Item Tracker](https://github.com/radicoon/tunic-rando-tracker) by Radicoon. +- Custom seeds can be set on the title screen before starting a New Game. +- The Randomizer will notify you on the title screen if a new update is available. +- A [Map Tracker Package](https://github.com/SapphireSapphic/TunicTracker) exists for this game with a full map tracker, created by SapphireSapphic and ScoutJD. It can also be used with Poptracker, thanks to Br00ty. ## Settings With the exception of the Logic settings (which are determined in your Archipelago yaml settings), all options can be freely toggled or changed at any point while playing. -### Archipelago -- Death Link - - Want a more chaotice experience? When a player with Death Link enabled dies, everyone else with the setting on *also* dies. Be careful! -- Auto-open !collect-ed Checks - - With this enabled, chests and other item pickup locations that you haven't already found but were retrieved by another player via !collect will appear as open/already picked up. - - This will also reflect the item counts on the inventory screen and on the end summary screen. - - This setting can be toggled on/off at will, and the world will update/revert accordingly on the next scene transition. ### Logic -- Game Mode - - Choose between a classic randomizer experience or Hexagon Quest, a separate game mode inspired by Triforce Hunt in Zelda randomizers. - - Classic Randomizer: Find the three Hexagon Keys and defeat The Heir or Share Your Wisdom to win. - - Hexagon Quest: 30 Gold Hexagons are shuffled into the item pool. Find 20 of them and visit The Heir to win. +- Hexagon Quest + - Gold Hexagons are shuffled into the item pool. Find the required amount of them and visit The Heir to win. The required amount can be customized in the Advanced Options menu. - Keys Behind Bosses - - Choose if the three Hexagon Keys are randomly shuffled or placed behind their respective bossfight. - - In Hexagon Quest, this option guarantees a Gold Hexagon is placed behind each of the three major bosses. + - Places the three Hexagon Keys behind their respective bossfight. In Hexagon Quest, this option guarantees a Gold Hexagon is placed behind each of the three major bosses. - Sword Progression - - Replaces the stick and swords in the item pool with four Sword Upgrades that progressively level up as you find them. + - Replaces the stick and swords in the item pool with four Sword Upgrades that progressively level up as you find them, with the final two upgrades being custom swords that offer extended reach compared to the standard Sword and a free +1 to your Attack level when found. - Level 1: Stick -> Level 2: Sword -> Level 3: Librarian's Sword -> Level 4: Heir's Sword - - The Level 3 and 4 Swords are custom swords that offer extended reach compared to the standard Sword and a free +1 to your Attack level when found. - Start With Sword - The player will spawn with a Sword in the inventory on New Game. Does not count towards Sword Progression. - Shuffle Abilities - - Locks the ability to use Prayer, most Holy Cross codes*, and the Ice Rod combo technique until the respective manual page for each ability is found. - - Prayer is unlocked by Page 24, Holy Cross is unlocked by Page 43, and Ice Rod is unlocked by Page 53. + - Locks the ability to use Prayer, most Holy Cross codes*, and the Icebolt combo technique until the respective manual page for each ability is found. + - Prayer is unlocked by Page 24, Holy Cross is unlocked by Page 43, and the Icebolt technique is unlocked by Page 53. If playing Hexagon Quest, abilities are unlocked when reaching 25%, 50%, and 75% of the required amount of Gold Hexagons. - *This option only locks Holy Cross codes that block access to checks in the randomizer. The free bomb codes and other player-facing codes like Big Head Mode, Sunglasses, Fairy Seeker, etc. are still usable from the start. - - This option does not currently apply when playing Hexagon Quest, as all pages are given from the start in that mode. +- Entrance Randomizer + - Shuffles all the connections between doors, teleporters, portals, and more. Where will the fox end up? +- Entrance Randomizer: Fewer Shop Entrances + - Reduces the amount of possible shops that can be found, and places a guaranteed shop entrance at the Overworld Windmill entrance. +- Hero Laurels Location + - Place the Laurels at a predetermined location, currently the options are the 6 coin reward, 10 coin reward, or 10 fairy reward. +- Lanternless Logic + - Removes the Lantern as a requirement for dark areas, allowing it (or items that grant access to it) to be shuffled into places like Dark Tomb, etc. +- Maskless Logic + - Removes the Mask as a requirement for the miasma in Quarry, allowing it to get shuffled into lower Quarry or beyond. +- Mystery Seed + - Randomly chooses logic options for you on New Game. Good luck! +### Archipelago-Specific Settings +- Death Link + - Want a more chaotic experience? When a player with Death Link enabled dies, everyone else with the setting on *also* dies. Be careful! +- Auto-open !collect-ed Checks + - Makes checks that you haven't found but were completed by another player (via !collect, slot co-op, etc) appear as already been opened/picked up. Also reflects the item counts on the inventory screen/ending summary. Can be toggled on/off freely, and will revert the appearance of checks to their previous state on the next scene transition. +- Send hints to server + - This setting will record certain hints from the ghost foxes and any shop items you inspect in the Archipelago Text Client. ### Hints - Path of the Hero - Places a major hint at specific locations around the world, including the Mailbox, the Hero Graves, and the statue in the Sealed Temple. These hint towards major progression items, such as Magic Items, Laurels, Hexagons, and more. - Ghost Foxes - Spawns 15 Ghost Fox NPCs around the world that give minor hints. These hints include the locations of useful non-progression items, items in hard-to-reach locations, and barren locations. - - There are over 50 unique Ghost Fox spawns, all with their own custom Trunic dialogue! + - There are over 50 unique Ghost Fox spawns, so be on the lookout! - Freestanding Items and Chests Match Contents - All freestanding items (Item Pickups, Page Pickups, Shop Items, Hero's Grave Relics, etc.) will have their model swapped to appear as the item they are randomized as. - Chest textures will be swapped to indicate what item is in them. Currently, the items with different chest textures are Fairies, Golden Trophies, the three Hexagons, and the Hero's Laurels. +- Display Hints in Trunic + - For the experienced Ruin Seekers out there, this option removes most English words from custom dialogue, hints, or other text produced by the randomizer, leaving it up to your own knowledge to figure out what is where. ### General - Easier Heir Fight - Attacks deal additional damage to The Heir based on the total number of checks found. - Cheaper Shop Items - - Reduces the cost of the four randomized Shop items from 1000 to 300. + - Reduces the cost of the four randomized Shop items to 300 bits each. - Bonus Upgrades - Makes the Golden Trophy items give free Level Ups for certain stats, allowing you to get up to +8 in every stat in a single playthrough when combined with the regular stat upgrades and the +2 Attack levels from Sword Progression. - - Note: Bonus upgrades will not be retroactively awarded if this setting is turned on after obtaining Golden Trophies with it disabled. The +2 Attack Levels from Sword Progression are always awarded and not affected by this setting. + - Note: Bonus upgrades will not be retroactively awarded if this setting is turned on after obtaining Golden Trophies with it disabled. The +2 Attack Levels from Sword Progression are always awarded and are not affected by this setting. - Disable Chest Interruption - Enemies will not be able to interrupt you while opening chests if this option is turned on. +- Skip Item Popups + - Turns off the item/page popups when receiving items. +- Skip Upgrade Animations + - Skips the animation that plays when upgrading stats. - Fool Traps - - Enables fool traps, which replace low-value money rewards when enabled and apply damage or other negative effects to the player. - - The different options determine how many fools are present, with None/Normal/Double/Onslaught containing 0/15/32/50 fools respectively. + - Enables fool traps, which replace low-value money rewards when enabled and apply damage/other negative effects to the player. Turning the setting up increases the amount of traps in the item pool. + - For Single Player seeds, this option can be changed mid-run by changing the value and reloading the save file. For Archipelago games, this is a yaml option and cannot be changed after the game starts. - ??? - !esirprus a rof no nruT +- More Skulls + - Does exactly what it says on the tin. +- Arachnophobia Mode + - Turns the spiders and another mulit-legged enemy into...something else. ### Enemy Randomization - Enemy Randomizer - - Randomly swaps out enemies with new ones when you load into a scene. See below settings for ways to affect enemy difficulty/generation. - - There is currently no logic for certain edge cases where grappling to an enemy may be required to reach a check (ex. East Forest Slime, Turret in Overworld/Frog's Domain). The Enemy Randomizer can be toggled on or off at any point while playing however, so if you find yourself unable to reach certain checks it is recommended to briefly turn the setting off to get the check and then turn it back on afterwards. - - Enemy Randomization is still a work-in-progress feature and may randomly stop working or cause the game to stutter/lag after a while. If either of these situations occur, restarting the game should temporarily fix the issue. + - Randomly swaps out enemies with new ones when you load into a scene. You may even see some enemies you've never seen before! - Extra Enemies - Enables certain NG+ and nighttime enemy slots to add more enemies into the mix for increased chaos. - Enemy Difficulty - Random: No balancing is performed, and all new enemy spawns are chosen randomly from the full pool of enemies. - - Balanced: Enemies will only be replaced with an enemy of similar difficulty. + - Balanced: Enemies are replaced with an enemy of similar difficulty. - Enemy Generation - Random: Enemies will change every time you leave and come back to an area. - Seeded: Enemy spawns will remain consistent per area. ### Fox Customization 🦊 - Random Fox Colors - - Fox colors will randomize everytime you enter a new scene, rest at a shrine, or load from the menu. - - Pressing '5' on keyboard will randomize fox colors on demand. + - Fox colors will randomize on every load/scene transition. - Keepin' It real - Toggles Sunglasses on/off without needing to use the Holy Cross code. - Fox Color Editor - - Opens an in-depth fox palette editor that allows you to create/save/load a custom color palette texture for the fox. -- Use Custom Texture - - When enabled, will always apply the saved custom texture to the fox. Note: Random Fox Colors should be disabled when using this setting or it may not work properly. - - The custom texture can be found under AppData in the same folder as the Spoiler log and Item Tracker file. - -## Custom Items -### Dath Stone -![image](https://github.com/silent-destroyer/tunic-randomizer/assets/110704408/a5797e96-66c6-4abd-ba94-ca61019a79d8) -- This item combines two unused items in the game into one and allows you to warp back to the last statue you saved at when used. -- What does Dath mean? The trunic characters on the item icon were presumably meant to spell "Dash", but instead they spell out "Dath", so the community has dubbed this item the "Dath Stone". - -### Librarian and Heir Swords -![image](https://github.com/silent-destroyer/tunic-randomizer/assets/110704408/412935ea-b5ab-4d96-b181-678a39025901) ![image](https://github.com/silent-destroyer/tunic-randomizer/assets/110704408/92e6b5d3-1d30-49ff-a344-1d8295e593dd) -- These familiar swords are now usable in the game as part of the Sword Progression mode! -- The Librarian Sword (Level 3) offers additional reach over the base sword, as well as a free +1 to your Attack stat level. -- the Heir Sword (Level 4) offers slightly more range than the Librarian Sword, another free +1 Attack, and also has the added bonus of ignoring collision with metalllic objecs, including enemy shields. - -## Golden Hexagons -![image](https://github.com/silent-destroyer/tunic-randomizer/assets/110704408/361efdaa-1849-44ee-9b2c-504d7f05c46e) -- These appear when playing the Hexagon Quest mode. Finding 20 of them allows you to end the game by visiting The Heir. - -## ??? -![image](https://github.com/silent-destroyer/tunic-randomizer/assets/110704408/ed015b2c-7b93-4d23-8728-b36b5cba36d1) -- ??? + - Allows you to customize the colors of your fox with more options than what the base game offers, and save it as a custom texture for future use. +- Custom Fox Texture + - When enabled, will always apply the saved custom texture to the fox. The custom texture can be found under AppData in the same folder as the Spoiler log and Item Tracker file. +### Race Mode Settings +- Race Mode + - An option to help facilitate ranomizer races more easily. Disables the spoiler log, and enables the options below to be used. +- Disable Icebolt in The Heir Fight + - Disables use of the icebolt technique when fighting The Heir. +- Disable Long-Distance Bell Shots + - Prevents you from ringing the West Bell early, by shoowing it with the Magic Wand from far away. +- Disable Ice Grappling + - Prevents you from pulling yourself towards frozen enemies with the Magic Orb. (The East Forest blob is excluded from this.) +- Disable Ladder Storage + - Prevents the Ladder Storage glitch from being used. ## Credits - Glace and RisingStar111 for helping research how to mod this game, and Jabberrock for creating an initial Archipelago integration. -- Radicoon, kingsamps0n, Landie, JimTheEternal, SapphireSapphic, ScoutJD, Scipio, and many others for playtesting and helping to improve the mod. +- Scipio for helping out with the entrance randomizer and Archipelago implementation. +- Radicoon, Glace, RisingStar111, kingsamps0n, Landie, JimTheEternal, SapphireSapphic, ScoutJD, and many others for playtesting and helping to improve the mod. - Andrew Shouldice, Kevin Regamey, Finji, and everyone else involved in making this wonderful game. diff --git a/Tunic.yaml b/Tunic.yaml index 01a384e..d75be50 100644 --- a/Tunic.yaml +++ b/Tunic.yaml @@ -8,10 +8,10 @@ # name: Ruin Seeker description: tunc -game: Tunic +game: TUNIC requires: version: 0.4.4 -Tunic: +TUNIC: # Play with sword upgrades enabled. sword_progression: 'true' # Start with a sword. diff --git a/TunicArchipelago.csproj b/TunicArchipelago.csproj index bb944bf..9429dd5 100644 --- a/TunicArchipelago.csproj +++ b/TunicArchipelago.csproj @@ -8,7 +8,7 @@ Library Properties TunicArchipelago - TunicArchipelago + TunicRandomizer v4.8 512 true @@ -166,11 +166,13 @@ - + + + diff --git a/src/Archipelago/Archipelago.cs b/src/Archipelago/Archipelago.cs index a20d886..4e69cad 100644 --- a/src/Archipelago/Archipelago.cs +++ b/src/Archipelago/Archipelago.cs @@ -41,7 +41,9 @@ public void ActivateCheck(string LocationId) { } public void UpdateDataStorage(string Key, object Value) { - integration.UpdateDataStorage(Key, Value); + if (SaveFlags.IsArchipelago()) { + integration.UpdateDataStorage(Key, Value); + } } public void Release() { diff --git a/src/Archipelago/ArchipelagoIntegration.cs b/src/Archipelago/ArchipelagoIntegration.cs index c5eb96f..b8d71cc 100644 --- a/src/Archipelago/ArchipelagoIntegration.cs +++ b/src/Archipelago/ArchipelagoIntegration.cs @@ -41,6 +41,10 @@ public class ArchipelagoIntegration { private int ItemIndex = 0; public void Update() { + if ((SceneManager.GetActiveScene().name == "TitleScreen" && TunicArchipelago.Settings.Mode != RandomizerSettings.RandomizerType.ARCHIPELAGO) || SaveFile.GetInt("archipelago") == 0) { + return; + } + if (!connected) { return; } @@ -64,7 +68,6 @@ public void Update() { if (SpeedrunData.gameComplete != 0 && !sentCompletion) { sentCompletion = true; SendCompletion(); - SpeedrunFinishlineDisplayPatches.SetupCompletionStatsDisplay(); } } @@ -81,7 +84,11 @@ public void TryConnect() { return; } if (session == null) { - session = ArchipelagoSessionFactory.CreateSession(TunicArchipelago.Settings.ConnectionSettings.Hostname, TunicArchipelago.Settings.ConnectionSettings.Port); + try { + session = ArchipelagoSessionFactory.CreateSession(TunicArchipelago.Settings.ConnectionSettings.Hostname, int.Parse(TunicArchipelago.Settings.ConnectionSettings.Port)); + } catch (Exception e) { + Logger.LogInfo("Failed to create archipelago session!"); + } } incomingItemHandler = IncomingItemHandler(); outgoingItemHandler = OutgoingItemHandler(); @@ -285,6 +292,11 @@ public void ActivateCheck(string LocationId) { if (Locations.VanillaLocations.Keys.Where(key => Locations.VanillaLocations[key].Location.SceneName == SceneLoaderPatches.SceneName && !Locations.CheckedLocations[key]).ToList().Count == 0) { FairyTargets.CreateLoadZoneTargets(); } + + if (TunicArchipelago.Settings.CreateSpoilerLog && !TunicArchipelago.Settings.RaceMode) { + ItemTracker.PopulateSpoilerLog(); + } + session.Locations.ScoutLocationsAsync(location) .ContinueWith(locationInfoPacket => outgoingItems.Enqueue(locationInfoPacket.Result.Locations[0])); @@ -303,7 +315,7 @@ public void SendCompletion() { } public void Release() { - if (sentCompletion && !sentRelease) { + if (connected && sentCompletion && !sentRelease) { session.Socket.SendPacket(new SayPacket() { Text = "!release" }); sentRelease = true; Logger.LogInfo("Released remaining checks."); @@ -311,7 +323,7 @@ public void Release() { } public void Collect() { - if (sentCompletion && !sentCollect) { + if (connected && sentCompletion && !sentCollect) { session.Socket.SendPacket(new SayPacket() { Text = "!collect" }); sentCollect = true; Logger.LogInfo("Collected remaining items."); diff --git a/src/Data/Check.cs b/src/Data/Check.cs new file mode 100644 index 0000000..71a70aa --- /dev/null +++ b/src/Data/Check.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TunicArchipelago { + + public struct Location { + public string LocationId; + public string Position; + public List> RequiredItems; + public List> RequiredItemsDoors; + public int SceneId; + public string SceneName; + + public bool reachable(Dictionary inventory) { + List> itemsRequired; + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { + itemsRequired = this.RequiredItemsDoors; + } else { + itemsRequired = this.RequiredItems; + } + + //if there are no requirements, the location is reachable + if (itemsRequired.Count == 0) { + return true; + } + + //if there are requirements, loop through each requirement to see if any are fully met + foreach (Dictionary req in itemsRequired) { + //ensure req and items use same terms + if (SaveFile.GetInt("randomizer sword progression enabled") != 0) { + if (req.ContainsKey("Stick")) { + req["Sword Progression"] = 1; + req.Remove("Stick"); + } + if (req.ContainsKey("Sword")) { + req["Sword Progression"] = 2; + req.Remove("Sword"); + } + } + + //check if this requirement is fully met, otherwise move to the next requirement + int met = 0; + foreach (string item in req.Keys) { + //Logger.LogInfo(item); + if (!inventory.ContainsKey(item)) { + break; + } else if (inventory[item] >= req[item]) { + met += 1; + } + } + if (met == req.Count) { + return true; + } + } + //if no requirements are met, the location isn't reachable + return false; + } + } + public struct Reward { + public int Amount; + public string Name; + public string Type; + } + public class Check { + public Location Location; + public Reward Reward; + + public Check() { } + + public Check(Location location, Reward reward) { + Location = location; + Reward = reward; + } + public Check(Reward reward, Location location) { + Location = location; + Reward = reward; + } + } +} diff --git a/src/Data/ConnectionSettings.cs b/src/Data/ConnectionSettings.cs index 03d8c54..d793dea 100644 --- a/src/Data/ConnectionSettings.cs +++ b/src/Data/ConnectionSettings.cs @@ -18,7 +18,7 @@ public string Hostname { set; } - public int Port { + public string Port { get; set; } @@ -29,9 +29,9 @@ public string Password { } public ConnectionSettings() { - Player = "Player"; + Player = "Ruin Seeker"; Hostname = "localhost"; - Port = 38281; + Port = "38281"; Password = ""; } } diff --git a/src/Data/Hints.cs b/src/Data/Hints.cs index 87b2dba..4aac278 100644 --- a/src/Data/Hints.cs +++ b/src/Data/Hints.cs @@ -11,6 +11,7 @@ using UnityEngine; using Archipelago.MultiClient.Net.Models; using Il2CppSystem; +using UnityEngine.InputSystem; namespace TunicArchipelago { public class Hints { @@ -59,47 +60,6 @@ public HeroGraveHint(string pathHintId, string pathHint, string relicHintId, str public static Dictionary HintMessages = new Dictionary(); - // Used for getting what sphere 1 is if you have ER on - // Gives you items in Overworld or items in adjacent scenes - // will need updating if/when we do a different starting spot - public static List GetERSphereOne() - { - List PortalInventory = new List(); - List CombinedInventory = new List{"Overworld"}; - - // add starting sword and abilities if applicable - if (SaveFile.GetInt("randomizer started with sword") == 1) - { CombinedInventory.Add("Sword"); } - if (SaveFile.GetInt(AbilityShuffle) == 0) - { - CombinedInventory.Add("12"); - CombinedInventory.Add("21"); - } - - // find which portals you can reach from spawn without additional progression - foreach (PortalCombo portalCombo in TunicPortals.RandomizedPortals.Values) - { - if (portalCombo.Portal1.Region == "Overworld") - { PortalInventory.Add(portalCombo.Portal2); } - if (portalCombo.Portal1.Region == "Overworld Ability" && SaveFile.GetInt(AbilityShuffle) == 0) - { PortalInventory.Add(portalCombo.Portal2); } - - if (portalCombo.Portal2.Region == "Overworld") - { PortalInventory.Add(portalCombo.Portal1); } - if (portalCombo.Portal2.Region == "Overworld Ability" && SaveFile.GetInt(AbilityShuffle) == 0) - { PortalInventory.Add(portalCombo.Portal1); } - } - - // add the new portals and any applicable new scenes to the inventory - foreach (Portal portal in PortalInventory) - { - CombinedInventory.Add(portal.SceneDestinationTag); - CombinedInventory.AddRange(portal.Rewards(CombinedInventory)); - } - - return CombinedInventory; - } - public static void PopulateHints() { HintMessages.Clear(); HeroGraveHints.Clear(); @@ -107,116 +67,79 @@ public static void PopulateHints() { string Hint = ""; string Scene = ""; string Prefix = ""; - List Vowels = new List() { 'A', 'E', 'I', 'O', 'U' }; + List Vowels = new List() { 'A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u' }; - int Player = Archipelago.instance.GetPlayerSlot(); - List MailboxItems = new List() { "Stick", "Sword", "Sword Upgrade", "Magic Dagger", "Magic Wand", "Magic Orb", "Lantern", "Gun", "Scavenger Mask", "Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)" }; - Dictionary SphereOnePlayer = new Dictionary(); - Dictionary SphereOneOthers = new Dictionary(); - List ERSphereOneItemsAndAreas = GetERSphereOne(); - foreach (string itemkey in ItemLookup.ItemList.Keys) { - ArchipelagoItem item = ItemLookup.ItemList[itemkey]; - // In ER, we need to check more info, since every item has a required item count - if (SaveFile.GetInt(EntranceRando) == 1) { - if (Archipelago.instance.IsTunicPlayer(item.Player) && MailboxItems.Contains(item.ItemName)) { - var requirements = Locations.VanillaLocations[itemkey].Location.RequiredItemsDoors[0].Keys; - foreach (string req in requirements) { - int checkCount = 0; - if (ERSphereOneItemsAndAreas.Contains(req)) { - checkCount++; - } else { - continue; - } - if (checkCount == requirements.Count) { - SphereOnePlayer.Add(itemkey, item); - } - } - } - else if (item.Player != Archipelago.instance.GetPlayerSlot() && item.Classification == ItemFlags.Advancement) { - var requirements = Locations.VanillaLocations[itemkey].Location.RequiredItemsDoors[0].Keys; - foreach (string req in requirements) { - int checkCount = 0; - if (ERSphereOneItemsAndAreas.Contains(req)) { - checkCount++; - } else { - continue; - } - if (checkCount == requirements.Count) - { SphereOneOthers.Add(itemkey, item); } - } - } - } else { - if (Archipelago.instance.IsTunicPlayer(item.Player) && MailboxItems.Contains(item.ItemName) && Locations.VanillaLocations[itemkey].Location.RequiredItems.Count == 0) { - SphereOnePlayer.Add(itemkey, item); - } - if (item.Player != Archipelago.instance.GetPlayerSlot() && item.Classification == ItemFlags.Advancement && Locations.VanillaLocations[itemkey].Location.RequiredItems.Count == 0) { - SphereOneOthers.Add(itemkey, item); - } - } - } - ArchipelagoItem mailboxitem = null; - string key = ""; - if (SphereOnePlayer.Count > 0) { - key = SphereOnePlayer.Keys.ToList()[random.Next(SphereOnePlayer.Count)]; - mailboxitem = SphereOnePlayer[key]; - } else if (SphereOneOthers.Count > 0) { - key = SphereOneOthers.Keys.ToList()[random.Next(SphereOneOthers.Count)]; - mailboxitem = SphereOneOthers[key]; - } - if (mailboxitem != null) { - Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[key].Location.SceneName].ToUpper(); - Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - SaveFile.SetString("randomizer mailbox hint location", key); - Hint = $"lehjehnd sehz {Prefix} \"{Scene.ToUpper()}\"\nkuhntAnz wuhn uhv mehnE \"<#00FFFF>FIRST STEPS<#ffffff>\" ahn yor jurnE."; - } else { - SaveFile.SetString("randomizer mailbox hint location", "no first steps"); - Hint = $"yor frehndz muhst furst hehlp yoo fInd yor wA...\ngoud luhk, rooin sEkur."; + (bool, bool, bool, bool) SinglePlayerItemsHinted = (false, false, false, false); + + if (IsArchipelago()) { + CreateAPMailboxHint(random); + } else if (IsSinglePlayer()) { + SinglePlayerItemsHinted = CreateSinglePlayerMailboxHint(random); } - HintMessages.Add("Mailbox", Hint); - ArchipelagoHint Hyperdash = Locations.MajorItemLocations["Hero's Laurels"][0]; + Hint = $"lehjehnd sehz <#FF00FF>suhm%i^ ehkstruhordinArE<#FFFFFF> [laurels] "; - if (Hyperdash.Player == Player) { - Scene = Hyperdash.Location == "Your Pocket" ? Hyperdash.Location.ToUpper() : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[Hyperdash.Location]].Location.SceneName].ToUpper(); + if (IsArchipelago()) { + int Player = Archipelago.instance.GetPlayerSlot(); + ArchipelagoHint Hyperdash = Locations.MajorItemLocations["Hero's Laurels"][0]; + if (Hyperdash.Player == Player) { + Scene = Hyperdash.Location == "Your Pocket" ? Hyperdash.Location.ToUpper() : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[Hyperdash.Location]].Location.SceneName].ToUpper(); + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; + Hint += $"\nuhwAts yoo in {Prefix} \"{Scene}...\""; + } else if (Archipelago.instance.IsTunicPlayer((int)Hyperdash.Player)) { + Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[Hyperdash.Location]].Location.SceneName].ToUpper(); + Hint += $"\nuhwAts yoo in \"{Archipelago.instance.GetPlayerName((int)Hyperdash.Player).ToUpper()}'S\"\n\"{Scene}...\""; + } else { + Hint += $" uhwAts yoo aht\n{WordWrapString($"\"{Hyperdash.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}\nin\"{Archipelago.instance.GetPlayerName((int)Hyperdash.Player).ToUpper()}'S WORLD...\""; + } + } else if (IsSinglePlayer()) { + Check LaurelsCheck = ItemRandomizer.FindRandomizedItemByName("Hyperdash"); + Scene = Locations.SimplifiedSceneNames[LaurelsCheck.Location.SceneName]; Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - Hint += $"\nuhwAts yoo in {Prefix} \"{Scene}...\""; - } else if (Archipelago.instance.IsTunicPlayer((int)Hyperdash.Player)) { - Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[Hyperdash.Location]].Location.SceneName].ToUpper(); - Hint += $"\nuhwAts yoo in \"{Archipelago.instance.GetPlayerName((int)Hyperdash.Player).ToUpper()}'S\"\n\"{Scene}...\""; - } else { - Hint += $" uhwAts yoo aht\n\"{WordWrapString($"\"{Hyperdash.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}\"\nin\"{Archipelago.instance.GetPlayerName((int)Hyperdash.Player).ToUpper()}'S WORLD...\""; + Hint += TunicArchipelago.Settings.UseTrunicTranslations ? $"\n<#FFFFFF>uhwAts yoo aht {Prefix} {Translations.Translate(Scene, false)}\"...\"" : $"\nuhwAts yoo in {Prefix} \"{Scene.ToUpper()}...\""; } HintMessages.Add("Temple Statue", Hint); - List<(string, string)> relicHints = CreateHeroRelicHints(); - List HintItems = new List() { "Magic Wand", "Magic Orb", "Magic Dagger" }; + List HintItems = new List() { SinglePlayerItemsHinted.Item1 ? "Lantern" : "Magic Wand", SinglePlayerItemsHinted.Item2 ? "Lantern" : "Magic Orb", "Magic Dagger" }; if (SaveFile.GetInt(AbilityShuffle) == 1 && SaveFile.GetInt(HexagonQuestEnabled) == 0) { - HintItems.Add("Pages 24-25 (Prayer)"); - HintItems.Add("Pages 42-43 (Holy Cross)"); + HintItems.Add(SinglePlayerItemsHinted.Item3 ? "Lantern" : "Pages 24-25 (Prayer)"); + HintItems.Add(SinglePlayerItemsHinted.Item4 ? "Lantern" : "Pages 42-43 (Holy Cross)"); HintItems.Remove("Magic Dagger"); } List HintGraves = new List() { "East Forest Relic", "Fortress Relic", "West Garden Relic" }; while (HintGraves.Count > 0) { string HintItem = HintItems[random.Next(HintItems.Count)]; - ArchipelagoHint ItemHint = Locations.MajorItemLocations[HintItem][0]; string HintGrave = HintGraves[random.Next(HintGraves.Count)]; + string slotLocation = ""; (string, string) RelicHint = relicHints[random.Next(relicHints.Count)]; - - if (ItemHint.Player == Player) { - Scene = ItemHint.Location == "Your Pocket" ? ItemHint.Location.ToUpper() : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[ItemHint.Location]].Location.SceneName].ToUpper(); - if (HintItem == "Pages 24-25 (Prayer)" && Scene == "Fortress Relic") { - continue; + Hint = $"lehjehnd sehz "; + if (IsArchipelago()) { + ArchipelagoHint ItemHint = Locations.MajorItemLocations[HintItem][0]; + int Player = Archipelago.instance.GetPlayerSlot(); + + if (ItemHint.Player == Player) { + Scene = ItemHint.Location == "Your Pocket" ? ItemHint.Location : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[ItemHint.Location]].Location.SceneName]; + if (HintItem == "Pages 24-25 (Prayer)" && Scene == "Fortress Relic") { + continue; + } + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; + Hint += $"{Prefix} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) : $"\"{Scene.ToUpper()}\"")}"; + } else if (Archipelago.instance.IsTunicPlayer((int)ItemHint.Player)) { + Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[ItemHint.Location]].Location.SceneName]; + Hint += $"\"{Archipelago.instance.GetPlayerName((int)ItemHint.Player).ToUpper()}'S\"\n{(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) : $"\"{Scene.ToUpper()}\"")}"; + } else { + Hint += $"\"{Archipelago.instance.GetPlayerName((int)ItemHint.Player).ToUpper()}'S WORLD\" aht\n{WordWrapString($"\"{ItemHint.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}"; } + slotLocation = ItemHint.Location == "Your Pocket" ? $"0, Server" : $"{ItemHint.Player}, {ItemHint.Location}"; + } else if (IsSinglePlayer()) { + ItemData Item = ItemLookup.Items[HintItem]; + Check ItemCheck = ItemRandomizer.FindRandomizedItemByName(Item.ItemNameForInventory); + Scene = Locations.SimplifiedSceneNames[ItemCheck.Location.SceneName]; Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - Hint = $"lehjehnd sehz {Prefix} \"{Scene}\""; - } else if (Archipelago.instance.IsTunicPlayer((int)ItemHint.Player)) { - Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[ItemHint.Location]].Location.SceneName].ToUpper(); - Hint = $"lehjehnd sehz \"{Archipelago.instance.GetPlayerName((int)ItemHint.Player).ToUpper()}'S\"\n\"{Scene}\""; - } else { - Hint = $"lehjehnd sehz \"{Archipelago.instance.GetPlayerName((int)ItemHint.Player).ToUpper()}'S WORLD\" aht\n{WordWrapString($"\"{ItemHint.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}"; + Hint += TunicArchipelago.Settings.UseTrunicTranslations ? $"lehjehnd sehz {Prefix} {Translations.Translate(Scene, false)}" : $"lehjehnd sehz {Prefix} \"{Scene.ToUpper()}\""; + slotLocation = $"{ItemCheck.Location.LocationId} [{ItemCheck.Location.SceneName}]"; } - Hint += $"\niz lOkAtid awn #uh \"<#ffd700>PATH OF THE HERO<#ffffff>...\""; + Hint += $"\niz lOkAtid awn #uh {(TunicArchipelago.Settings.UseTrunicTranslations ? $"<#ffd700>pah% uhv #uh hErO<#ffffff>\"...\"" : $"\"<#ffd700>PATH OF THE HERO<#ffffff>...\"")}"; - string slotLocation = ItemHint.Location == "Your Pocket" ? $"0, Server" : $"{ItemHint.Player}, {ItemHint.Location}"; if (HintGrave == "East Forest Relic") { HeroGraveHints.Add(HintGrave, new HeroGraveHint(slotLocation, Hint, RelicHint.Item1, RelicHint.Item2, "Sword Access", "_Setpieces/RelicPlinth (1)/", true)); } else if (HintGrave == "Fortress Relic") { @@ -241,22 +164,33 @@ public static void PopulateHints() { for (int i = 0; i < 3; i++) { string Hexagon = Hexagons[random.Next(Hexagons.Count)]; string HexagonHintArea = HexagonHintGraves[random.Next(HexagonHintGraves.Count)]; + string slotLocation = ""; (string, string) RelicHint = relicHints[random.Next(relicHints.Count)]; - ArchipelagoHint HexHint = Hexagon == "Gold Questagon" ? Locations.MajorItemLocations[Hexagon][i] : Locations.MajorItemLocations[Hexagon][0]; - if (HexHint.Player == Player) { - Scene = HexHint.Location == "Your Pocket" ? HexHint.Location.ToUpper() : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[HexHint.Location]].Location.SceneName].ToUpper(); - Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - Hint = $"#A sA {Prefix} \"{Scene.ToUpper()}\" iz \nwAr #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd\"...\""; - } else if (Archipelago.instance.IsTunicPlayer((int)HexHint.Player)) { - Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[HexHint.Location]].Location.SceneName].ToUpper(); + if (IsArchipelago()) { + ArchipelagoHint HexHint = Hexagon == "Gold Questagon" ? Locations.MajorItemLocations[Hexagon][i] : Locations.MajorItemLocations[Hexagon][0]; + int Player = Archipelago.instance.GetPlayerSlot(); + if (HexHint.Player == Player) { + Scene = HexHint.Location == "Your Pocket" ? HexHint.Location : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[HexHint.Location]].Location.SceneName]; + Prefix = Vowels.Contains(Scene.ToUpper()[0]) ? "#E" : "#uh"; + Hint = $"#A sA {Prefix} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) : $"\"{Scene.ToUpper()}\"")} iz \nwAr #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd\"...\""; + } else if (Archipelago.instance.IsTunicPlayer((int)HexHint.Player)) { + Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[HexHint.Location]].Location.SceneName]; + Prefix = Vowels.Contains(Scene.ToUpper()[0]) ? "#E" : "#uh"; + Hint = $"#A sA \"{Archipelago.instance.GetPlayerName((int)HexHint.Player).ToUpper()}'S\"\n{(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) : $"\"{Scene.ToUpper()}\"")}\niz wAr #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd\"...\""; + } else { + Hint = $"#A sA #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd aht\n{WordWrapString($"\"{HexHint.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}\nin \"{Archipelago.instance.GetPlayerName((int)HexHint.Player).ToUpper()}'S WORLD...\""; + } + slotLocation = HexHint.Location == "Your Pocket" ? $"0, Server" : $"{HexHint.Player}, {HexHint.Location}"; + } else if (IsSinglePlayer()) { + ItemData Hex = ItemLookup.Items[Hexagon]; + Check HexCheck = ItemRandomizer.FindRandomizedItemByName(Hex.ItemNameForInventory); + Scene = Locations.SimplifiedSceneNames[HexCheck.Location.SceneName]; Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - Hint = $"#A sA \"{Archipelago.instance.GetPlayerName((int)HexHint.Player).ToUpper()}'S\"\n\"{Scene}\"\niz wAr #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd\"...\""; - } else { - Hint = $"#A sA #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd aht\n{WordWrapString($"\"{HexHint.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}\nin \"{Archipelago.instance.GetPlayerName((int)HexHint.Player).ToUpper()}'S WORLD...\""; + Hint = $"#A sA {Prefix} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) : $"\"{Scene.ToUpper()}\"")} iz \nwAr #uh {HexagonColors[Hexagon]}kwehstuhgawn [hexagram]<#FFFFFF> iz fownd\"...\""; + slotLocation = $"{HexCheck.Location.LocationId} [{HexCheck.Location.SceneName}]"; } - string slotLocation = HexHint.Location == "Your Pocket" ? $"0, Server" : $"{HexHint.Player}, {HexHint.Location}"; if (HexagonHintArea == "Swamp Relic") { HeroGraveHints.Add(HexagonHintArea, new HeroGraveHint(slotLocation, Hint, RelicHint.Item1, RelicHint.Item2, "Swamp Redux 2", "_Setpieces Etc/RelicPlinth/", false)); @@ -280,47 +214,47 @@ public static void PopulateHints() { foreach (PortalCombo Portal in TunicPortals.RandomizedPortals.Values) { if (Portal.Portal1.SceneDestinationTag == "Overworld Redux, Forest Belltower_") - { HintMessages.Add("East Forest Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\" [arrow_right]"); } + { HintMessages.Add("East Forest Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])} [arrow_right]"); } if (Portal.Portal2.SceneDestinationTag == "Overworld Redux, Forest Belltower_") - { HintMessages.Add("East Forest Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\" [arrow_right]"); } + { HintMessages.Add("East Forest Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])} [arrow_right]"); } if (Portal.Portal1.SceneDestinationTag == "Overworld Redux, Archipelagos Redux_lower") - { HintMessages.Add("West Garden Sign", $"[arrow_left] \"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\""); } + { HintMessages.Add("West Garden Sign", $"[arrow_left] {Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])}"); } if (Portal.Portal2.SceneDestinationTag == "Overworld Redux, Archipelagos Redux_lower") - { HintMessages.Add("West Garden Sign", $"[arrow_left] \"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\""); } + { HintMessages.Add("West Garden Sign", $"[arrow_left] {Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])}"); } if (Portal.Portal1.SceneDestinationTag == "Overworld Redux, Fortress Courtyard_") - { HintMessages.Add("Fortress Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\" [arrow_right]"); } + { HintMessages.Add("Fortress Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])} [arrow_right]"); } if (Portal.Portal2.SceneDestinationTag == "Overworld Redux, Fortress Courtyard_") - { HintMessages.Add("Fortress Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\" [arrow_right]"); } + { HintMessages.Add("Fortress Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])} [arrow_right]"); } if (Portal.Portal1.SceneDestinationTag == "Overworld Redux, Darkwoods Tunnel_") - { HintMessages.Add("Quarry Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\" [arrow_up]"); } + { HintMessages.Add("Quarry Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])} [arrow_up]"); } if (Portal.Portal2.SceneDestinationTag == "Overworld Redux, Darkwoods Tunnel_") - { HintMessages.Add("Quarry Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\" [arrow_up]"); } + { HintMessages.Add("Quarry Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])} [arrow_up]"); } if (Portal.Portal1.SceneDestinationTag == "Overworld Redux, Ruins Passage_west") - { HintMessages.Add("Ruined Hall Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\" [arrow_right]"); } + { HintMessages.Add("Ruined Hall Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])} [arrow_right]"); } if (Portal.Portal2.SceneDestinationTag == "Overworld Redux, Ruins Passage_west") - { HintMessages.Add("Ruined Hall Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\" [arrow_right]"); } + { HintMessages.Add("Ruined Hall Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])} [arrow_right]"); } if (Portal.Portal1.SceneDestinationTag == "Overworld Redux, Overworld Interiors_house") - { HintMessages.Add("Town Sign", $"[arrow_left] \"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\""); } + { HintMessages.Add("Town Sign", $"[arrow_left] {Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])}"); } if (Portal.Portal2.SceneDestinationTag == "Overworld Redux, Overworld Interiors_house") - { HintMessages.Add("Town Sign", $"[arrow_left] \"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\""); } + { HintMessages.Add("Town Sign", $"[arrow_left] {Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])}"); } if (Portal.Portal1.SceneDestinationTag == "East Forest Redux, Sword Access_lower") { - HintMessages.Add("East East Forest Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\" [arrow_right]"); + HintMessages.Add("East East Forest Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])} [arrow_right]"); } if (Portal.Portal2.SceneDestinationTag == "East Forest Redux, Sword Access_lower") { - HintMessages.Add("East East Forest Sign", $"\"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\" [arrow_right]"); + HintMessages.Add("East East Forest Sign", $"{Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])} [arrow_right]"); } if (Portal.Portal1.SceneDestinationTag == "East Forest Redux, East Forest Redux Laddercave_lower") { - HintMessages.Add("West East Forest Sign", $"[arrow_left] \"{Locations.SimplifiedSceneNames[Portal.Portal2.Scene]}\""); + HintMessages.Add("West East Forest Sign", $"[arrow_left] {Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal2.Scene])}"); } if (Portal.Portal2.SceneDestinationTag == "East Forest Redux, East Forest Redux Laddercave_lower") { - HintMessages.Add("West East Forest Sign", $"[arrow_left] \"{Locations.SimplifiedSceneNames[Portal.Portal1.Scene]}\""); + HintMessages.Add("West East Forest Sign", $"[arrow_left] {Translations.TranslateDefaultQuotes(Locations.SimplifiedSceneNames[Portal.Portal1.Scene])}"); } } } @@ -332,28 +266,211 @@ public static void PopulateHints() { string Scene = ""; string Prefix = ""; string RelicHint = ""; - int Player = Archipelago.instance.GetPlayerSlot(); foreach (ItemData Relic in Relics) { - ArchipelagoHint RelicItemHint = Locations.MajorItemLocations[Relic.Name][0]; - string itemDisplayText = $"{TextBuilderPatches.ItemNameToAbbreviation[Relic.Name]} {ItemLookup.BonusUpgrades[Relic.ItemNameForInventory].CustomPickupMessage.ToUpper()}"; + string itemDisplayText = $"{TextBuilderPatches.ItemNameToAbbreviation[Relic.Name]} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(ItemLookup.BonusUpgrades[Relic.ItemNameForInventory].CustomPickupMessage, false) : ItemLookup.BonusUpgrades[Relic.ItemNameForInventory].CustomPickupMessage.ToUpper())}"; + + if (IsArchipelago()) { + int Player = Archipelago.instance.GetPlayerSlot(); + ArchipelagoHint RelicItemHint = Locations.MajorItemLocations[Relic.Name][0]; + + if (RelicItemHint.Player == Player) { + Scene = RelicItemHint.Location == "Your Pocket" ? RelicItemHint.Location : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[RelicItemHint.Location]].Location.SceneName]; + + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; + RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd aht {Prefix} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) + "." : $"\"{Scene.ToUpper()}.\"")}"; + } else if (Archipelago.instance.IsTunicPlayer((int)RelicItemHint.Player)) { + Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[RelicItemHint.Location]].Location.SceneName]; + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; + RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd aht {Prefix} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) : $"\"{Scene.ToUpper()}\"")}\nin \"{Archipelago.instance.GetPlayerName((int)RelicItemHint.Player).ToUpper()}'S WORLD.\""; + } else { + RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd in \"{Archipelago.instance.GetPlayerName((int)RelicItemHint.Player).ToUpper()}'S WORLD\"\naht {WordWrapString($"\"{RelicItemHint.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}."; + } + string slotLocation = RelicItemHint.Player == Player && RelicItemHint.Location == "Your Pocket" ? "0, Server" : $"{RelicItemHint.Player}, {RelicItemHint.Location}"; + RelicHints.Add((slotLocation, RelicHint)); + } else if (IsSinglePlayer()) { + Check RelicCheck = ItemRandomizer.FindRandomizedItemByName(Relic.ItemNameForInventory); + Scene = Locations.SimplifiedSceneNames[RelicCheck.Location.SceneName]; + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - if (RelicItemHint.Player == Player) { - Scene = RelicItemHint.Location == "Your Pocket" ? RelicItemHint.Location.ToUpper() : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[RelicItemHint.Location]].Location.SceneName].ToUpper(); + RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd aht {Prefix} {(TunicArchipelago.Settings.UseTrunicTranslations ? Translations.Translate(Scene, false) + "." : $"\"{Scene.ToUpper()}.\"")}"; + RelicHints.Add(($"{RelicCheck.Location.LocationId} [{RelicCheck.Location.SceneName}]", RelicHint)); + } + } - Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd aht {Prefix} \"{Scene}.\""; - } else if (Archipelago.instance.IsTunicPlayer((int)RelicItemHint.Player)) { - Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[RelicItemHint.Location]].Location.SceneName].ToUpper(); - Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd aht {Prefix} \"{Scene}\"\nin \"{Archipelago.instance.GetPlayerName((int)RelicItemHint.Player).ToUpper()}'S WORLD.\""; + return RelicHints; + } + + public static (bool, bool, bool, bool) CreateSinglePlayerMailboxHint(System.Random random) { + string Scene = ""; + string Prefix = ""; + string HintMessage = ""; + List MailboxItems = new List() { "Stick", "Sword", "Sword Progression", "Stundagger", "Techbow", "Wand", "Lantern", "Shotgun", "Mask" }; + if (SaveFile.GetInt("randomizer shuffled abilities") == 1 && SaveFile.GetInt(HexagonQuestEnabled) != 1) { + MailboxItems.Add("12"); + MailboxItems.Add("21"); + } + List mailboxHintables = new List(); + foreach (string Item in MailboxItems) { + mailboxHintables.AddRange(ItemRandomizer.FindAllRandomizedItemsByName(Item)); + } + Shuffle(mailboxHintables, random); + int n = 0; + Check HintItem = null; + while (HintItem == null && n < mailboxHintables.Count) { + if (mailboxHintables[n].Location.reachable(ItemRandomizer.SphereZero)) { + HintItem = mailboxHintables[n]; + } + n++; + } + if (HintItem == null) { + n = 0; + while (HintItem == null && n < mailboxHintables.Count) { + if (mailboxHintables[n].Location.SceneName == "Trinket Well") { + foreach (Check itemData in ItemRandomizer.FindAllRandomizedItemsByName("Trinket Coin")) { + if (itemData.Location.reachable(ItemRandomizer.SphereZero)) { + HintItem = itemData; + } + } + } else if (mailboxHintables[n].Location.SceneName == "Waterfall") { + foreach (Check itemData in ItemRandomizer.FindAllRandomizedItemsByType("Fairy")) { + if (itemData.Location.reachable(ItemRandomizer.SphereZero)) { + HintItem = itemData; + } + } + } else if (mailboxHintables[n].Location.SceneName == "Overworld Interiors" && SaveFile.GetInt("randomizer entrance rando enabled") == 0) { + Check itemData = ItemRandomizer.FindRandomizedItemByName("Key (House)"); + if (itemData.Location.reachable(ItemRandomizer.SphereZero)) { + HintItem = itemData; + } + } else if (mailboxHintables[n].Location.LocationId == "71" || mailboxHintables[n].Location.LocationId == "73") { + foreach (Check itemData in ItemRandomizer.FindAllRandomizedItemsByName("Key")) { + if (itemData.Location.reachable(ItemRandomizer.SphereZero)) { + HintItem = itemData; + } + } + } else if (SaveFile.GetInt("randomizer entrance rando enabled") == 1 && mailboxHintables[n].Location.RequiredItemsDoors.Count == 1 && mailboxHintables[n].Location.RequiredItemsDoors[0].ContainsKey("Mask") + || mailboxHintables[n].Location.RequiredItems.Count == 1 && mailboxHintables[n].Location.RequiredItems[0].ContainsKey("Mask")) { + Check itemData = ItemRandomizer.FindRandomizedItemByName("Mask"); + if (itemData.Location.reachable(ItemRandomizer.SphereZero)) { + HintItem = itemData; + } + } + n++; + } + } + if (HintItem == null) { + HintMessage = "nO lehjehnd forsaw yor uhrIvuhl, rooin sEker.\nyoo hahv uh difikuhlt rOd uhhehd. \"GOOD LUCK\"."; + //TrunicHint = HintMessage; + } else { + Scene = Locations.SimplifiedSceneNames[HintItem.Location.SceneName]; + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; + HintMessage = $"lehjehnd sehz {Prefix} \"{Scene.ToUpper()}\"\nkuhntAnz wuhn uhv mehnE \"<#00FFFF>FIRST STEPS<#ffffff>\" ahn yor jurnE."; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + HintMessage = $"lehjehnd sehz {Prefix} {Translations.Translate(Scene, false)}\nkuhntAnz wuhn uhv mehnE <#00FFFF>furst stehps<#ffffff> ahn yor jurnE."; + } + SaveFile.SetString("randomizer mailbox hint location", $"{HintItem.Location.LocationId} [{HintItem.Location.SceneName}]"); + + } + HintMessages.Add("Mailbox", HintMessage); + return HintItem == null ? (false, false, false, false) : (HintItem.Reward.Name == "Techbow", HintItem.Reward.Name == "Wand", HintItem.Reward.Name == "12", HintItem.Reward.Name == "21"); + } + + public static void CreateAPMailboxHint(System.Random random) { + string Scene = ""; + string Prefix = ""; + string Hint = ""; + int Player = Archipelago.instance.GetPlayerSlot(); + List MailboxItems = new List() { "Stick", "Sword", "Sword Upgrade", "Magic Dagger", "Magic Wand", "Magic Orb", "Lantern", "Gun", "Scavenger Mask", "Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)" }; + Dictionary SphereOnePlayer = new Dictionary(); + Dictionary SphereOneOthersTunic = new Dictionary(); + Dictionary SphereOneOthers = new Dictionary(); + List ERSphereOneItemsAndAreas = ItemRandomizer.GetERSphereOne(); + foreach (string itemkey in ItemLookup.ItemList.Keys) { + ArchipelagoItem item = ItemLookup.ItemList[itemkey]; + // In ER, we need to check more info, since every item has a required item count + if (SaveFile.GetInt(EntranceRando) == 1) { + if (Archipelago.instance.IsTunicPlayer(item.Player) && MailboxItems.Contains(item.ItemName)) { + var requirements = Locations.VanillaLocations[itemkey].Location.RequiredItemsDoors[0].Keys; + foreach (string req in requirements) { + int checkCount = 0; + if (ERSphereOneItemsAndAreas.Contains(req)) { + checkCount++; + } else { + continue; + } + if (checkCount == requirements.Count) { + if (item.Player == Archipelago.instance.GetPlayerSlot()) { + SphereOnePlayer.Add(itemkey, item); + } else { + SphereOneOthersTunic.Add(itemkey, item); + } + } + } + } else if (item.Player != Archipelago.instance.GetPlayerSlot() && item.Classification == ItemFlags.Advancement) { + var requirements = Locations.VanillaLocations[itemkey].Location.RequiredItemsDoors[0].Keys; + foreach (string req in requirements) { + int checkCount = 0; + if (ERSphereOneItemsAndAreas.Contains(req)) { + checkCount++; + } else { + continue; + } + if (checkCount == requirements.Count) { + SphereOneOthers.Add(itemkey, item); + } + } + } } else { - RelicHint = $"lehjehnd sehz #uh {itemDisplayText}\nkahn bE fownd in \"{Archipelago.instance.GetPlayerName((int)RelicItemHint.Player).ToUpper()}'S WORLD\"\naht {WordWrapString($"\"{RelicItemHint.Location.Replace("_", " ").Replace(" ", "\" \"").ToUpper()}\"").Replace("\" \"", " ")}."; + if (Archipelago.instance.IsTunicPlayer(item.Player) && MailboxItems.Contains(item.ItemName) && Locations.VanillaLocations[itemkey].Location.RequiredItems.Count == 0) { + if (item.Player == Archipelago.instance.GetPlayerSlot()) { + SphereOnePlayer.Add(itemkey, item); + } else { + SphereOneOthersTunic.Add(itemkey, item); + } + } + if (item.Player != Archipelago.instance.GetPlayerSlot() && item.Classification == ItemFlags.Advancement && Locations.VanillaLocations[itemkey].Location.RequiredItems.Count == 0) { + SphereOneOthers.Add(itemkey, item); + } + } + } + ArchipelagoItem mailboxitem = null; + string key = ""; + if (SphereOnePlayer.Count > 0) { + key = SphereOnePlayer.Keys.ToList()[random.Next(SphereOnePlayer.Count)]; + mailboxitem = SphereOnePlayer[key]; + } else if (SphereOneOthersTunic.Count > 0) { + key = SphereOneOthersTunic.Keys.ToList()[random.Next(SphereOneOthersTunic.Count)]; + mailboxitem = SphereOneOthersTunic[key]; + } else if (SphereOneOthers.Count > 0) { + key = SphereOneOthers.Keys.ToList()[random.Next(SphereOneOthers.Count)]; + mailboxitem = SphereOneOthers[key]; + } + if (mailboxitem != null) { + Scene = Locations.SimplifiedSceneNames[Locations.VanillaLocations[key].Location.SceneName]; + Prefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; + SaveFile.SetString("randomizer mailbox hint location", key); + Hint = $"lehjehnd sehz {Prefix} \"{Scene.ToUpper()}\"\nkuhntAnz wuhn uhv mehnE \"<#00FFFF>FIRST STEPS<#ffffff>\" ahn yor jurnE."; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"lehjehnd sehz {Prefix} {Translations.Translate(Scene, false)}\nkuhntAnz wuhn uhv mehnE <#00FFFF>furst stehps<#ffffff> ahn yor jurnE."; } - string slotLocation = RelicItemHint.Player == Player && RelicItemHint.Location == "Your Pocket" ? "0, Server" : $"{RelicItemHint.Player}, {RelicItemHint.Location}"; - RelicHints.Add((slotLocation, RelicHint)); + } else { + SaveFile.SetString("randomizer mailbox hint location", "no first steps"); + Hint = $"yor frehndz muhst furst hehlp yoo fInd yor wA...\ngoud luhk, rooin sEkur."; } + HintMessages.Add("Mailbox", Hint); + } - return RelicHints; + private static void Shuffle(List list, System.Random random) { + int n = list.Count; + int r; + while (n > 1) { + n--; + r = random.Next(n + 1); + + Check holder = list[r]; + list[r] = list[n]; + list[n] = holder; + } } public static string WordWrapString(string Hint) { diff --git a/src/Data/ItemListJson.cs b/src/Data/ItemListJson.cs index 5739ca1..0bae532 100644 --- a/src/Data/ItemListJson.cs +++ b/src/Data/ItemListJson.cs @@ -7,11 +7,10 @@ public class ItemListJson ""LocationId"": ""999"", ""Position"": ""(0.0, 0.0, 0.0)"", ""RequiredItems"": [ - {""Hyperdash"": 1} + {""Hyperdash"": 1, ""Sword"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Cathedral Arena, Swamp Redux_"": 1, ""Sword"": 1}, - {""Cathedral Arena, Shop_"": 1, ""Sword"": 1} + {""Cathedral Gauntlet"": 1, ""Sword"": 1} ], ""SceneId"": 61, ""SceneName"": ""Cathedral Arena"" @@ -28,7 +27,7 @@ public class ItemListJson ""Position"": ""(-135.2, 34.1, -43.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Furnace, Overworld Redux_gyro_upper_north"": 1} + {""Furnace Fuse"": 1} ], ""SceneId"": 57, ""SceneName"": ""Furnace"" @@ -45,7 +44,7 @@ public class ItemListJson ""Position"": ""(50.8, 22.3, -11.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main, Frog Stairs_Entrance"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -64,7 +63,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -83,7 +82,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""CubeRoom"": 1, ""21"": 1} + {""Cube Cave"": 1, ""21"": 1} ], ""SceneId"": 66, ""SceneName"": ""CubeRoom"" @@ -100,7 +99,7 @@ public class ItemListJson ""Position"": ""(8.8, 0.0, 9.9)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Sword Cave"": 1} + {""Stick House"": 1} ], ""SceneId"": 5, ""SceneName"": ""Sword Cave"" @@ -117,7 +116,7 @@ public class ItemListJson ""Position"": ""(-19.0, 28.0, -89.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -134,7 +133,7 @@ public class ItemListJson ""Position"": ""(-51.0, 28.0, -87.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -151,7 +150,7 @@ public class ItemListJson ""Position"": ""(-68.0, 40.0, -29.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -170,7 +169,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -187,7 +186,7 @@ public class ItemListJson ""Position"": ""(-31.5, 40.3, -39.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -204,7 +203,7 @@ public class ItemListJson ""Position"": ""(25.0, 36.0, -110.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -221,7 +220,7 @@ public class ItemListJson ""Position"": ""(26.0, 28.0, -116.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -240,7 +239,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""EastFiligreeCache"": 1} + {""Southeast Cross Room"": 1} ], ""SceneId"": 82, ""SceneName"": ""EastFiligreeCache"" @@ -259,7 +258,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""EastFiligreeCache"": 1} + {""Southeast Cross Room"": 1} ], ""SceneId"": 82, ""SceneName"": ""EastFiligreeCache"" @@ -278,7 +277,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""EastFiligreeCache"": 1} + {""Southeast Cross Room"": 1} ], ""SceneId"": 82, ""SceneName"": ""EastFiligreeCache"" @@ -295,7 +294,7 @@ public class ItemListJson ""Position"": ""(79.3, 2.5, -173.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -314,7 +313,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Cave"": 1, ""21"": 1} + {""Caustic Light Cave"": 1, ""21"": 1} ], ""SceneId"": 50, ""SceneName"": ""Overworld Cave"" @@ -331,7 +330,7 @@ public class ItemListJson ""Position"": ""(176.0, 16.0, 39.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Ruins Passage"": 1} + {""Ruined Passage"": 1} ], ""SceneId"": 8, ""SceneName"": ""Ruins Passage"" @@ -350,7 +349,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Ruins Passage"": 1, ""21"": 1} + {""Ruined Passage"": 1, ""21"": 1} ], ""SceneId"": 8, ""SceneName"": ""Ruins Passage"" @@ -367,7 +366,7 @@ public class ItemListJson ""Position"": ""(96.5, 28.0, -137.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -386,7 +385,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -403,7 +402,7 @@ public class ItemListJson ""Position"": ""(54.0, 48.8, -90.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -454,7 +453,7 @@ public class ItemListJson ""Position"": ""(91.0, 8.0, 67.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -471,7 +470,7 @@ public class ItemListJson ""Position"": ""(85.0, 4.0, 6.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -488,7 +487,7 @@ public class ItemListJson ""Position"": ""(109.7, 0.0, 10.1)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -505,7 +504,7 @@ public class ItemListJson ""Position"": ""(124.0, 1.4, -16.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -522,7 +521,7 @@ public class ItemListJson ""Position"": ""(92.0, 17.0, 70.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux, East Forest Redux Laddercave_upper"": 1} + {""East Forest Dance Fox Spot"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -541,7 +540,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""East Forest Redux, East Forest Redux Laddercave_upper"": 1, ""21"": 1} + {""East Forest Dance Fox Spot"": 1, ""21"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -558,7 +557,7 @@ public class ItemListJson ""Position"": ""(-48.0, 0.0, -172.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Sword Access"": 1} + {""Forest Grave Path Main"": 1} ], ""SceneId"": 12, ""SceneName"": ""Sword Access"" @@ -575,7 +574,7 @@ public class ItemListJson ""Position"": ""(-25.0, 8.0, -172.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Sword Access"": 1} + {""Forest Grave Path Main"": 1} ], ""SceneId"": 12, ""SceneName"": ""Sword Access"" @@ -592,7 +591,7 @@ public class ItemListJson ""Position"": ""(28.2, 6.1, -190.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Sword Access, RelicVoid_teleporter_relic plinth"": 1} + {""Forest Grave Path by Grave"": 1} ], ""SceneId"": 12, ""SceneName"": ""Sword Access"" @@ -611,7 +610,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Sword Access, RelicVoid_teleporter_relic plinth"": 1, ""21"": 1} + {""Forest Grave Path by Grave"": 1, ""21"": 1} ], ""SceneId"": 12, ""SceneName"": ""Sword Access"" @@ -628,7 +627,7 @@ public class ItemListJson ""Position"": ""(162.2, 0.1, -8.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux Interior"": 1} + {""Guard House 2"": 1} ], ""SceneId"": 54, ""SceneName"": ""East Forest Redux Interior"" @@ -645,7 +644,7 @@ public class ItemListJson ""Position"": ""(193.9, -32.0, -23.8)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux Interior"": 1} + {""Guard House 2"": 1} ], ""SceneId"": 54, ""SceneName"": ""East Forest Redux Interior"" @@ -662,7 +661,7 @@ public class ItemListJson ""Position"": ""(91.0, -26.0, -58.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -679,7 +678,7 @@ public class ItemListJson ""Position"": ""(88.0, -30.5, -59.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -698,7 +697,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1, ""21"": 1} + {""East Forest"": 1, ""21"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -717,7 +716,7 @@ public class ItemListJson {""Wand"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""East Forest Redux"": 1} + {""Wand"": 1, ""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -736,7 +735,7 @@ public class ItemListJson {""Wand"": 1, ""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""Hyperdash"": 1, ""East Forest Redux"": 1} + {""Wand"": 1, ""Hyperdash"": 1, ""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -755,7 +754,7 @@ public class ItemListJson {""Wand"": 1, ""Stundagger"": 1, ""Techbow"": 1, ""26"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""Stundagger"": 1, ""Techbow"": 1, ""26"": 1, ""East Forest Redux"": 1} + {""Wand"": 1, ""Stundagger"": 1, ""Techbow"": 1, ""26"": 1, ""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -772,7 +771,7 @@ public class ItemListJson ""Position"": ""(-46.0, 8.0, -171.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Sword Access, East Forest Redux_upper"": 1} + {""Forest Grave Path Upper"": 1} ], ""SceneId"": 12, ""SceneName"": ""Sword Access"" @@ -789,7 +788,7 @@ public class ItemListJson ""Position"": ""(154.0, 8.0, 4.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -806,7 +805,7 @@ public class ItemListJson ""Position"": ""(173.0, 8.0, -1.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux"": 1} + {""East Forest"": 1} ], ""SceneId"": 53, ""SceneName"": ""East Forest Redux"" @@ -823,7 +822,7 @@ public class ItemListJson ""Position"": ""(166.5, 16.0, 62.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux Laddercave"": 1} + {""Guard House 1 East"": 1} ], ""SceneId"": 55, ""SceneName"": ""East Forest Redux Laddercave"" @@ -840,7 +839,7 @@ public class ItemListJson ""Position"": ""(147.0, 16.0, 77.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""East Forest Redux Laddercave"": 1} + {""Guard House 1 East"": 1} ], ""SceneId"": 55, ""SceneName"": ""East Forest Redux Laddercave"" @@ -891,7 +890,7 @@ public class ItemListJson ""Position"": ""(-13.0, -4.0, -143.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Fortress Courtyard, Forest Belltower_"": 1} + {""Fortress Exterior from East Forest"": 1} ], ""SceneId"": 15, ""SceneName"": ""Fortress Courtyard"" @@ -910,7 +909,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -927,7 +926,7 @@ public class ItemListJson ""Position"": ""(86.0, 46.0, -51.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""PatrolCave"": 1} + {""Patrol Cave"": 1} ], ""SceneId"": 67, ""SceneName"": ""PatrolCave"" @@ -946,7 +945,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""PatrolCave"": 1, ""21"": 1} + {""Patrol Cave"": 1, ""21"": 1} ], ""SceneId"": 67, ""SceneName"": ""PatrolCave"" @@ -963,7 +962,7 @@ public class ItemListJson ""Position"": ""(67.0, 66.0, 22.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -980,7 +979,7 @@ public class ItemListJson ""Position"": ""(-95.0, 73.0, 46.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -999,7 +998,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1016,7 +1015,7 @@ public class ItemListJson ""Position"": ""(-111.7, 66.0, 38.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1033,7 +1032,7 @@ public class ItemListJson ""Position"": ""(-142.0, 40.0, 29.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1050,7 +1049,7 @@ public class ItemListJson ""Position"": ""(-73.0, 40.0, -4.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1069,7 +1068,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Waterfall"": 1, ""21"": 1} + {""Secret Gathering Place"": 1, ""21"": 1} ], ""SceneId"": 49, ""SceneName"": ""Waterfall"" @@ -1086,7 +1085,7 @@ public class ItemListJson ""Position"": ""(-19.3, 43.0, 21.7)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1105,7 +1104,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1122,7 +1121,7 @@ public class ItemListJson ""Position"": ""(-70.0, 30.5, -70.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1139,7 +1138,7 @@ public class ItemListJson ""Position"": ""(27.9, 8.0, -34.7)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Ruined Shop"": 1} + {""Ruined Shop"": 1} ], ""SceneId"": 6, ""SceneName"": ""Ruined Shop"" @@ -1156,7 +1155,7 @@ public class ItemListJson ""Position"": ""(25.1, 8.0, -25.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Ruined Shop"": 1} + {""Ruined Shop"": 1} ], ""SceneId"": 6, ""SceneName"": ""Ruined Shop"" @@ -1173,7 +1172,7 @@ public class ItemListJson ""Position"": ""(22.4, 8.0, -24.7)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Ruined Shop"": 1} + {""Ruined Shop"": 1} ], ""SceneId"": 6, ""SceneName"": ""Ruined Shop"" @@ -1192,7 +1191,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1209,7 +1208,7 @@ public class ItemListJson ""Position"": ""(-118.0, 28.0, -46.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1228,7 +1227,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Town_FiligreeRoom"": 1} + {""Fountain Cross Room"": 1} ], ""SceneId"": 30, ""SceneName"": ""Town_FiligreeRoom"" @@ -1247,7 +1246,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Overworld Redux"": 1} + {""Hyperdash"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1266,7 +1265,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1283,7 +1282,7 @@ public class ItemListJson ""Position"": ""(33.0, 8.0, 27.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Changing Room"": 1} + {""Changing Room"": 1} ], ""SceneId"": 70, ""SceneName"": ""Changing Room"" @@ -1300,7 +1299,7 @@ public class ItemListJson ""Position"": ""(-119.5, 16.0, -111.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1320,8 +1319,8 @@ public class ItemListJson {""Wand"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Overworld Redux"": 1}, - {""Wand"": 1, ""Overworld Redux"": 1} + {""Hyperdash"": 1, ""Overworld"": 1}, + {""Wand"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1338,7 +1337,7 @@ public class ItemListJson ""Position"": ""(-65.0, 12.3, -138.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1355,7 +1354,7 @@ public class ItemListJson ""Position"": ""(-83.0, 12.0, -174.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1372,7 +1371,7 @@ public class ItemListJson ""Position"": ""(-60.0, 9.0, -119.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1389,7 +1388,7 @@ public class ItemListJson ""Position"": ""(-33.3, 0.3, -169.8)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1408,7 +1407,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1427,7 +1426,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1, ""21"": 1} + {""Overworld"": 1, ""21"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1444,7 +1443,7 @@ public class ItemListJson ""Position"": ""(-83.2, 4.0, -177.1)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1461,7 +1460,7 @@ public class ItemListJson ""Position"": ""(-202.0, 3.0, 74.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Town Basement"": 1} + {""Hourglass Cave"": 1} ], ""SceneId"": 7, ""SceneName"": ""Town Basement"" @@ -1480,7 +1479,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Town Basement"": 1, ""21"": 1} + {""Hourglass Cave"": 1, ""21"": 1} ], ""SceneId"": 7, ""SceneName"": ""Town Basement"" @@ -1497,7 +1496,7 @@ public class ItemListJson ""Position"": ""(-130.0, 12.0, -119.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1514,7 +1513,7 @@ public class ItemListJson ""Position"": ""(65.0, 22.0, -138.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1551,8 +1550,8 @@ public class ItemListJson {""Wand"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Overworld Redux"": 1}, - {""Wand"": 1, ""Overworld Redux"": 1} + {""Hyperdash"": 1, ""Overworld"": 1}, + {""Wand"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1569,7 +1568,7 @@ public class ItemListJson ""Position"": ""(-3.0, 1.5, -152.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1588,7 +1587,7 @@ public class ItemListJson {""12"": 1} ], ""RequiredItemsDoors"": [ - {""Transit, Overworld Redux_teleporter_starting island"": 1} + {""Far Shore to Spawn"": 1} ], ""SceneId"": 39, ""SceneName"": ""Transit"" @@ -1607,7 +1606,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Transit"": 1, ""Hyperdash"": 1} + {""Far Shore"": 1, ""Hyperdash"": 1} ], ""SceneId"": 39, ""SceneName"": ""Transit"" @@ -1624,7 +1623,7 @@ public class ItemListJson ""Position"": ""(-56.0, 24.0, -95.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1641,7 +1640,7 @@ public class ItemListJson ""Position"": ""(1.0, 0.0, 16.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Maze Room"": 1} + {""Maze Cave"": 1} ], ""SceneId"": 68, ""SceneName"": ""Maze Room"" @@ -1660,7 +1659,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Maze Room"": 1, ""21"": 1} + {""Maze Cave"": 1, ""21"": 1} ], ""SceneId"": 68, ""SceneName"": ""Maze Room"" @@ -1677,7 +1676,7 @@ public class ItemListJson ""Position"": ""(15.5, 1.0, -147.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1696,7 +1695,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Overworld Redux"": 1} + {""Hyperdash"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -1713,7 +1712,7 @@ public class ItemListJson ""Position"": ""(51.5, 1.0, 78.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux, Overworld Redux_lower"": 1} + {""Ruined Atoll Lower Entry Area"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1730,7 +1729,7 @@ public class ItemListJson ""Position"": ""(-7.0, 16.9, -72.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1747,7 +1746,7 @@ public class ItemListJson ""Position"": ""(38.0, 16.0, -72.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1764,7 +1763,7 @@ public class ItemListJson ""Position"": ""(-7.3, 2.2, -110.7)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1781,7 +1780,7 @@ public class ItemListJson ""Position"": ""(71.8, 13.0, -44.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1798,7 +1797,7 @@ public class ItemListJson ""Position"": ""(5.8, 0.3, 33.1)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1815,7 +1814,7 @@ public class ItemListJson ""Position"": ""(0.0, 4.0, 33.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1832,7 +1831,7 @@ public class ItemListJson ""Position"": ""(-77.0, 3.5, 40.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1849,7 +1848,7 @@ public class ItemListJson ""Position"": ""(-55.5, 2.0, 17.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1868,7 +1867,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Atoll Redux"": 1} + {""Hyperdash"": 1, ""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1902,7 +1901,7 @@ public class ItemListJson ""Position"": ""(-38.0, 2.3, -106.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1919,7 +1918,7 @@ public class ItemListJson ""Position"": ""(78.1, 6.3, 84.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1939,8 +1938,8 @@ public class ItemListJson {""Key"": 2} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Atoll Redux"": 1}, - {""Key"": 2, ""Atoll Redux"": 1} + {""Hyperdash"": 1, ""Ruined Atoll"": 1}, + {""Key"": 2, ""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1960,8 +1959,8 @@ public class ItemListJson {""Key"": 2} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Atoll Redux"": 1}, - {""Key"": 2, ""Atoll Redux"": 1} + {""Hyperdash"": 1, ""Ruined Atoll"": 1}, + {""Key"": 2, ""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1978,7 +1977,7 @@ public class ItemListJson ""Position"": ""(73.1, 4.0, 56.6)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -1995,7 +1994,7 @@ public class ItemListJson ""Position"": ""(-20.3, 28.0, -9.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2015,8 +2014,8 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""frog cave main"": 1}, - {""Hyperdash"": 1, ""frog cave main"": 1} + {""Wand"": 1, ""Frog's Domain"": 1}, + {""Hyperdash"": 1, ""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2033,7 +2032,7 @@ public class ItemListJson ""Position"": ""(60.5, 36.0, 4.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2050,7 +2049,7 @@ public class ItemListJson ""Position"": ""(69.0, 36.0, -3.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2067,7 +2066,7 @@ public class ItemListJson ""Position"": ""(-114.5, 28.0, 10.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2087,8 +2086,8 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""frog cave main"": 1}, - {""Hyperdash"": 1, ""frog cave main"": 1} + {""Wand"": 1, ""Frog's Domain"": 1}, + {""Hyperdash"": 1, ""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2105,7 +2104,7 @@ public class ItemListJson ""Position"": ""(-75.0, 9.8, -25.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2122,7 +2121,7 @@ public class ItemListJson ""Position"": ""(-8.5, 8.0, -43.2)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2139,7 +2138,7 @@ public class ItemListJson ""Position"": ""(13.6, -2.0, -77.1)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""frog cave main"": 1} + {""Frog's Domain"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2159,9 +2158,8 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""frog cave main, Frog Stairs_Exit"": 1}, - {""Hyperdash"": 1, ""frog cave main, Frog Stairs_Exit"": 1}, - {""Wand"": 1, ""frog cave main, Frog Stairs_Entrance"": 1} + {""Wand"": 1, ""Frog's Domain Back"": 1}, + {""Hyperdash"": 1, ""Frog's Domain Back"": 1} ], ""SceneId"": 52, ""SceneName"": ""frog cave main"" @@ -2178,7 +2176,7 @@ public class ItemListJson ""Position"": ""(73.0, 14.0, 29.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Atoll Redux"": 1} + {""Ruined Atoll"": 1} ], ""SceneId"": 32, ""SceneName"": ""Atoll Redux"" @@ -2377,7 +2375,7 @@ public class ItemListJson {""Key (House)"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Interiors"": 1} + {""Old House Front"": 1} ], ""SceneId"": 26, ""SceneName"": ""Overworld Interiors"" @@ -2396,7 +2394,7 @@ public class ItemListJson {""Key (House)"": 1, ""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Interiors"": 1, ""21"": 1} + {""Old House Front"": 1, ""21"": 1} ], ""SceneId"": 26, ""SceneName"": ""Overworld Interiors"" @@ -2415,7 +2413,7 @@ public class ItemListJson {""Key (House)"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Interiors"": 1} + {""Old House Front"": 1} ], ""SceneId"": 26, ""SceneName"": ""Overworld Interiors"" @@ -2434,7 +2432,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Interiors, Overworld Redux_under_checkpoint"": 1, ""21"": 1} + {""Old House Back"": 1, ""21"": 1} ], ""SceneId"": 26, ""SceneName"": ""Overworld Interiors"" @@ -2453,7 +2451,7 @@ public class ItemListJson {""Fairy"": 10} ], ""RequiredItemsDoors"": [ - {""Fairy"": 10, ""Waterfall"": 1} + {""Fairy"": 10, ""Secret Gathering Place"": 1} ], ""SceneId"": 49, ""SceneName"": ""Waterfall"" @@ -2472,7 +2470,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Overworld Redux"": 1} + {""Hyperdash"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -2489,7 +2487,7 @@ public class ItemListJson ""Position"": ""(-36.0, 40.0, -13.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -2508,7 +2506,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2527,7 +2525,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2546,7 +2544,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2565,7 +2563,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2584,7 +2582,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2603,7 +2601,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2622,7 +2620,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2641,7 +2639,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2660,7 +2658,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2679,7 +2677,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer, Sewer_Boss_"": 1} + {""Beneath the Well Main, Sewer_Boss_"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2698,7 +2696,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer, Overworld Redux_west_aqueduct"": 1} + {""Beneath the Well Back"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2717,7 +2715,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer, Sewer_Boss_"": 1} + {""Beneath the Well Back"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2734,7 +2732,7 @@ public class ItemListJson ""Position"": ""(57.0, 9.4, -10.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Sewer_Boss, Crypt Redux_"": 1} + {""Dark Tomb Checkpoint"": 1} ], ""SceneId"": 51, ""SceneName"": ""Sewer_Boss"" @@ -2753,7 +2751,7 @@ public class ItemListJson {""Stick"": 1} ], ""RequiredItemsDoors"": [ - {""Sewer"": 1, ""Stick"": 1} + {""Beneath the Well Main"": 1, ""Stick"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2772,7 +2770,7 @@ public class ItemListJson {""12"": 1} ], ""RequiredItemsDoors"": [ - {""Furnace, Overworld Redux_gyro_upper_north"": 1, ""12"": 1} + {""Beneath the Well Back"": 1, ""Furnace Fuse"": 1, ""12"": 1} ], ""SceneId"": 27, ""SceneName"": ""Sewer"" @@ -2789,7 +2787,7 @@ public class ItemListJson ""Position"": ""(-138.2, 28.0, 10.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Overworld Redux"": 1} + {""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -2808,7 +2806,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2827,7 +2825,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2846,7 +2844,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2865,7 +2863,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2884,7 +2882,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2903,7 +2901,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2922,7 +2920,7 @@ public class ItemListJson {""Lantern"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Crypt Redux"": 1} + {""Lantern"": 1, ""Dark Tomb Main"": 1} ], ""SceneId"": 64, ""SceneName"": ""Crypt Redux"" @@ -2942,7 +2940,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Furnace, Crypt Redux_"": 1} + {""Furnace Walking Path"": 1} ], ""SceneId"": 57, ""SceneName"": ""Furnace"" @@ -2962,7 +2960,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux, Archipelagos Redux_lower"": 1} + {""Overworld to West Garden from Furnace"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -2982,7 +2980,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3002,7 +3000,7 @@ public class ItemListJson {""Hyperdash"": 1, ""21"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1, ""21"": 1} + {""West Garden"": 1, ""21"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3022,7 +3020,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3041,7 +3039,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Archipelagos Redux"": 1} + {""Hyperdash"": 1, ""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3060,7 +3058,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Archipelagos Redux"": 1} + {""Hyperdash"": 1, ""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3080,7 +3078,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3100,7 +3098,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3120,7 +3118,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3140,7 +3138,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3159,7 +3157,7 @@ public class ItemListJson {""Hyperdash"": 1, ""21"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Archipelagos Redux"": 1, ""21"": 1} + {""Hyperdash"": 1, ""West Garden"": 1, ""21"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3179,7 +3177,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3199,7 +3197,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3219,7 +3217,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3239,7 +3237,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3259,7 +3257,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""archipelagos_house"": 1} + {""Magic Dagger House"": 1} ], ""SceneId"": 41, ""SceneName"": ""archipelagos_house"" @@ -3278,7 +3276,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Archipelagos Redux, Transit_teleporter_archipelagos_teleporter"": 1} + {""Hyperdash"": 1, ""West Garden Portal"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3297,7 +3295,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Archipelagos Redux"": 1} + {""Hyperdash"": 1, ""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3317,7 +3315,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3337,7 +3335,7 @@ public class ItemListJson {""Hyperdash"": 1, ""21"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1, ""21"": 1} + {""West Garden"": 1, ""21"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3357,7 +3355,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux"": 1} + {""West Garden"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3376,7 +3374,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Overworld Redux"": 1} + {""Hyperdash"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -3396,7 +3394,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Archipelagos Redux, Overworld Redux_upper"": 1} + {""West Garden after Boss"": 1} ], ""SceneId"": 31, ""SceneName"": ""Archipelagos Redux"" @@ -3416,7 +3414,7 @@ public class ItemListJson {""Lantern"": 1, ""Sword"": 1} ], ""RequiredItemsDoors"": [ - {""Overworld Redux, Archipelagos Redux_upper"": 1} + {""Overworld Belltower"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -3437,7 +3435,7 @@ public class ItemListJson {""Lantern"": 1, ""Sword"": 1, ""21"": 1} ], ""RequiredItemsDoors"": [ - {""Temple"": 1, ""21"": 1} + {""Sealed Temple"": 1, ""21"": 1} ], ""SceneId"": 24, ""SceneName"": ""Temple"" @@ -3458,7 +3456,7 @@ public class ItemListJson {""Lantern"": 1, ""Sword"": 1} ], ""RequiredItemsDoors"": [ - {""Temple"": 1} + {""Sealed Temple"": 1} ], ""SceneId"": 24, ""SceneName"": ""Temple"" @@ -3477,7 +3475,7 @@ public class ItemListJson {""Wand"": 1} ], ""RequiredItemsDoors"": [ - {""Wand"": 1, ""Overworld Redux"": 1} + {""Wand"": 1, ""Overworld"": 1} ], ""SceneId"": 25, ""SceneName"": ""Overworld Redux"" @@ -3494,7 +3492,7 @@ public class ItemListJson ""Position"": ""(-18.0, -4.5, -130.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Fortress Courtyard, Overworld Redux_"": 1} + {""Fortress Exterior from Overworld"": 1} ], ""SceneId"": 15, ""SceneName"": ""Fortress Courtyard"" @@ -3511,7 +3509,7 @@ public class ItemListJson ""Position"": ""(6.4, 0.3, -123.8)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Fortress Courtyard, Overworld Redux_"": 1} + {""Fortress Exterior from Overworld"": 1} ], ""SceneId"": 15, ""SceneName"": ""Fortress Courtyard"" @@ -3531,7 +3529,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Courtyard, Shop_"": 1} + {""Fortress Exterior near cave"": 1} ], ""SceneId"": 15, ""SceneName"": ""Fortress Courtyard"" @@ -3551,7 +3549,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Courtyard, Shop_"": 1} + {""Fortress Exterior near cave"": 1} ], ""SceneId"": 15, ""SceneName"": ""Fortress Courtyard"" @@ -3571,7 +3569,7 @@ public class ItemListJson {""Lantern"": 1, ""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Fortress Basement"": 1} + {""Lantern"": 1, ""Beneath the Vault Front"": 1} ], ""SceneId"": 14, ""SceneName"": ""Fortress Basement"" @@ -3593,9 +3591,10 @@ public class ItemListJson {""Lantern"": 1, ""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Stick"": 1, ""Fortress Basement"": 1}, - {""Lantern"": 1, ""Sword"": 1, ""Fortress Basement"": 1}, - {""Lantern"": 1, ""Techbow"": 1, ""Fortress Basement"": 1} + {""Stick"": 1, ""Beneath the Vault Back"": 1}, + {""Sword"": 1, ""Beneath the Vault Back"": 1}, + {""Techbow"": 1, ""Beneath the Vault Back"": 1}, + {""Hyperdash"": 1, ""Beneath the Vault Back"": 1} ], ""SceneId"": 14, ""SceneName"": ""Fortress Basement"" @@ -3615,8 +3614,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Fortress Basement, Fortress Courtyard_"": 1}, - {""Fortress Basement, Fortress Main_"": 1} + {""Beneath the Vault Back"": 1} ], ""SceneId"": 14, ""SceneName"": ""Fortress Basement"" @@ -3636,8 +3634,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Fortress Basement, Fortress Courtyard_"": 1}, - {""Fortress Basement, Fortress Main_"": 1} + {""Beneath the Vault Back"": 1} ], ""SceneId"": 14, ""SceneName"": ""Fortress Basement"" @@ -3657,8 +3654,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Lantern"": 1, ""Fortress Basement, Fortress Courtyard_"": 1}, - {""Fortress Basement, Fortress Main_"": 1} + {""Beneath the Vault Back"": 1} ], ""SceneId"": 14, ""SceneName"": ""Fortress Basement"" @@ -3678,7 +3674,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Main"": 1} + {""Eastern Vault Fortress"": 1} ], ""SceneId"": 13, ""SceneName"": ""Fortress Main"" @@ -3698,7 +3694,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Main"": 1} + {""Eastern Vault Fortress"": 1} ], ""SceneId"": 13, ""SceneName"": ""Fortress Main"" @@ -3718,7 +3714,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Main"": 1} + {""Eastern Vault Fortress"": 1} ], ""SceneId"": 13, ""SceneName"": ""Fortress Main"" @@ -3738,7 +3734,7 @@ public class ItemListJson {""Hyperdash"": 1, ""21"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Main"": 1, ""21"": 1} + {""Eastern Vault Fortress"": 1, ""21"": 1} ], ""SceneId"": 13, ""SceneName"": ""Fortress Main"" @@ -3758,7 +3754,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Main"": 1} + {""Eastern Vault Fortress"": 1} ], ""SceneId"": 13, ""SceneName"": ""Fortress Main"" @@ -3778,7 +3774,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Reliquary, Fortress Courtyard_Upper"": 1} + {""Fortress Grave Path Upper"": 1} ], ""SceneId"": 48, ""SceneName"": ""Fortress Reliquary"" @@ -3798,7 +3794,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Reliquary"": 1} + {""Fortress Grave Path"": 1} ], ""SceneId"": 48, ""SceneName"": ""Fortress Reliquary"" @@ -3818,7 +3814,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress Reliquary"": 1} + {""Fortress Grave Path"": 1} ], ""SceneId"": 48, ""SceneName"": ""Fortress Reliquary"" @@ -3837,7 +3833,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Dusty"": 1} + {""Fortress Leaf Piles"": 1} ], ""SceneId"": 83, ""SceneName"": ""Dusty"" @@ -3857,7 +3853,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Fortress East, Fortress Main_lower"": 1} + {""Fortress East Shortcut Lower"": 1} ], ""SceneId"": 47, ""SceneName"": ""Fortress East"" @@ -3916,7 +3912,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""ShopSpecial"": 1} + {""Special Shop"": 1} ], ""SceneId"": 65, ""SceneName"": ""ShopSpecial"" @@ -3933,7 +3929,7 @@ public class ItemListJson ""Position"": ""(49.0, 41.2, 3.7)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Mountain"": 1} + {""Lower Mountain"": 1} ], ""SceneId"": 9, ""SceneName"": ""Mountain"" @@ -3952,7 +3948,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Mountaintop"": 1} + {""Top of the Mountain"": 1} ], ""SceneId"": 10, ""SceneName"": ""Mountaintop"" @@ -3971,7 +3967,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""21"": 1} + {""Quarry Back"": 1, ""21"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -3987,10 +3983,11 @@ public class ItemListJson ""LocationId"": ""126"", ""Position"": ""(80.0, 16.0, 13.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4006,10 +4003,11 @@ public class ItemListJson ""LocationId"": ""133"", ""Position"": ""(3.4, -8.0, -21.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4024,9 +4022,12 @@ public class ItemListJson ""Location"": { ""LocationId"": ""200"", ""Position"": ""(0.2, 25.0, 175.4)"", - ""RequiredItems"": [], + ""RequiredItems"": [ + {""Sword"": 1}, + {""Techbow"": 1} + ], ""RequiredItemsDoors"": [ - {""Monastery"": 1} + {""Monastery Back"": 1} ], ""SceneId"": 22, ""SceneName"": ""Monastery"" @@ -4043,7 +4044,7 @@ public class ItemListJson ""Position"": ""(-55.0, 22.0, 48.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Quarry Redux, Mountain_"": 1} + {""Quarry Back"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4060,7 +4061,7 @@ public class ItemListJson ""Position"": ""(-56.0, 0.0, 43.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Quarry Redux, Mountain_"": 1} + {""Quarry Back"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4077,7 +4078,7 @@ public class ItemListJson ""Position"": ""(-49.5, -12.0, 21.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Quarry Redux, Mountain_"": 1} + {""Quarry Back"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4093,10 +4094,11 @@ public class ItemListJson ""LocationId"": ""127"", ""Position"": ""(-71.0, -20.0, 40.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4112,10 +4114,11 @@ public class ItemListJson ""LocationId"": ""120"", ""Position"": ""(-149.7, -39.7, 8.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4131,10 +4134,11 @@ public class ItemListJson ""LocationId"": ""265"", ""Position"": ""(-78.9, -40.0, 38.8)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4150,10 +4154,11 @@ public class ItemListJson ""LocationId"": ""121"", ""Position"": ""(-140.0, -48.0, -6.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4169,10 +4174,11 @@ public class ItemListJson ""LocationId"": ""130"", ""Position"": ""(-80.0, -56.0, -57.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4188,10 +4194,11 @@ public class ItemListJson ""LocationId"": ""131"", ""Position"": ""(1.0, -47.6, -78.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4207,10 +4214,11 @@ public class ItemListJson ""LocationId"": ""262"", ""Position"": ""(-88.0, -43.5, -66.5)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4226,10 +4234,11 @@ public class ItemListJson ""LocationId"": ""122"", ""Position"": ""(-131.0, -77.0, -103.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4245,10 +4254,11 @@ public class ItemListJson ""LocationId"": ""129"", ""Position"": ""(-100.0, -69.0, -153.0)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4264,10 +4274,11 @@ public class ItemListJson ""LocationId"": ""132"", ""Position"": ""(-7.4, -80.3, -77.3)"", ""RequiredItems"": [ - {""Mask"": 1} + {""Mask"": 1, ""Sword"": 1}, + {""Mask"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Mask"": 1, ""Quarry Redux"": 1} + {""Mask"": 1, ""Lower Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4283,10 +4294,11 @@ public class ItemListJson ""LocationId"": ""123"", ""Position"": ""(-9.0, -12.0, -64.7)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4302,10 +4314,11 @@ public class ItemListJson ""LocationId"": ""117"", ""Position"": ""(-23.0, 0.0, 7.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4321,10 +4334,11 @@ public class ItemListJson ""LocationId"": ""224"", ""Position"": ""(28.5, 0.0, -3.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4340,10 +4354,11 @@ public class ItemListJson ""LocationId"": ""289"", ""Position"": ""(52.0, 0.0, 2.5)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4359,10 +4374,11 @@ public class ItemListJson ""LocationId"": ""118"", ""Position"": ""(125.4, 16.0, 14.8)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4378,10 +4394,11 @@ public class ItemListJson ""LocationId"": ""268"", ""Position"": ""(-13.0, 8.0, 21.6)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4397,10 +4414,11 @@ public class ItemListJson ""LocationId"": ""125"", ""Position"": ""(38.7, 24.5, 28.6)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4416,10 +4434,11 @@ public class ItemListJson ""LocationId"": ""124"", ""Position"": ""(62.0, 8.0, 15.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4435,10 +4454,11 @@ public class ItemListJson ""LocationId"": ""119"", ""Position"": ""(81.0, 56.0, 25.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4454,10 +4474,11 @@ public class ItemListJson ""LocationId"": ""250"", ""Position"": ""(21.8, 63.0, 51.4)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux, Monastery_front"": 1} + {""Quarry Monastery Entry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4476,7 +4497,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Quarry Redux"": 1} + {""Hyperdash"": 1, ""Quarry"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4492,10 +4513,11 @@ public class ItemListJson ""LocationId"": ""134"", ""Position"": ""(1.0, 40.0, -19.0)"", ""RequiredItems"": [ - {""Sword"": 1} + {""Sword"": 1}, + {""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""Quarry Redux"": 1, ""Sword"": 1} + {""Quarry"": 1, ""Sword"": 1} ], ""SceneId"": 60, ""SceneName"": ""Quarry Redux"" @@ -4511,10 +4533,11 @@ public class ItemListJson ""LocationId"": ""274"", ""Position"": ""(135.0, 138.0, -58.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_1"": 1, ""Sword"": 1} + {""Rooted Ziggurat Upper Front"": 1, ""Sword"": 1} ], ""SceneId"": 43, ""SceneName"": ""ziggurat2020_1"" @@ -4530,10 +4553,11 @@ public class ItemListJson ""LocationId"": ""275"", ""Position"": ""(130.0, 106.0, -129.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_1, ziggurat2020_2_"": 1} + {""Rooted Ziggurat Upper Back"": 1} ], ""SceneId"": 43, ""SceneName"": ""ziggurat2020_1"" @@ -4549,10 +4573,11 @@ public class ItemListJson ""LocationId"": ""229"", ""Position"": ""(149.9, 424.3, -42.6)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_2"": 1} + {""Rooted Ziggurat Middle Top"": 1} ], ""SceneId"": 42, ""SceneName"": ""ziggurat2020_2"" @@ -4568,10 +4593,11 @@ public class ItemListJson ""LocationId"": ""230"", ""Position"": ""(74.0, 4.0, 5.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1} + {""Rooted Ziggurat Lower Front"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4587,10 +4613,11 @@ public class ItemListJson ""LocationId"": ""231"", ""Position"": ""(-7.0, 4.0, -24.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1} + {""Rooted Ziggurat Lower Front"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4606,10 +4633,11 @@ public class ItemListJson ""LocationId"": ""234"", ""Position"": ""(67.6, 4.0, -38.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1} + {""Rooted Ziggurat Lower Front"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4625,10 +4653,11 @@ public class ItemListJson ""LocationId"": ""261"", ""Position"": ""(129.1, 4.1, -27.3)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1} + {""Rooted Ziggurat Lower Front"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4644,10 +4673,11 @@ public class ItemListJson ""LocationId"": ""260"", ""Position"": ""(149.2, 4.0, -53.5)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1} + {""Rooted Ziggurat Lower Front"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4663,10 +4693,11 @@ public class ItemListJson ""LocationId"": ""232"", ""Position"": ""(85.6, 4.0, -51.8)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1} + {""Rooted Ziggurat Lower Front"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4682,10 +4713,11 @@ public class ItemListJson ""LocationId"": ""233"", ""Position"": ""(153.0, 0.0, -61.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3"": 1, ""12"": 1, ""Sword"": 1} + {""Rooted Ziggurat Lower Front"": 1, ""12"": 1, ""Sword"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4701,10 +4733,11 @@ public class ItemListJson ""LocationId"": ""Hexagon Blue"", ""Position"": ""(521.0, -32.9, -146.0)"", ""RequiredItems"": [ - {""Wand"": 1, ""Mask"": 1, ""12"": 1} + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Sword"": 1}, + {""Wand"": 1, ""Mask"": 1, ""12"": 1, ""Techbow"": 1} ], ""RequiredItemsDoors"": [ - {""ziggurat2020_3, ziggurat2020_FTRoom_"": 1, ""Sword"": 1} + {""Rooted Ziggurat Lower Back"": 1, ""Sword"": 1} ], ""SceneId"": 44, ""SceneName"": ""ziggurat2020_3"" @@ -4723,7 +4756,7 @@ public class ItemListJson {""Fairy"": 20} ], ""RequiredItemsDoors"": [ - {""Fairy"": 20, ""Waterfall"": 1} + {""Fairy"": 20, ""Secret Gathering Place"": 1} ], ""SceneId"": 49, ""SceneName"": ""Waterfall"" @@ -4742,7 +4775,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Swamp Redux, Overworld Redux_wall"": 1} + {""Hyperdash"": 1, ""Back of Swamp Laurels Area"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4759,7 +4792,7 @@ public class ItemListJson ""Position"": ""(-47.7, -1.5, -33.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4776,7 +4809,7 @@ public class ItemListJson ""Position"": ""(-41.6, -0.6, 55.4)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4793,7 +4826,7 @@ public class ItemListJson ""Position"": ""(14.5, 0.0, -73.5)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4810,7 +4843,7 @@ public class ItemListJson ""Position"": ""(39.2, -0.1, -85.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4827,7 +4860,7 @@ public class ItemListJson ""Position"": ""(83.7, 0.0, -73.8)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4844,7 +4877,7 @@ public class ItemListJson ""Position"": ""(166.0, 0.0, -82.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4861,7 +4894,7 @@ public class ItemListJson ""Position"": ""(147.0, 5.8, -33.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4878,7 +4911,7 @@ public class ItemListJson ""Position"": ""(100.0, 4.0, -70.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4895,7 +4928,7 @@ public class ItemListJson ""Position"": ""(38.0, 12.8, -29.8)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4914,7 +4947,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""Swamp Redux 2"": 1} + {""Hyperdash"": 1, ""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4929,9 +4962,11 @@ public class ItemListJson ""Location"": { ""LocationId"": ""1005"", ""Position"": ""(59.8, 0.0, -70.6)"", - ""RequiredItems"": [], + ""RequiredItems"": [ + {""Sword"": 1} + ], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4948,7 +4983,7 @@ public class ItemListJson ""Position"": ""(102.0, 6.0, -40.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4965,7 +5000,7 @@ public class ItemListJson ""Position"": ""(47.0, -1.0, 42.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4982,7 +5017,7 @@ public class ItemListJson ""Position"": ""(85.0, 0.0, 85.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -4999,7 +5034,7 @@ public class ItemListJson ""Position"": ""(145.0, 4.0, 23.3)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5016,7 +5051,7 @@ public class ItemListJson ""Position"": ""(160.5, 4.0, -63.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5033,7 +5068,7 @@ public class ItemListJson ""Position"": ""(153.0, 16.0, -55.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5050,7 +5085,7 @@ public class ItemListJson ""Position"": ""(184.8, 15.0, 51.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5067,7 +5102,7 @@ public class ItemListJson ""Position"": ""(160.5, 12.0, 22.0)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5086,7 +5121,7 @@ public class ItemListJson {""Hyperdash"": 1} ], ""RequiredItemsDoors"": [ - {""Swamp Back"": 1} + {""Back of Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5103,7 +5138,7 @@ public class ItemListJson ""Position"": ""(75.0, 14.0, 172.6)"", ""RequiredItems"": [], ""RequiredItemsDoors"": [ - {""Swamp Redux 2"": 1} + {""Swamp"": 1} ], ""SceneId"": 59, ""SceneName"": ""Swamp Redux 2"" @@ -5122,7 +5157,7 @@ public class ItemListJson {""21"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux, Swamp Redux 2_secret"": 1, ""21"": 1} + {""Cathedral Secret Legend Room"": 1, ""21"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5141,7 +5176,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5160,7 +5195,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5179,7 +5214,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5198,7 +5233,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5217,7 +5252,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5236,7 +5271,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5255,7 +5290,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5274,7 +5309,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5293,7 +5328,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Cathedral Redux"": 1} + {""Cathedral"": 1} ], ""SceneId"": 69, ""SceneName"": ""Cathedral Redux"" @@ -5388,7 +5423,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""RelicVoid, Sword Access_teleporter_relic plinth"": 1} + {""Hyperdash"": 1, ""Hero Relic - East Forest"": 1} ], ""SceneId"": 62, ""SceneName"": ""RelicVoid"" @@ -5407,7 +5442,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""RelicVoid, Library Hall_teleporter_relic plinth"": 1} + {""Hyperdash"": 1, ""Hero Relic - Library"": 1} ], ""SceneId"": 62, ""SceneName"": ""RelicVoid"" @@ -5426,7 +5461,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""RelicVoid, Monastery_teleporter_relic plinth"": 1} + {""Hyperdash"": 1, ""Hero Relic - Quarry"": 1} ], ""SceneId"": 62, ""SceneName"": ""RelicVoid"" @@ -5445,7 +5480,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""RelicVoid, Fortress Reliquary_teleporter_relic plinth"": 1} + {""Hyperdash"": 1, ""Hero Relic - Fortress"": 1} ], ""SceneId"": 62, ""SceneName"": ""RelicVoid"" @@ -5464,7 +5499,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""RelicVoid, Archipelagos Redux_teleporter_relic plinth"": 1} + {""Hyperdash"": 1, ""Hero Relic - West Garden"": 1} ], ""SceneId"": 62, ""SceneName"": ""RelicVoid"" @@ -5483,7 +5518,7 @@ public class ItemListJson {""Hyperdash"": 1, ""12"": 1} ], ""RequiredItemsDoors"": [ - {""Hyperdash"": 1, ""RelicVoid, Swamp Redux 2_teleporter_relic plinth"": 1} + {""Hyperdash"": 1, ""Hero Relic - Swamp"": 1} ], ""SceneId"": 62, ""SceneName"": ""RelicVoid"" diff --git a/src/Data/ItemTracker.cs b/src/Data/ItemTracker.cs index eb307be..7359dec 100644 --- a/src/Data/ItemTracker.cs +++ b/src/Data/ItemTracker.cs @@ -41,9 +41,9 @@ public struct SceneInfo { {"Flask Shard", 0}, {"Flask Container", 0}, {"Pages", 0}, - {"Prayer Page", 0}, - {"Holy Cross Page", 0}, - {"Ice Rod Page", 0}, + {"Prayer", 0}, + {"Holy Cross", 0}, + {"Icebolt", 0}, {"Fairies", 0}, {"Golden Trophies", 0}, {"Dath Stone", 0}, @@ -124,9 +124,9 @@ public void SetCollectedItem(string ItemName, bool WriteToDisk) { if (Item.Type == ItemTypes.PAGE) { ImportantItems["Pages"]++; - if (Item.Name == "Pages 24-25 (Prayer)") { ImportantItems["Prayer Page"]++; } - if (Item.Name == "Pages 42-43 (Holy Cross)") { ImportantItems["Holy Cross Page"]++; } - if (Item.Name == "Pages 52-53 (Ice Rod)") { ImportantItems["Ice Rod Page"]++; } + if (Item.Name == "Pages 24-25 (Prayer)") { ImportantItems["Prayer"]++; } + if (Item.Name == "Pages 42-43 (Holy Cross)") { ImportantItems["Holy Cross"]++; } + if (Item.Name == "Pages 52-53 (Icebolt)") { ImportantItems["Icebolt"]++; } } if (Item.Type == ItemTypes.GOLDENTROPHY) { @@ -136,6 +136,12 @@ public void SetCollectedItem(string ItemName, bool WriteToDisk) { Inventory.GetItemByName("Spear").Quantity = 1; } } + + if (Item.Type == ItemTypes.HEXAGONQUEST && SaveFile.GetInt(AbilityShuffle) == 1) { + if (Inventory.GetItemByName("Hexagon Gold").Quantity == SaveFile.GetInt(HexagonQuestPrayer)) { ImportantItems["Prayer"]++; } + if (Inventory.GetItemByName("Hexagon Gold").Quantity == SaveFile.GetInt(HexagonQuestHolyCross)) { ImportantItems["Holy Cross"]++; } + if (Inventory.GetItemByName("Hexagon Gold").Quantity == SaveFile.GetInt(HexagonQuestIcebolt)) { ImportantItems["Icebolt"]++; } + } if (Item.Type == ItemTypes.SWORDUPGRADE) { if (SaveFile.GetInt(SwordProgressionEnabled) == 1) { @@ -168,6 +174,8 @@ public static void SaveTrackerFile() { } public static void PopulateSpoilerLog() { + if (TunicArchipelago.Settings.RaceMode) { return; } + int seed = SaveFile.GetInt("seed"); Dictionary> SpoilerLog = new Dictionary>(); foreach (string Key in Locations.SceneNamesForSpoilerLog.Keys) @@ -175,34 +183,64 @@ public static void PopulateSpoilerLog() { SpoilerLog[Key] = new List(); } - foreach (string Key in ItemLookup.ItemList.Keys) { - ArchipelagoItem Item = ItemLookup.ItemList[Key]; + if (IsArchipelago()) { + foreach (string Key in ItemLookup.ItemList.Keys) { + ArchipelagoItem Item = ItemLookup.ItemList[Key]; - string Spoiler = $"\t{((Locations.CheckedLocations[Key] || SaveFile.GetInt($"randomizer picked up {Key}") == 1 || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Key} was collected") == 1)) ? "x" : "-")} {Locations.LocationIdToDescription[Key]}: {Item.ItemName} ({Archipelago.instance.GetPlayerName(Item.Player)})"; + string Spoiler = $"\t{((Locations.CheckedLocations[Key] || SaveFile.GetInt($"randomizer picked up {Key}") == 1 || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Key} was collected") == 1)) ? "x" : "-")} {Locations.LocationIdToDescription[Key]}: {Item.ItemName} ({Archipelago.instance.GetPlayerName(Item.Player)})"; - SpoilerLog[Locations.VanillaLocations[Key].Location.SceneName].Add(Spoiler); + SpoilerLog[Locations.VanillaLocations[Key].Location.SceneName].Add(Spoiler); + } + } + if (IsSinglePlayer()) { + foreach(string Key in Locations.RandomizedLocations.Keys) { + Check Check = Locations.RandomizedLocations[Key]; + ItemData Item = ItemLookup.GetItemDataFromCheck(Check); + string Spoiler = $"\t{(Locations.CheckedLocations[Key] ? "x" : "-")} {Locations.LocationIdToDescription[Key]}: {Item.Name}"; + SpoilerLog[Locations.VanillaLocations[Key].Location.SceneName].Add(Spoiler); + } } - List SpoilerLogLines = new List() { - "Seed: " + SaveFile.GetString("seed"), + "Seed: " + seed, "Lines that start with 'x' instead of '-' represent items that have been collected\n", - "Major Items" }; - - foreach (string MajorItem in ItemLookup.MajorItems) { + if (IsArchipelago()) { + SpoilerLogLines.Add("Major Items"); + foreach (string MajorItem in ItemLookup.MajorItems) { if(MajorItem == "Gold Questagon") { continue; } - if(Locations.MajorItemLocations.ContainsKey(MajorItem) && Locations.MajorItemLocations[MajorItem].Count > 0) { - foreach (ArchipelagoHint apHint in Locations.MajorItemLocations[MajorItem]) { + if(Locations.MajorItemLocations.ContainsKey(MajorItem) && Locations.MajorItemLocations[MajorItem].Count > 0) { + foreach (ArchipelagoHint apHint in Locations.MajorItemLocations[MajorItem]) { - bool HasItem = false; - if (Archipelago.instance.integration.session.Locations.AllLocationsChecked.Contains(Archipelago.instance.integration.session.Locations.GetLocationIdFromName(Archipelago.instance.GetPlayerGame((int)apHint.Player), apHint.Location))) { - HasItem = true; + bool HasItem = false; + if (Archipelago.instance.integration.session.Locations.AllLocationsChecked.Contains(Archipelago.instance.integration.session.Locations.GetLocationIdFromName(Archipelago.instance.GetPlayerGame((int)apHint.Player), apHint.Location))) { + HasItem = true; + } + string Spoiler = $"\t{(HasItem ? "x" : "-")} {MajorItem}: {apHint.Location} ({Archipelago.instance.GetPlayerName((int)apHint.Player)}'s World)"; + SpoilerLogLines.Add(Spoiler); } - string Spoiler = $"\t{(HasItem ? "x" : "-")} {MajorItem}: {apHint.Location} ({Archipelago.instance.GetPlayerName((int)apHint.Player)}'s World)"; + } + } + } + if (IsSinglePlayer()) { + SpoilerLogLines.AddRange(GetMysterySeedSettingsForSpoilerLog()); + + SpoilerLogLines.Add("Major Items"); + foreach (string MajorItem in ItemLookup.LegacyMajorItems) { + foreach (Check Check in ItemRandomizer.FindAllRandomizedItemsByName(MajorItem)) { + ItemData ItemData = ItemLookup.GetItemDataFromCheck(Check); + string Key = $"{Check.Location.LocationId} [{Check.Location.SceneName}]"; + string Spoiler = $"\t{(Locations.CheckedLocations[Key] ? "x" : "-")} {ItemData.Name}: {Locations.SceneNamesForSpoilerLog[Check.Location.SceneName]} - {Locations.LocationIdToDescription[Key]}"; SpoilerLogLines.Add(Spoiler); } } } + + if (SaveFile.GetInt(HexagonQuestEnabled) == 1 && SaveFile.GetInt(AbilityShuffle) == 1) { + SpoilerLogLines.Add($"\t{(SaveFile.GetInt(PrayerUnlocked) == 1 ? "x" : "-")} Prayer: {SaveFile.GetInt(HexagonQuestPrayer)} Gold Questagons"); + SpoilerLogLines.Add($"\t{(SaveFile.GetInt(HolyCrossUnlocked) == 1 ? "x" : "-")} Holy Cross: {SaveFile.GetInt(HexagonQuestHolyCross)} Gold Questagons"); + SpoilerLogLines.Add($"\t{(SaveFile.GetInt(IceBoltUnlocked) == 1 ? "x" : "-")} Icebolt: {SaveFile.GetInt(HexagonQuestIcebolt)} Gold Questagons"); + } + foreach (string Key in SpoilerLog.Keys) { SpoilerLogLines.Add(Locations.SceneNamesForSpoilerLog[Key]); SpoilerLog[Key].Sort(); @@ -210,11 +248,6 @@ public static void PopulateSpoilerLog() { SpoilerLogLines.Add(line); } } - if (SaveFile.GetInt(HexagonQuestEnabled) == 1 && SaveFile.GetInt(AbilityShuffle) == 1) { - SpoilerLogLines.Add($"\t{(SaveFile.GetInt(PrayerUnlocked) == 1 ? "x" : "-")} Prayer: {SaveFile.GetInt(HexagonQuestPrayer)} Gold Questagons"); - SpoilerLogLines.Add($"\t{(SaveFile.GetInt(HolyCrossUnlocked) == 1 ? "x" : "-")} Holy Cross: {SaveFile.GetInt(HexagonQuestHolyCross)} Gold Questagons"); - SpoilerLogLines.Add($"\t{(SaveFile.GetInt(IceRodUnlocked) == 1 ? "x" : "-")} Ice Rod: {SaveFile.GetInt(HexagonQuestIceRod)} Gold Questagons"); - } if (SaveFile.GetInt(EntranceRando) == 1) { @@ -237,5 +270,26 @@ public static void PopulateSpoilerLog() { } Logger.LogInfo("Wrote spoiler log to " + TunicArchipelago.SpoilerLogPath); } + + private static List GetMysterySeedSettingsForSpoilerLog() { + if (SaveFile.GetInt("randomizer mystery seed") == 0) { return new List(); }; + List MysterySettings = new List() { + "Mystery Seed Settings:", + $"\t- Hexagon Quest: {SaveFile.GetInt(HexagonQuestEnabled) == 1}", + SaveFile.GetInt(HexagonQuestEnabled) == 1 ? $"\t- Hexagon Quest Goal: {SaveFile.GetInt("randomizer hexagon quest goal")}" : "", + SaveFile.GetInt(HexagonQuestEnabled) == 1 ? $"\t- Extra Hexagons: {SaveFile.GetInt("randomizer hexagon quest extras")}%" : "", + $"\t- Sword Progression: {SaveFile.GetInt(SwordProgressionEnabled) == 1}", + $"\t- Keys Behind Bosses: {SaveFile.GetInt(KeysBehindBosses) == 1}", + $"\t- Start with Sword: {SaveFile.GetInt("randomizer started with sword") == 1}", + $"\t- Shuffled Abilities: {SaveFile.GetInt(AbilityShuffle) == 1}", + $"\t- Entrance Randomizer: {SaveFile.GetInt(EntranceRando) == 1}", + SaveFile.GetInt(EntranceRando) == 1 ? $"\t- Entrance Randomizer (Fewer Shops): {SaveFile.GetInt("randomizer ER fixed shop") == 1}" : "", + $"\t- Maskless Logic: {SaveFile.GetInt(MasklessLogic) == 1}", + $"\t- Lanternless Logic: {SaveFile.GetInt(LanternlessLogic) == 1}", + $"\t- Laurels Location: {((RandomizerSettings.FixedLaurelsType)SaveFile.GetInt("randomizer laurels location")).ToString()}\n", + }; + MysterySettings.RemoveAll(x => x == ""); + return MysterySettings; + } } } diff --git a/src/Data/Items.cs b/src/Data/Items.cs index 4f98590..ac99b47 100644 --- a/src/Data/Items.cs +++ b/src/Data/Items.cs @@ -1,6 +1,8 @@ using Archipelago.MultiClient.Net.Enums; +using BepInEx.Logging; using System; using System.Collections.Generic; +using System.Linq; namespace TunicArchipelago { @@ -276,10 +278,23 @@ public class ItemLookup { { "Pages 46-47", new ItemData("Pages 46-47", "useful", "23", ItemTypes.PAGE, 1) }, { "Pages 48-49", new ItemData("Pages 48-49", "useful", "24", ItemTypes.PAGE, 1) }, { "Pages 50-51", new ItemData("Pages 50-51", "useful", "25", ItemTypes.PAGE, 1) }, - { "Pages 52-53 (Ice Rod)", new ItemData("Pages 52-53 (Ice Rod)", "progression", "26", ItemTypes.PAGE, 1) }, + { "Pages 52-53 (Icebolt)", new ItemData("Pages 52-53 (Icebolt)", "progression", "26", ItemTypes.PAGE, 1) }, { "Pages 54-55", new ItemData("Pages 54-55", "useful", "27", ItemTypes.PAGE, 1) }, }; + public static ItemData GetItemDataFromCheck(Check Check) { + if (ItemLookup.FairyLookup.ContainsKey(Check.Reward.Name)) { + return Items["Fairy"]; + } else if (Check.Reward.Name == "Sword Progression") { + return Items["Sword Upgrade"]; + } else if (Check.Reward.Name == "Fool Trap") { + return Items["Fool Trap"]; + } else { + string itemName = ItemLookup.Items.Values.Where(itemdata => itemdata.ItemNameForInventory == Check.Reward.Name && itemdata.QuantityToGive == Check.Reward.Amount).FirstOrDefault().Name; + return Items.ContainsKey(itemName) ? Items[itemName] : Items["Money x1"]; + } + } + public static List LevelUpItems = new List() { "Level Up - Attack", "Level Up - DamageResist", "Level Up - PotionEfficiency", "Level Up - Health", "Level Up - Stamina", "Level Up - Magic" }; public static Dictionary BonusUpgrades = new Dictionary() { @@ -338,13 +353,17 @@ public class ItemLookup { public static List MajorItems = new List() { "Stick", "Sword", "Sword Upgrade", "Magic Dagger", "Magic Wand", "Magic Orb", "Hero's Laurels", "Lantern", "Shield", "Gun", "Scavenger Mask", "Old House Key", "Fortress Vault Key", "Dath Stone", "Hourglass", "Hero Relic - ATT", "Hero Relic - DEF", "Hero Relic - POTION", "Hero Relic - HP", "Hero Relic - SP", - "Hero Relic - MP", "Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon", "Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Ice Rod)" + "Hero Relic - MP", "Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon", "Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)" + }; + + public static List LegacyMajorItems = new List() { "Sword", "Sword Progression", "Stundagger", "Techbow", "Wand", "Hyperdash", "Lantern", "Shield", "Shotgun", "Mask", + "Key (House)", "Vault Key (Red)", "Dath Stone", "Relic - Hero Sword", "Relic - Hero Crown", "Relic - Hero Water", "Relic - Hero Pendant HP", "Relic - Hero Pendant SP", + "Relic - Hero Pendant MP", "Hexagon Red", "Hexagon Green", "Hexagon Blue", "12", "21", "26" }; public static string PrayerUnlockedLine = $"\"PRAYER Unlocked.\" Jahnuhl yor wizduhm, rooin sEkur."; public static string HolyCrossUnlockedLine = $"\"HOLY CROSS Unlocked.\" sEk wuht iz rItfuhlE yorz."; - public static string IceRodUnlockedLine = $"\"ICE ROD Unlocked.\" #A wOnt nO wuht hit #ehm"; - + public static string IceboltUnlockedLine = $"\"ICEBOLT Unlocked.\" #A wOnt nO wuht hit #ehm"; public static Dictionary SimplifiedItemNames = new Dictionary() { {"Firecracker", "Firecracker"}, @@ -361,6 +380,7 @@ public class ItemLookup { {"Stick", "Stick"}, {"Sword", "Sword"}, {"Sword Progression", "Sword Upgrade"}, + {"Sword Upgrade", "Sword Upgrade"}, {"Stundagger", "Magic Dagger"}, {"Techbow", "Magic Wand"}, {"Wand", "Magic Orb"}, @@ -429,6 +449,7 @@ public class ItemLookup { {"Archipelagos Redux-(-236.0, 8.0, 86.3)", "Fairy"}, {"Fortress Main-(-75.0, -1.0, 17.0)", "Fairy"}, {"East Forest Redux-(164.0, -25.0, -56.0)", "Fairy"}, + {"Fairy", "Fairy"}, {"GoldenTrophy_1", "Mr Mayor"}, {"GoldenTrophy_2", "Secret Legend"}, {"GoldenTrophy_3", "Sacred Geometry"}, @@ -467,7 +488,7 @@ public class ItemLookup { {"23", "Pages 46-47"}, {"24", "Pages 48-49"}, {"25", "Pages 50-51"}, - {"26", "Pages 52-53 (Ice Rod)"}, + {"26", "Pages 52-53 (Icebolt)"}, {"27", "Pages 54-55"}, }; @@ -476,6 +497,18 @@ public class ItemLookup { { "lurdrurdrurdl", "Granted Firebomb" }, { "ldrurdrurdrul", "Granted Icebomb" }, }; + + public static Dictionary> FillerItems = new Dictionary>() { + { "Firecracker", new List() { 2, 3, 4, 5, 6 } }, + { "Firebomb", new List() { 2, 3 } }, + { "Ice Bomb", new List() { 2, 3, 5 } }, + { "Bait", new List() { 1, 2 } }, + { "Pepper", new List() { 2 } }, + { "Ivy", new List() { 3 } }, + { "Berry_HP", new List() { 1, 2, 3 } }, + { "Berry_MP", new List() { 1, 2, 3 } }, + { "money", new List() { 20, 25, 30, 32, 40, 48, 50 } }, + }; } } diff --git a/src/Data/Locations.cs b/src/Data/Locations.cs index 4b40d3a..6e9800f 100644 --- a/src/Data/Locations.cs +++ b/src/Data/Locations.cs @@ -15,7 +15,8 @@ public class Locations { public static Dictionary LocationIdToDescription = new Dictionary(); public static Dictionary LocationDescriptionToId = new Dictionary(); - public static Dictionary VanillaLocations = new Dictionary() { }; + public static Dictionary VanillaLocations = new Dictionary() { }; + public static Dictionary RandomizedLocations = new Dictionary { }; public static Dictionary CheckedLocations = new Dictionary(); public static Dictionary> MajorItemLocations = new Dictionary>(); @@ -25,7 +26,7 @@ public class Locations { public static string LocationNamesJson = "{\"5 [Overworld Redux]\":\"Overworld - [East] Between Ladders Near Ruined Passage\",\"55 [Archipelagos Redux]\":\"West Garden - [North] Obscured Beneath Hero's Memorial\",\"217 [Archipelagos Redux]\":\"West Garden - [North] Behind Holy Cross Door\",\"west_garden [Archipelagos Redux]\":\"West Garden - [North] Page Pickup\",\"256 [Archipelagos Redux]\":\"West Garden - [North] Across From Page Pickup\",\"280 [Archipelagos Redux]\":\"West Garden - [West] In Flooded Walkway\",\"283 [Archipelagos Redux]\":\"West Garden - [West] Past Flooded Walkway\",\"57 [Archipelagos Redux]\":\"West Garden - [West Highlands] Upper Left Walkway\",\"253 [Archipelagos Redux]\":\"West Garden - [Central Lowlands] Passage Beneath Bridge\",\"56 [Archipelagos Redux]\":\"West Garden - [Central Lowlands] Chest Near Shortcut Bridge\",\"Archipelagos Redux-(-396.3, 1.4, 42.3) [Archipelagos Redux]\":\"West Garden - [West Lowlands] Tree Holy Cross Chest\",\"58 [Archipelagos Redux]\":\"West Garden - [Central Lowlands] Chest Beneath Save Point\",\"206 [Archipelagos Redux]\":\"West Garden - [Central Lowlands] Chest Beneath Faeries\",\"94 [Archipelagos Redux]\":\"West Garden - [South Highlands] Secret Chest Beneath Fuse\",\"111 [Archipelagos Redux]\":\"West Garden - [Southeast Lowlands] Outside Cave\",\"archipelagos_night [Archipelagos Redux]\":\"West Garden - [East Lowlands] Page Behind Ice Dagger House\",\"257 [Archipelagos Redux]\":\"West Garden - [Central Lowlands] Below Left Walkway\",\"59 [Archipelagos Redux]\":\"West Garden - [Central Highlands] Behind Guard Captain\",\"Archipelagos Redux-(-236.0, 8.0, 86.3) [Archipelagos Redux]\":\"West Garden - [Central Highlands] Holy Cross (Blue Lines)\",\"223 [Archipelagos Redux]\":\"West Garden - [Central Highlands] Top of Ladder Before Boss\",\"93 [Archipelagos Redux]\":\"West Garden - [Central Highlands] After Garden Knight\",\"Stundagger [archipelagos_house]\":\"West Garden House - [Southeast Lowlands] Ice Dagger Pickup\",\"72 [Atoll Redux]\":\"Ruined Atoll - [North] From Lower Overworld Entrance\",\"67 [Atoll Redux]\":\"Ruined Atoll - [South] Upper Floor On Bricks\",\"218 [Atoll Redux]\":\"Ruined Atoll - [South] Upper Floor On Power Line\",\"219 [Atoll Redux]\":\"Ruined Atoll - [South] Chest Near Big Crabs\",\"76 [Atoll Redux]\":\"Ruined Atoll - [Southeast] Chest Near Fuse\",\"66 [Atoll Redux]\":\"Ruined Atoll - [North] Obscured Beneath Bridge\",\"220 [Atoll Redux]\":\"Ruined Atoll - [North] Guarded By Bird\",\"287 [Atoll Redux]\":\"Ruined Atoll - [Northwest] Bombable Wall\",\"69 [Atoll Redux]\":\"Ruined Atoll - [Northwest] Behind Envoy\",\"1010 [Atoll Redux]\":\"Ruined Atoll - [West] Near Kevin Block\",\"70 [Atoll Redux]\":\"Ruined Atoll - [Southwest] Obscured Behind Fuse\",\"68 [Atoll Redux]\":\"Ruined Atoll - [South] Near Birds\",\"Key [Atoll Redux]\":\"Ruined Atoll - [Northeast] Key Pickup\",\"73 [Atoll Redux]\":\"Ruined Atoll - [East] Locked Room Lower Chest\",\"71 [Atoll Redux]\":\"Ruined Atoll - [East] Locked Room Upper Chest\",\"221 [Atoll Redux]\":\"Ruined Atoll - [Northeast] Chest Beneath Brick Walkway\",\"75 [Atoll Redux]\":\"Ruined Atoll - [Northeast] Chest On Brick Walkway\",\"999 [Cathedral Arena]\":\"Cathedral Gauntlet - Gauntlet Reward\",\"1002 [Cathedral Redux]\":\"Cathedral - Secret Legend Trophy Chest\",\"240 [Cathedral Redux]\":\"Cathedral - [1F] Library\",\"244 [Cathedral Redux]\":\"Cathedral - [1F] Library Secret\",\"236 [Cathedral Redux]\":\"Cathedral - [1F] Guarded By Lasers\",\"237 [Cathedral Redux]\":\"Cathedral - [1F] Near Spikes\",\"243 [Cathedral Redux]\":\"Cathedral - [2F] Bird Room Secret\",\"238 [Cathedral Redux]\":\"Cathedral - [2F] Bird Room\",\"239 [Cathedral Redux]\":\"Cathedral - [2F] Entryway Upper Walkway\",\"241 [Cathedral Redux]\":\"Cathedral - [2F] Library\",\"242 [Cathedral Redux]\":\"Cathedral - [2F] Guarded By Lasers\",\"60 [Changing Room]\":\"Changing Room - Normal Chest\",\"52 [Crypt Redux]\":\"Dark Tomb - Skulls Chest\",\"213 [Crypt Redux]\":\"Dark Tomb - Spike Maze Upper Walkway\",\"53 [Crypt Redux]\":\"Dark Tomb - Spike Maze Near Stairs\",\"210 [Crypt Redux]\":\"Dark Tomb - Spike Maze Near Exit\",\"54 [Crypt Redux]\":\"Dark Tomb - 1st Laser Room Obscured\",\"212 [Crypt Redux]\":\"Dark Tomb - 1st Laser Room\",\"211 [Crypt Redux]\":\"Dark Tomb - 2nd Laser Room\",\"CubeRoom-(321.1, 3.0, 217.0) [CubeRoom]\":\"Cube Cave - Holy Cross Chest\",\"1011 [Dusty]\":\"Fortress Leaf Piles - Secret Chest\",\"286 [East Forest Redux]\":\"East Forest - Bombable Wall\",\"25 [East Forest Redux]\":\"East Forest - Near Telescope\",\"24 [East Forest Redux]\":\"East Forest - Near Save Point\",\"forest [East Forest Redux]\":\"East Forest - Page On Teleporter\",\"23 [East Forest Redux]\":\"East Forest - From Guardhouse 1 Chest\",\"East Forest Redux-(104.0, 16.0, 61.0) [East Forest Redux]\":\"East Forest - Dancing Fox Spirit Holy Cross\",\"26 [East Forest Redux]\":\"East Forest - Spider Chest\",\"248 [East Forest Redux]\":\"East Forest - Beneath Spider Chest\",\"East Forest Redux-(164.0, -25.0, -56.0) [East Forest Redux]\":\"East Forest - Golden Obelisk Holy Cross\",\"284 [East Forest Redux]\":\"East Forest - Lower Grapple Chest\",\"281 [East Forest Redux]\":\"East Forest - Lower Dash Chest\",\"1006 [East Forest Redux]\":\"East Forest - Ice Rod Grapple Chest\",\"21 [East Forest Redux]\":\"East Forest - Above Save Point\",\"22 [East Forest Redux]\":\"East Forest - Above Save Point Obscured\",\"29 [East Forest Redux Interior]\":\"Guardhouse 2 - Upper Floor\",\"30 [East Forest Redux Interior]\":\"Guardhouse 2 - Bottom Floor Secret\",\"27 [East Forest Redux Laddercave]\":\"Guardhouse 1 - Upper Floor Obscured\",\"28 [East Forest Redux Laddercave]\":\"Guardhouse 1 - Upper Floor\",\"270 [EastFiligreeCache]\":\"Southeast Cross Door - Chest 3\",\"271 [EastFiligreeCache]\":\"Southeast Cross Door - Chest 2\",\"272 [EastFiligreeCache]\":\"Southeast Cross Door - Chest 1\",\"forest shortcut [Forest Belltower]\":\"Forest Belltower - Page Pickup\",\"205 [Forest Belltower]\":\"Forest Belltower - Obscured Beneath Bell Bottom Floor\",\"20 [Forest Belltower]\":\"Forest Belltower - After Guard Captain\",\"204 [Forest Belltower]\":\"Forest Belltower - Obscured Near Bell Top Floor\",\"19 [Forest Belltower]\":\"Forest Belltower - Near Save Point\",\"Vault Key (Red) [Fortress Arena]\":\"Fortress Arena - Siege Engine/Vault Key Pickup\",\"Hexagon Red [Fortress Arena]\":\"Fortress Arena - Hexagon Red\",\"63 [Fortress Basement]\":\"Beneath the Fortress - Obscured Behind Waterfall\",\"61 [Fortress Basement]\":\"Beneath the Fortress - Bridge\",\"62 [Fortress Basement]\":\"Beneath the Fortress - Cell Chest 1\",\"65 [Fortress Basement]\":\"Beneath the Fortress - Cell Chest 2\",\"64 [Fortress Basement]\":\"Beneath the Fortress - Back Room Chest\",\"86 [Fortress Courtyard]\":\"Fortress Courtyard - From East Belltower\",\"96 [Fortress Courtyard]\":\"Fortress Courtyard - Below Walkway\",\"88 [Fortress Courtyard]\":\"Fortress Courtyard - Near Fuse\",\"87 [Fortress Courtyard]\":\"Fortress Courtyard - Chest Near Cave\",\"spidercave [Fortress Courtyard]\":\"Fortress Courtyard - Page Near Cave\",\"112 [Fortress East]\":\"Fortress East Shortcut - Chest Near Slimes\",\"fortress [Fortress Main]\":\"Eastern Vault Fortress - [West Wing] Page Pickup\",\"83 [Fortress Main]\":\"Eastern Vault Fortress - [West Wing] Dark Room Chest 1\",\"84 [Fortress Main]\":\"Eastern Vault Fortress - [West Wing] Dark Room Chest 2\",\"Fortress Main-(-75.0, -1.0, 17.0) [Fortress Main]\":\"Eastern Vault Fortress - [West Wing] Candles Holy Cross\",\"85 [Fortress Main]\":\"Eastern Vault Fortress - [East Wing] Bombable Wall\",\"113 [Fortress Reliquary]\":\"Fortress Grave Path - Upper Walkway\",\"114 [Fortress Reliquary]\":\"Fortress Grave Path - Chest Right of Grave\",\"115 [Fortress Reliquary]\":\"Fortress Grave Path - Obscured Chest Left of Grave\",\"Wand [frog cave main]\":\"Frog's Domain - Magic Orb Pickup\",\"77 [frog cave main]\":\"Frog's Domain - Above Vault\",\"82 [frog cave main]\":\"Frog's Domain - Side Room Grapple Secret\",\"81 [frog cave main]\":\"Frog's Domain - Side Room Chest\",\"80 [frog cave main]\":\"Frog's Domain - Side Room Secret Passage\",\"78 [frog cave main]\":\"Frog's Domain - Main Room Top Floor\",\"279 [frog cave main]\":\"Frog's Domain - Grapple Above Hot Tub\",\"79 [frog cave main]\":\"Frog's Domain - Main Room Bottom Floor\",\"222 [frog cave main]\":\"Frog's Domain - Near Vault\",\"259 [frog cave main]\":\"Frog's Domain - Slorm Room\",\"276 [frog cave main]\":\"Frog's Domain - Escape Chest\",\"Lantern [Furnace]\":\"West Furnace - Lantern Pickup\",\"92 [Furnace]\":\"West Furnace - Chest\",\"Hexagon Green [Library Arena]\":\"Librarian - Hexagon Green\",\"Library Hall-(133.3, 10.0, -43.2) [Library Hall]\":\"Library Hall - Holy Cross Chest\",\"library_2 [Library Lab]\":\"Library Lab - Page 1\",\"library_3 [Library Lab]\":\"Library Lab - Page 2\",\"library_1 [Library Lab]\":\"Library Lab - Page 3\",\"226 [Library Lab]\":\"Library Lab - Chest By Shrine 1\",\"225 [Library Lab]\":\"Library Lab - Chest By Shrine 2\",\"227 [Library Lab]\":\"Library Lab - Chest By Shrine 3\",\"228 [Library Lab]\":\"Library Lab - Behind Chalkboard by Fuse\",\"216 [Maze Room]\":\"Maze Cave - Maze Room Chest\",\"Maze Room-(1.0, 0.0, -1.0) [Maze Room]\":\"Maze Cave - Maze Room Holy Cross\",\"200 [Monastery]\":\"Monastery - Monastery Chest\",\"mountain [Mountain]\":\"Lower Mountain - Page Before Door\",\"final [Mountaintop]\":\"Top of the Mountain - Page At The Peak\",\"Overworld Cave-(-90.4, 515.0, -738.9) [Overworld Cave]\":\"Caustic Light Cave - Holy Cross Chest\",\"89 [Overworld Interiors]\":\"Old House - Normal Chest\",\"Overworld Interiors-(-28.0, 27.0, -50.5) [Overworld Interiors]\":\"Old House - Holy Cross Chest\",\"Shield [Overworld Interiors]\":\"Old House - Shield Pickup\",\"under_overworld [Overworld Interiors]\":\"Old House - Holy Cross Door Page\",\"1013 [Overworld Redux]\":\"Overworld - [South] Starting Platform Holy Cross\",\"8 [Overworld Redux]\":\"Overworld - [Central] Chest Across From Well\",\"11 [Overworld Redux]\":\"Overworld - [West] Obscured Near Well\",\"1 [Overworld Redux]\":\"Overworld - [West] Obscured Behind Windmill\",\"1003 [Overworld Redux]\":\"Overworld - [West] Windmill Holy Cross\",\"Key [Overworld Redux]\":\"Overworld - [West] Key Pickup\",\"12 [Overworld Redux]\":\"Overworld - [Central] Bombable Wall\",\"90 [Overworld Redux]\":\"Overworld - [East] Chest In Trees\",\"255 [Overworld Redux]\":\"Overworld - [Southeast] Chest Near Swamp\",\"15 [Overworld Redux]\":\"Overworld - [East] Chest Near Pots\",\"Overworld Redux-(90.4, 36.0, -122.1) [Overworld Redux]\":\"Overworld - [East] Weathervane Holy Cross\",\"overworld post-forest [Overworld Redux]\":\"Overworld - [East] Page Near Secret Shop\",\"Overworld Redux-(64.5, 44.0, -40.0) [Overworld Redux]\":\"Overworld - [Northeast] Flowers Holy Cross\",\"6 [Overworld Redux]\":\"Overworld - [Northeast] Chest Above Patrol Cave\",\"Techbow [Overworld Redux]\":\"Overworld - [Northwest] Fire Wand Pickup\",\"stonehenge_reward [Overworld Redux]\":\"Overworld - [Northwest] Golden Obelisk Page\",\"16 [Overworld Redux]\":\"Overworld - [Northwest] Chest Near Golden Obelisk\",\"9 [Overworld Redux]\":\"Overworld - [Northwest] Chest Near Quarry Gate\",\"13 [Overworld Redux]\":\"Overworld - [Northwest] Chest Near Turret\",\"207 [Overworld Redux]\":\"Overworld - [Northwest] Shadowy Corner Chest\",\"1008 [Overworld Redux]\":\"Overworld - [West] Windchimes Holy Cross\",\"town_upper [Overworld Redux]\":\"Overworld - [West] Page On Teleporter\",\"Overworld Redux-(-132.0, 28.0, -55.5) [Overworld Redux]\":\"Overworld - [West] Moss Wall Holy Cross\",\"91 [Overworld Redux]\":\"Overworld - [West] Chest Behind Moss Wall\",\"overworld_dash [Overworld Redux]\":\"Overworld - [Southwest] Fountain Page\",\"Overworld Redux-(-83.0, 20.0, -117.5) [Overworld Redux]\":\"Overworld - [Southwest] Fountain Holy Cross\",\"2 [Overworld Redux]\":\"Overworld - [Southwest] Chest Guarded By Turret\",\"209 [Overworld Redux]\":\"Overworld - [Southwest] Grapple Chest Over Walkway\",\"Key (House) [Overworld Redux]\":\"Overworld - [Southwest] Key Pickup\",\"17 [Overworld Redux]\":\"Overworld - [Southwest] South Chest Near Guard\",\"208 [Overworld Redux]\":\"Overworld - [Southwest] Obscured In Tunnel To Beach\",\"273 [Overworld Redux]\":\"Overworld - [Southwest] Beach Chest Near Flowers\",\"Overworld Redux-(-52.0, 2.0, -174.8) [Overworld Redux]\":\"Overworld - [Southwest] Flowers Holy Cross\",\"1004 [Overworld Redux]\":\"Overworld - [Southwest] Haiku Holy Cross\",\"7 [Overworld Redux]\":\"Overworld - [Southwest] Beach Chest Beneath Guard\",\"4 [Overworld Redux]\":\"Overworld - [Southwest] Tunnel Guarded By Turret\",\"18 [Overworld Redux]\":\"Overworld - [Southwest] West Beach Guarded By Turret\",\"267 [Overworld Redux]\":\"Overworld - [Southwest] West Beach Guarded By Turret 2\",\"beach [Overworld Redux]\":\"Overworld - [South] Beach Page\",\"285 [Overworld Redux]\":\"Overworld - [Southwest] Bombable Wall Near Fountain\",\"10 [Overworld Redux]\":\"Overworld - [South] Beach Chest\",\"cathedral [Overworld Redux]\":\"Overworld - [Southeast] Page on Pillar by Swamp\",\"tablet [Overworld Redux]\":\"Overworld - [Northwest] Page on Pillar by Dark Tomb\",\"town_well [Overworld Redux]\":\"Overworld - [Northwest] Page By Well\",\"245 [Overworld Redux]\":\"Overworld - [Northwest] Chest Beneath Quarry Gate\",\"14 [Overworld Redux]\":\"Overworld - [West] Near West Garden Entrance\",\"258 [Overworld Redux]\":\"Overworld - [Southwest] From West Garden\",\"3 [Overworld Redux]\":\"Overworld - [West] Chest After Bell\",\"266 [Overworld Redux]\":\"Overworld - [East] Grapple Chest\",\"214 [PatrolCave]\":\"Patrol Cave - Normal Chest\",\"PatrolCave-(74.0, 46.0, 24.0) [PatrolCave]\":\"Patrol Cave - Holy Cross Chest\",\"Quarry Redux-(0.7, 68.0, 84.7) [Quarry Redux]\":\"Quarry - [Back Entrance] Bushes Holy Cross\",\"126 [Quarry Redux]\":\"Quarry - [East] Obscured Near Telescope\",\"133 [Quarry Redux]\":\"Quarry - [Central] Obscured Below Entry Walkway\",\"116 [Quarry Redux]\":\"Quarry - [Back Entrance] Chest\",\"128 [Quarry Redux]\":\"Quarry - [Back Entrance] Obscured Behind Wall\",\"288 [Quarry Redux]\":\"Quarry - [West] Upper Area Bombable Wall\",\"127 [Quarry Redux]\":\"Quarry - [West] Upper Area Near Waterfall\",\"120 [Quarry Redux]\":\"Quarry - [West] Near Shooting Range\",\"265 [Quarry Redux]\":\"Quarry - [West] Shooting Range Secret Path\",\"121 [Quarry Redux]\":\"Quarry - [West] Below Shooting Range\",\"130 [Quarry Redux]\":\"Quarry - [West] Lower Area Below Bridge\",\"131 [Quarry Redux]\":\"Quarry - [West] Lower Area Isolated Chest\",\"262 [Quarry Redux]\":\"Quarry - [West] Lower Area After Bridge\",\"122 [Quarry Redux]\":\"Quarry - [Lowlands] Below Broken Ladder\",\"129 [Quarry Redux]\":\"Quarry - [Lowlands] Upper Walkway\",\"132 [Quarry Redux]\":\"Quarry - [Lowlands] Near Elevator\",\"123 [Quarry Redux]\":\"Quarry - [Central] Below Entry Walkway\",\"117 [Quarry Redux]\":\"Quarry - [Central] Near Shortcut Ladder\",\"224 [Quarry Redux]\":\"Quarry - [East] Near Bridge\",\"289 [Quarry Redux]\":\"Quarry - [East] Bombable Wall\",\"118 [Quarry Redux]\":\"Quarry - [East] Near Telescope\",\"268 [Quarry Redux]\":\"Quarry - [Central] Obscured Behind Staircase\",\"125 [Quarry Redux]\":\"Quarry - [East] Obscured Beneath Scaffolding\",\"124 [Quarry Redux]\":\"Quarry - [East] Obscured Near Winding Staircase\",\"119 [Quarry Redux]\":\"Quarry - [East] Upper Floor\",\"250 [Quarry Redux]\":\"Quarry - [Central] Above Ladder\",\"282 [Quarry Redux]\":\"Quarry - [Central] Above Ladder Dash Chest\",\"134 [Quarry Redux]\":\"Quarry - [Central] Top Floor Overhang\",\"Relic PIckup (6) Sword) [RelicVoid]\":\"Hero's Grave - Tooth Relic\",\"Relic PIckup (5) (MP) [RelicVoid]\":\"Hero's Grave - Mushroom Relic\",\"Relic PIckup (4) (water) [RelicVoid]\":\"Hero's Grave - Ash Relic\",\"Relic PIckup (3) (HP) [RelicVoid]\":\"Hero's Grave - Flowers Relic\",\"Relic PIckup (2) (Crown) [RelicVoid]\":\"Hero's Grave - Effigy Relic\",\"Relic PIckup (1) (SP) [RelicVoid]\":\"Hero's Grave - Feathers Relic\",\"35 [Ruined Shop]\":\"Ruined Shop - Chest 1\",\"36 [Ruined Shop]\":\"Ruined Shop - Chest 2\",\"37 [Ruined Shop]\":\"Ruined Shop - Chest 3\",\"first [Ruins Passage]\":\"Ruined Passage - Page Pickup\",\"1001 [Ruins Passage]\":\"Ruined Passage - Holy Cross Chest\",\"40 [Sewer]\":\"Beneath the Well - [Entryway] Chest\",\"43 [Sewer]\":\"Beneath the Well - [Entryway] Obscured Behind Waterfall\",\"sewer [Sewer]\":\"Beneath the Well - [Second Room] Page\",\"49 [Sewer]\":\"Beneath the Well - [Second Room] Obscured Behind Waterfall\",\"47 [Sewer]\":\"Beneath the Well - [Back Corridor] Right Secret\",\"48 [Sewer]\":\"Beneath the Well - [Back Corridor] Left Secret\",\"41 [Sewer]\":\"Beneath the Well - [Third Room] Beneath Platform Chest\",\"42 [Sewer]\":\"Beneath the Well - [Third Room] Tentacle Chest\",\"46 [Sewer]\":\"Beneath the Well - [Second Room] Underwater Chest\",\"50 [Sewer]\":\"Beneath the Well - [Side Room] Chest By Pots\",\"44 [Sewer]\":\"Beneath the Well - [Save Room] Upper Floor Chest 1\",\"45 [Sewer]\":\"Beneath the Well - [Save Room] Upper Floor Chest 2\",\"51 [Sewer]\":\"Beneath the Well - [Side Room] Chest By Phrends\",\"264 [Sewer]\":\"Beneath the Well - [Powered Secret Room] Chest\",\"below_crypt [Sewer_Boss]\":\"Dark Tomb Checkpoint - [Passage To Dark Tomb] Page Pickup\",\"Potion (First) [Shop]\":\"Shop - Potion 1\",\"Potion (West Garden) [Shop]\":\"Shop - Potion 2\",\"Trinket Coin 1 (day) [Shop]\":\"Shop - Coin 1\",\"Trinket Coin 2 (night) [Shop]\":\"Shop - Coin 2\",\"skullcave [ShopSpecial]\":\"Special Shop - Secret Page Pickup\",\"105 [Swamp Redux 2]\":\"Swamp - [Entrance] Above Entryway\",\"235 [Swamp Redux 2]\":\"Swamp - [Entrance] Obscured Inside Watchtower\",\"254 [Swamp Redux 2]\":\"Swamp - [Entrance] North Small Island\",\"246 [Swamp Redux 2]\":\"Swamp - [Entrance] South Near Fence\",\"249 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Chest Near Graves\",\"247 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Guarded By Big Skeleton\",\"108 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Guarded By Tentacles\",\"104 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Obscured Beneath Telescope\",\"98 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Above Big Skeleton\",\"107 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Upper Walkway On Pedestal\",\"97 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Upper Walkway Dash Chest\",\"1005 [Swamp Redux 2]\":\"Swamp - [South Graveyard] 4 Orange Skulls\",\"103 [Swamp Redux 2]\":\"Swamp - [South Graveyard] Obscured Behind Ridge\",\"278 [Swamp Redux 2]\":\"Swamp - [Central] Obscured Behind Northern Mountain\",\"99 [Swamp Redux 2]\":\"Swamp - [Central] Beneath Memorial\",\"101 [Swamp Redux 2]\":\"Swamp - [Central] Near Ramps Up\",\"106 [Swamp Redux 2]\":\"Swamp - [Central] South Secret Passage\",\"109 [Swamp Redux 2]\":\"Swamp - [Upper Graveyard] Near Telescope\",\"100 [Swamp Redux 2]\":\"Swamp - [Upper Graveyard] Obscured Behind Hill\",\"102 [Swamp Redux 2]\":\"Swamp - [Upper Graveyard] Near Shield Fleemers\",\"277 [Swamp Redux 2]\":\"Swamp - [Outside Cathedral] Obscured Behind Memorial\",\"110 [Swamp Redux 2]\":\"Swamp - [Outside Cathedral] Near Moonlight Bridge Door\",\"32 [Sword Access]\":\"Forest Grave Path - Obscured Chest\",\"31 [Sword Access]\":\"Forest Grave Path - Above Gate\",\"Sword [Sword Access]\":\"Forest Grave Path - Sword Pickup\",\"1009 [Sword Access]\":\"Forest Grave Path - Holy Cross Code by Grave\",\"33 [Sword Access]\":\"Forest Grave Path - Upper Walkway\",\"19 [Sword Cave]\":\"Stick House - Stick Chest\",\"Temple-(14.0, 0.1, 42.4) [Temple]\":\"Sealed Temple - Holy Cross Chest\",\"temple [Temple]\":\"Sealed Temple - Page Pickup\",\"95 [Town Basement]\":\"Hourglass Cave - Hourglass Chest\",\"Town Basement-(-202.0, 28.0, 150.0) [Town Basement]\":\"Hourglass Cave - Holy Cross Chest\",\"town_filigree [Town_FiligreeRoom]\":\"Fountain Cross Door - Page Pickup\",\"FT_Island [Transit]\":\"Far Shore - Page Pickup\",\"1012 [Transit]\":\"Far Shore - Secret Chest\",\"Well Reward (3 Coins) [Trinket Well]\":\"Coins in the Well - 3 Coins\",\"Well Reward (6 Coins) [Trinket Well]\":\"Coins in the Well - 6 Coins\",\"Well Reward (10 Coins) [Trinket Well]\":\"Coins in the Well - 10 Coins\",\"Well Reward (15 Coins) [Trinket Well]\":\"Coins in the Well - 15 Coins\",\"Waterfall-(-47.0, 45.0, 10.0) [Waterfall]\":\"Secret Gathering Place - Holy Cross Chest\",\"waterfall [Waterfall]\":\"Secret Gathering Place - 10 Fairy Reward\",\"1007 [Waterfall]\":\"Secret Gathering Place - 20 Fairy Reward\",\"274 [ziggurat2020_1]\":\"Rooted Ziggurat Upper - Near Bridge Switch\",\"275 [ziggurat2020_1]\":\"Rooted Ziggurat Upper - Beneath Bridge To Administrator\",\"229 [ziggurat2020_2]\":\"Rooted Ziggurat Tower - Inside Tower\",\"230 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - Near Corpses\",\"231 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - Spider Ambush\",\"234 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - Guarded By Double Turrets\",\"261 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - Guarded By Double Turrets 2\",\"260 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - After 2nd Double Turret Chest\",\"232 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - Left Of Checkpoint Before Fuse\",\"233 [ziggurat2020_3]\":\"Rooted Ziggurat Lower - After Guarded Fuse\",\"Hexagon Blue [ziggurat2020_3]\":\"Rooted Ziggurat Lower - Hexagon Blue\"}"; public static void CreateLocationLookups() { - foreach (VanillaCheck info in JsonConvert.DeserializeObject>(ItemListJson.ItemList)) { + foreach (Check info in JsonConvert.DeserializeObject>(ItemListJson.ItemList)) { string locationId = $"{info.Location.LocationId} [{info.Location.SceneName}]"; VanillaLocations.Add(locationId, info); } diff --git a/src/Data/Portal.cs b/src/Data/Portal.cs index 99ff18b..5254d7f 100644 --- a/src/Data/Portal.cs +++ b/src/Data/Portal.cs @@ -5,196 +5,24 @@ public class Portal { //private static ManualLogSource Logger = TunicRandomizer.Logger; public string Scene { get; set; } public string Destination { get; set; } - public string Tag { get; set; } public string Name { get; set; } public string Region { get; set; } - public Dictionary RequiredItems { get; set; } = new Dictionary(); - public List> RequiredItemsOr { get; set; } = new List>(); - public Dictionary EntryItems { get; set; } = new Dictionary(); - public List GivesAccess { get; set; } = new List(); - public bool DeadEnd { get; set; } = false; - public bool PrayerPortal { get; set; } = false; - public bool OneWay { get; set; } = false; - public bool IgnoreScene { get; set; } = false; public string SceneDestinationTag { get; set; } - public Portal(string destination, string tag, string name, string scene) { - Destination = destination; - Tag = tag; - Name = name; - Scene = scene; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - public Portal(string destination, string tag, string name, string scene, string region, bool deadEnd = false, bool prayerPortal = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - Tag = tag; - Name = name; - Scene = scene; - Region = region; - DeadEnd = deadEnd; - PrayerPortal = prayerPortal; - OneWay = oneWay; - IgnoreScene = ignoreScene; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - public Portal(string destination, string tag, string name, string scene, string region, Dictionary requiredItems) { - Destination = destination; - Tag = tag; + public Portal(string name, string destination, string scene, string region) { Name = name; - Scene = scene; - Region = region; - RequiredItems = requiredItems; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - public Portal(string destination, string tag, string name, string scene, string region, List> requiredItemsOr) { Destination = destination; - Tag = tag; - Name = name; Scene = scene; Region = region; - RequiredItemsOr = requiredItemsOr; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); + SceneDestinationTag = (Scene + ", " + Destination); } - public Portal(string destination, string tag, string name, string scene, string region, Dictionary entryItems, bool deadEnd = false, bool prayerPortal = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - Tag = tag; - Name = name; - Scene = scene; - Region = region; - EntryItems = entryItems; - DeadEnd = deadEnd; - PrayerPortal = prayerPortal; - OneWay = oneWay; - IgnoreScene = ignoreScene; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - public Portal(string destination, string tag, string name, string scene, string region, List givesAccess, bool deadEnd = false, bool prayerPortal = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - Tag = tag; - Name = name; - Scene = scene; - Region = region; - GivesAccess = givesAccess; - DeadEnd = deadEnd; - PrayerPortal = prayerPortal; - OneWay = oneWay; - IgnoreScene = ignoreScene; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - public Portal(string destination, string tag, string name, string scene, string region, Dictionary requiredItems, List givesAccess, bool deadEnd = false, bool prayerPortal = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - Tag = tag; - Name = name; - Scene = scene; - Region = region; - RequiredItems = requiredItems; - GivesAccess = givesAccess; - DeadEnd = deadEnd; - PrayerPortal = prayerPortal; - OneWay = oneWay; - IgnoreScene = ignoreScene; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - public Portal(string destination, string tag, string name, string scene, string region, Dictionary requiredItems, List> requiredItemsOr, Dictionary entryItems, List givesAccess, bool deadEnd = false, bool prayerPortal = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - Tag = tag; - Name = name; - Scene = scene; - Region = region; - RequiredItems = requiredItems; - RequiredItemsOr = requiredItemsOr; - EntryItems = entryItems; - GivesAccess = givesAccess; - DeadEnd = deadEnd; - PrayerPortal = prayerPortal; - OneWay = oneWay; - IgnoreScene = ignoreScene; - SceneDestinationTag = (Scene + ", " + Destination + "_" + Tag); - } - - public bool CanReachCenterFromPortal(List inventory) { - // if ignore scene is set, we don't care about this function since we give the region elsewhere - if (this.IgnoreScene == true) { - return false; - } - - // create our list of dicts of required items - List> itemsRequired = new List>(); - if (this.RequiredItems != null) { - if (this.RequiredItems.Count != 0) { - itemsRequired.Add(new Dictionary(this.RequiredItems)); - } - } else if (this.RequiredItemsOr != null) { - if (this.RequiredItemsOr.Count != 0) { - foreach (Dictionary reqSet in this.RequiredItemsOr) { - itemsRequired.Add(reqSet); - } - } - } - - // see if we meet any of the requirement dicts for the portal - if (itemsRequired != null) { - // if there are no required items, we can reach the center of the region without items (can just walk there) - if (itemsRequired.Count == 0) { - return true; - } - foreach (Dictionary req in itemsRequired) { - //ensure req and items use same terms - if (SaveFile.GetInt("randomizer sword progression enabled") != 0) { - if (req.ContainsKey("Stick")) { - req["Sword Progression"] = 1; - req.Remove("Stick"); - } - if (req.ContainsKey("Sword")) { - req["Sword Progression"] = 2; - req.Remove("Sword"); - } - } - - //check if this requirement is fully met, otherwise move to the next requirement - int met = 0; - foreach (string item in req.Keys) { - if (!inventory.Contains(item)) { - break; - } else { - met += 1; - } - } - if (met == req.Count) { - return true; - } - } - } else { + public bool CanReach(Dictionary inventory) { + if (inventory.ContainsKey(this.Region)) { return true; + } else { + return false; } - - return false; - } - - public List Rewards(List inventory) { - List rewardsList = new List(); - - // if you can reach, you get the center of the region. One-ways give you the center too - if (CanReachCenterFromPortal(inventory) || this.OneWay == true) { - rewardsList.Add(this.Scene); - } - - if (this.Region == "Swamp Back") { - rewardsList.Add("Swamp Back"); - } else if (this.Region == "Forest Belltower Upper") { - rewardsList.Add("Forest Belltower Upper"); - rewardsList.Add("Forest Belltower Main"); - rewardsList.Add("Forest Belltower Lower"); - } else if (this.Region == "Forest Belltower Main") { - rewardsList.Add("Forest Belltower Main"); - rewardsList.Add("Forest Belltower Lower"); - } else if (this.Region == "Forest Belltower Lower") { - rewardsList.Add("Forest Belltower Lower"); - } - - return rewardsList; } - } } diff --git a/src/Data/PortalCombo.cs b/src/Data/PortalCombo.cs index 8f459f8..6943021 100644 --- a/src/Data/PortalCombo.cs +++ b/src/Data/PortalCombo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace TunicArchipelago { public class PortalCombo { @@ -20,5 +21,29 @@ public PortalCombo(Portal portal1, Portal portal2) { }; } + public bool CanReach(Dictionary inventory) { + if (inventory.ContainsKey(this.Portal1.Region) || inventory.ContainsKey(this.Portal2.Region)) { + return true; + } else { + return false; + } + } + + public Dictionary AddComboRegions(Dictionary inventory) { + // if both regions are in here already, just skip it + if (inventory.ContainsKey(this.Portal1.Region) && inventory.ContainsKey(this.Portal2.Region)) { + return inventory; + } + // if you can reach this portal combo, add whichever region you don't have yet + if (this.CanReach(inventory)) { + if (!inventory.ContainsKey(this.Portal1.Region)) { + inventory.Add(this.Portal1.Region, 1); + } + if (!inventory.ContainsKey(this.Portal2.Region)) { + inventory.Add(this.Portal2.Region, 1); + } + } + return inventory; + } } } diff --git a/src/Data/RandomizerSettings.cs b/src/Data/RandomizerSettings.cs index e5ad9b5..3d16b93 100644 --- a/src/Data/RandomizerSettings.cs +++ b/src/Data/RandomizerSettings.cs @@ -10,6 +10,87 @@ public class RandomizerSettings { public ConnectionSettings ConnectionSettings { get; set; } + public enum RandomizerType { + SINGLEPLAYER, + ARCHIPELAGO + } + + public RandomizerType Mode { + get; + set; + } + + // Single Player Settings + public GameModes GameMode { + get; + set; + } + + public bool KeysBehindBosses { + get; + set; + } + + public bool StartWithSwordEnabled { + get; + set; + } + + public bool SwordProgressionEnabled { + get; + set; + } + + public bool ShuffleAbilities { + get; + set; + } + + public bool EntranceRandoEnabled { + get; + set; + } + + public bool ERFixedShop { + get; + set; + } + + public int HexagonQuestGoal { + get; + set; + } + + public int HexagonQuestExtraPercentage { + get; + set; + } + + public bool Lanternless { + get; + set; + } + + public bool Maskless { + get; + set; + } + + public FixedLaurelsType FixedLaurelsOption { + get; + set; + } + + public FoolTrapOption FoolTrapIntensity { + get; + set; + } + + public bool MysterySeed { + get; + set; + } + // Archipelago Settings public bool DeathLinkEnabled { get; @@ -52,6 +133,16 @@ public bool ChestsMatchContentsEnabled { set; } + public bool UseTrunicTranslations { + get; + set; + } + + public bool CreateSpoilerLog { + get; + set; + } + // Gameplay Settings public bool HeirAssistModeEnabled { get; @@ -114,6 +205,36 @@ public bool ExtraEnemiesEnabled { set; } + // Race Mode Settings + public bool RaceMode { + get; + set; + } + + public bool DisableIceboltInHeirFight { + get; + set; + } + + public bool DisableDistantBellShots { + get; + set; + } + + public bool DisableIceGrappling { + get; + set; + } + + public bool DisableLadderStorage { + get; + set; + } + + public bool DisableUpgradeStealing { + get; + set; + } // Fox Settings public bool RandomFoxColorsEnabled { @@ -131,6 +252,12 @@ public bool UseCustomTexture { set; } + public enum GameModes { + RANDOMIZER, + HEXAGONQUEST, + VANILLA + } + public enum FoolTrapOption { NONE, NORMAL, @@ -148,24 +275,49 @@ public enum EnemyRandomizationType { BALANCED } + public enum FixedLaurelsType { + RANDOM, + SIXCOINS, + TENCOINS, + TENFAIRIES, + } + public RandomizerSettings() { ConnectionSettings = new ConnectionSettings(); -/* GameMode = GameModes.RANDOMIZER; + Mode = RandomizerType.SINGLEPLAYER; + + // Single Player + GameMode = GameModes.RANDOMIZER; KeysBehindBosses = false; SwordProgressionEnabled = true; StartWithSwordEnabled = false; - ShuffleAbilities = false;*/ + ShuffleAbilities = false; + EntranceRandoEnabled = false; + ERFixedShop = false; + HexagonQuestGoal = 20; + HexagonQuestExtraPercentage = 50; + FixedLaurelsOption = FixedLaurelsType.RANDOM; + FoolTrapIntensity = FoolTrapOption.NORMAL; + Lanternless = false; + Maskless = false; + MysterySeed = false; + + // Archipelago DeathLinkEnabled = false; CollectReflectsInWorld = false; SkipItemAnimations = false; SendHintsToServer = false; + // Hints HeroPathHintsEnabled = true; GhostFoxHintsEnabled = true; ShowItemsEnabled = true; ChestsMatchContentsEnabled = true; + UseTrunicTranslations = false; + CreateSpoilerLog = true; + // General HeirAssistModeEnabled = false; ClearEarlyBushes = false; CheaperShopItemsEnabled = true; @@ -175,11 +327,21 @@ public RandomizerSettings() { MoreSkulls = false; ArachnophobiaMode = false; + // Enemy Randomizer EnemyRandomizerEnabled = false; EnemyDifficulty = EnemyRandomizationType.BALANCED; EnemyGeneration = EnemyGenerationType.SEEDED; ExtraEnemiesEnabled = false; + // Race Settings + RaceMode = false; + DisableIceboltInHeirFight = false; + DisableDistantBellShots = false; + DisableIceGrappling = false; + DisableLadderStorage = false; + DisableUpgradeStealing = false; + + // Fox Customization RandomFoxColorsEnabled = true; RealestAlwaysOn = false; UseCustomTexture = false; diff --git a/src/Data/SaveFlags.cs b/src/Data/SaveFlags.cs index 223ccbf..cb2dfff 100644 --- a/src/Data/SaveFlags.cs +++ b/src/Data/SaveFlags.cs @@ -8,7 +8,7 @@ public class SaveFlags { public const string HexagonQuestEnabled = "randomizer hexagon quest enabled"; public const string HexagonQuestPrayer = "randomizer hexagon quest prayer requirement"; public const string HexagonQuestHolyCross = "randomizer hexagon quest holy cross requirement"; - public const string HexagonQuestIceRod = "randomizer hexagon quest ice rod requirement"; + public const string HexagonQuestIcebolt = "randomizer hexagon quest icebolt requirement"; public const string GoldHexagonQuantity = "inventory quantity Hexagon Gold"; public const string HexagonQuestGoal = "randomizer hexagon quest goal"; @@ -16,10 +16,10 @@ public class SaveFlags { public const string AbilityShuffle = "randomizer shuffled abilities"; public const string PrayerUnlocked = "randomizer prayer unlocked"; public const string HolyCrossUnlocked = "randomizer holy cross unlocked"; - public const string IceRodUnlocked = "randomizer ice rod unlocked"; + public const string IceBoltUnlocked = "randomizer icebolt unlocked"; public const string PrayerUnlockedTime = "randomizer prayer unlocked time"; public const string HolyCrossUnlockedTime = "randomizer holy cross unlocked time"; - public const string IceRodUnlockedTime = "randomizer ice rod unlocked time"; + public const string IceboltUnlockedTime = "randomizer icebolt unlocked time"; // Keys Behind Bosses Flag public const string KeysBehindBosses = "randomizer keys behind bosses"; @@ -31,10 +31,22 @@ public class SaveFlags { // Entrance Rando Flag public const string EntranceRando = "randomizer entrance rando enabled"; + // Other Logic Flags + public const string MasklessLogic = "randomizer maskless logic enabled"; + public const string LanternlessLogic = "randomizer lanternless logic enabled"; + // Special Flags public const string PlayerDeathCount = "randomizer death count"; public const string EnemiesDefeatedCount = "randomizer enemies defeated"; public const string DiedToHeir = "randomizer died to heir"; public const string RescuedLostFox = "randomizer sent lost fox home"; + + public static bool IsArchipelago() { + return SaveFile.GetInt("archipelago") == 1 && (Archipelago.instance != null && Archipelago.instance.integration != null && Archipelago.instance.integration.connected); + } + + public static bool IsSinglePlayer() { + return SaveFile.GetInt("randomizer") == 1; + } } } diff --git a/src/Data/Translations.cs b/src/Data/Translations.cs new file mode 100644 index 0000000..82ceaa7 --- /dev/null +++ b/src/Data/Translations.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TunicArchipelago { + public class Translations { + + public static string Translate(string Input, bool CheckSetting) { + + return ((CheckSetting ? TunicArchipelago.Settings.UseTrunicTranslations : true) && EnglishToTrunic.ContainsKey(Input)) ? EnglishToTrunic[Input] : Input.StartsWith($"\"") ? Input : $"\"{Input}\""; + } + + public static string TranslateDefaultNoQuotes(string Input) { + + return (TunicArchipelago.Settings.UseTrunicTranslations && EnglishToTrunic.ContainsKey(Input)) ? EnglishToTrunic[Input] : Input; + } + + public static string TranslateDefaultNoQuotes(string Input, bool CheckSetting) { + + return ((CheckSetting ? TunicArchipelago.Settings.UseTrunicTranslations : true) && EnglishToTrunic.ContainsKey(Input)) ? EnglishToTrunic[Input] : Input; + } + + public static string TranslateDefaultQuotes(string Input) { + return (TunicArchipelago.Settings.UseTrunicTranslations && EnglishToTrunic.ContainsKey(Input)) ? EnglishToTrunic[Input] : $"\"{Input}\""; + } + + public static Dictionary EnglishToTrunic = new Dictionary() { + {"Firecracker", "fIurkrahkur"}, + {"Fire Bomb", "fIur bawm"}, + {"Ice Bomb", "Is bawm"}, + {"Lure", "lour"}, + {"Pepper", "pehpur"}, + {"Ivy", "IvE"}, + {"Effigy", "ehfijE"}, + {"Money", "muhnE"}, + {"HP Berry", "Aj pE bArE"}, + {"MP Berry", "ehm pE bArE"}, + {"Fool Trap", "fool trahp"}, + {"Stick", "stik"}, + {"Sword", "sord"}, + {"Sword Upgrade", "sord uhpgrAd"}, + {"Magic Dagger", "mahjik dahgur"}, + {"Magic Wand", "mahjik wawnd"}, + {"Magic Orb", "mahjik orb"}, + {"Hero's Laurels", "hErOz loruhlz"}, + {"Lantern", "lahnturn"}, + {"Shotgun", "$awtguhn"}, + {"Gun", "guhn"}, + {"Shield", "$Eld"}, + {"Dath Stone", "dah% stOn"}, + {"Hourglass", "owurglahs"}, + {"Old House Key", "Old hows kE"}, + {"Key", "kE"}, + {"Fortress Vault Key", "fOrtris vawlt kE"}, + {"Flask Shard", "flahsk #Rd"}, + {"Potion Flask", "pO$uhn flahsk"}, + {"Golden Coin", "gOldin koin"}, + {"Card Slot", "kRd slawt"}, + {"Red Hexagon", "rehd kwehstuhgawn"}, + {"Green Hexagon", "grEn kwehstuhgawn"}, + {"Blue Hexagon", "bloo kwehstuhgawn"}, + {"Gold Hexagon", "gOld kwehstuhgawn"}, + {"ATT Offering", "uhtahk awfuri^"}, + {"DEF Offering", "difehns awfuri^"}, + {"Potion Offering", "pO$uhn awfuri^"}, + {"HP Offering", "Aj pE awfuri^"}, + {"SP Offering", "ehs pE awfuri^"}, + {"MP Offering", "ehm pE awfuri^"}, + {"Hero Relic - ATT", $"hErO rehlik \"-\" uhtahk"}, + {"Hero Relic - DEF", $"hErO rehlik \"-\" difehns"}, + {"Hero Relic - POTION", $"hErO rehlik \"-\" pO$uhn"}, + {"Hero Relic - HP", $"hErO rehlik \"-\" Aj pE"}, + {"Hero Relic - SP", $"hErO rehlik \"-\" ehs pE"}, + {"Hero Relic - MP", $"hErO rehlik \"-\" ehm pE"}, + {"Orange Peril Ring", "oruhnj pAruhl ri^"}, + {"Tincture", "ti^kJur"}, + {"Scavenger Mask", "skahvuhnjur mahsk"}, + {"Cyan Peril Ring", "sIahn pAruhl ri^"}, + {"Bracer", "brAsur"}, + {"Dagger Strap", "dahgur strahp"}, + {"Inverted Ash", "invurtid ah$"}, + {"Lucky Cup", "luhkE kuhp"}, + {"Magic Echo", "mahjik ehkO"}, + {"Anklet", "ah^klit"}, + {"Muffling Bell", "muhfli^ behl"}, + {"Glass Cannon", "glahs kahnuhn"}, + {"Perfume", "purfyoom"}, + {"Louder Echo", "lowdur ehkO"}, + {"Aura's Gem", "oruhz jehm"}, + {"Bone Card", "bOn kRd"}, + {"Fairy", "fArE"}, + {"Mr Mayor", "mistur mAor"}, + {"Secret Legend", "sEkrit lehjehnd"}, + {"Sacred Geometry", "sAkrid jEawmuhtrE"}, + {"Vintage", "vintij"}, + {"Just Some Pals", "juhst suhm pahlz"}, + {"Regal Weasel", "rEguhl wEzuhl"}, + {"Spring Falls", "spri^ fawlz"}, + {"Power Up", "powur uhp"}, + {"Back To Work", "bahk too wurk"}, + {"Phonomath", "fOnuhmah%"}, + {"Dusty", "duhstE"}, + {"Forever Friend", "forehvur frehnd"}, + {"Pages 0-1", $"pAjuhz \"0-1\""}, + {"Pages 2-3", $"pAjuhz \"2-3\""}, + {"Pages 4-5", $"pAjuhz \"4-5\""}, + {"Pages 6-7", $"pAjuhz \"6-7\""}, + {"Pages 8-9", $"pAjuhz \"8-9\""}, + {"Pages 10-11", $"pAjuhz \"10-11\""}, + {"Pages 12-13", $"pAjuhz \"12-13\""}, + {"Pages 14-15", $"pAjuhz \"14-15\""}, + {"Pages 16-17", $"pAjuhz \"16-17\""}, + {"Pages 18-19", $"pAjuhz \"18-19\""}, + {"Pages 20-21", $"pAjuhz \"20-21\""}, + {"Pages 22-23", $"pAjuhz \"22-23\""}, + {"Pages 24-25 (Prayer)", $"pAjuhz \"24-25\" (prAr)"}, + {"Pages 26-27", $"pAjuhz \"26-27\""}, + {"Pages 28-29", $"pAjuhz \"28-29\""}, + {"Pages 30-31", $"pAjuhz \"30-31\""}, + {"Pages 32-33", $"pAjuhz \"32-33\""}, + {"Pages 34-35", $"pAjuhz \"34-35\""}, + {"Pages 36-37", $"pAjuhz \"36-37\""}, + {"Pages 38-39", $"pAjuhz \"38-39\""}, + {"Pages 40-41", $"pAjuhz \"40-41\""}, + {"Pages 42-43 (Holy Cross)", $"pAjuhz \"42-43\" (hOlE kraws)"}, + {"Pages 44-45", $"pAjuhz \"44-45\""}, + {"Pages 46-47", $"pAjuhz \"46-47\""}, + {"Pages 48-49", $"pAjuhz \"48-49\""}, + {"Pages 50-51", $"pAjuhz \"50-51\""}, + {"Pages 52-53 (Icebolt)", $"pAjuhz \"52-53\" (IsbOlt)"}, + {"Pages 54-55", $"pAjuhz \"54-55\""}, + {"Overworld", "Ovurwurld"}, + {"West Furnace", "wehst furnis"}, + {"Cube Cave", "kyoob kAv"}, + {"Stick House", "stik hows"}, + {"Windmill", "windmil"}, + {"Southeast Cross Room", "sow%Est kraws room"}, + {"Caustic Light Cave", "kawstik lIt kAv"}, + {"Ruined Passage", "rooind"}, + {"Patrol Cave", "pahsuhj"}, + {"Secret Gathering Place", "sEkrit gah#uri^ plAs"}, + {"Fountain Cross Room", "fowntin kraws room"}, + {"Hourglass Cave", "owurglahs kAv"}, + {"Maze Cave", "mAz kAv"}, + {"Ruined Shop", "rooind $awp"}, + {"Changing Room", "JAnji^ room"}, + {"Special Shop", "speh$uhl $awp"}, + {"Old House", "Old hows"}, + {"Far Shore", "fR $or"}, + {"Sealed Temple", "sEld tehmpuhl"}, + {"Shop", "$awp"}, + {"Coins in the Well", "koinz in #uh wehl"}, + {"Forest Belltower", "foruhst behltowur"}, + {"East Forest", "Est foruhst"}, + {"Forest Grave Path", "foruhst grAv pah%"}, + {"Guardhouse 2", "gRdhows 2"}, + {"Guardhouse 1", "gRdhows 1"}, + {"Forest Boss Room", "forist baws room"}, + {"Bottom of the Well", "bawtuhm uhv #uh wehl"}, + {"Dark Tomb Checkpoint", "dRk toom Jehkpoint"}, + {"Dark Tomb", "dRk toom"}, + {"West Garden", "wehst gRdin"}, + {"West Garden House", "wehst gRdin hows"}, + {"Ruined Atoll", "rooind ahtawl"}, + {"Frog Stairway", "frawg stArwA"}, + {"Frog's Domain", "frawgz dOmAn"}, + {"Library Exterior", "lIbrArE ehkstErEur"}, + {"Library Hall", "lIbrArE hawl"}, + {"Library Rotunda", "lIbrArE rOtuhnduh"}, + {"Library Lab", "lIbrArE lahb"}, + {"Librarian", "lIbrArEuhn"}, + {"Beneath the Fortress", "bEnE% #uh fortruhs"}, + {"Eastern Vault Fortress", "Esturn vawlt fortruhs"}, + {"Fortress East Shortcut", "fortruhs Est $ortkuht"}, + {"Fortress Grave Path", "fortruhs grAv pah%"}, + {"Fortress Courtyard", "fortruhs kortyRd"}, + {"Fortress Leaf Piles", "fortruhs lEf pIlz"}, + {"Fortress Arena", "fortruhs uhrEnuh"}, + {"Lower Mountain", "lOur mowntin"}, + {"Top of the Mountain", "tawp uhv #uh mowntin"}, + {"Quarry Entryway", "kworE ehntrEwA"}, + {"Quarry", "kworE"}, + {"Monastery", "mawnuhstArE"}, + {"Rooted Ziggurat Entrance", "ziguraht ehntruhns"}, + {"Rooted Ziggurat Upper", "uhpur ziguraht"}, + {"Rooted Ziggurat Tower", "ziguraht towur"}, + {"Rooted Ziggurat Lower", "lOur ziguraht"}, + {"Rooted Ziggurat Teleporter", "ziguraht tehluhportur"}, + {"Swamp", "swawmp"}, + {"Cathedral", "kuh%Edruhl"}, + {"Cathedral Gauntlet", "kuh%Edruhl gawntluht"}, + {"Hero's Grave", "hErOz grAv"}, + {"Glyph Tower", "glif towur"}, + {"The Heir", "#uh Ar"}, + {"Purgatory", "purguhtorE"}, + {"Your Pocket", "yor pawkit"}, + {"FOUR SKULLS", "for skuhlz"}, + {"EAST FOREST SLIME", "Est foruhst slIm"}, + {"CATHEDRAL GAUNTLET", "kuh%Edruhl gawntluht"}, + {"SIEGE ENGINE", "sEj ehnhjuhn"}, + {"LIBRARIAN", "lIbrArEuhn"}, + {"SCAVENGER BOSS", "skahvuhnjur baws"}, + {"VAULT KEY PLINTH", "vawlt kE plin%"}, + {"20 FAIRIES", "20 fArEz"}, + {"10 COIN TOSSES", "10 koin tawsiz"}, + {"15 COIN TOSSES", "15 koin tawsiz"}, + {"PILES OF LEAVES", "pIlz uhv lEvs"}, + {"WEST GARDEN TREE", "wehst gRdin trE"}, + {"TOP OF THE MOUNTAIN", "tawp uhv #uh mowntin"}, + {"BONUS CUSTOMIZATION Unlocked", "bOnuhs kuhstuhmizA$uhn uhnlawkd"}, + {"PRAYER Unlocked", "prAr uhnlawkd"}, + {"HOLY CROSS Unlocked", "hOlE kraws uhnlawkd"}, + {"ICEBOLT Unlocked", "IsbOlt uhnlawkd"}, + {"\"Blob\"", "blawb"}, + {"\"Hedgehog\"", "hehjhawg"}, + {"\"Rudeling\"", "roodli^"}, + {"\"Plover\"", "plOvur"}, + {"\"Baby Slorm\"", "bAbE slorm"}, + {"\"Crabbit\"", "krahbit"}, + {"gAmkyoob \"Crabbit\"", "gAmkyoob krahbit"}, + {"yoo...\"?\"", "yoo...?"}, + {"\"Blob\" (big)", "blawb (big)"}, + {"\"Blob\" (bigur)", "blawb (bigur)"}, + {"\"Hedgehog\" (big)", "hehjhawg (big)"}, + {"\"Phrend\"", "frehnd"}, + {"\"Spyrite\"", "spIrIt"}, + {"\"Fleemer\"", "flEmur"}, + {"\"Fairy\" (Is)", "fArE (Is)"}, + {"\"Fairy\" (fIur)", "fArE (fIur)"}, + {"\"Rudeling\" ($Eld)", "roodli^ ($Eld)"}, + {"\"Crabbo\"", "krahbO"}, + {"\"Slorm\"", "slorm"}, + {"\"Autobolt\"", "awtObOlt"}, + {"\"Laser Turret\"", "lAzur turit"}, + {"\"Administrator\" (frehnd)", "ahdminuhstrAtur (frehnd)"}, + {"slorm...\"?\"", "slorm...?"}, + {"\"???\"", "???"}, + {"\"Custodian\" (kahnduhlahbruh)", "kuhstOdEuhn (kahnduhlahbruh)"}, + {"\"Tentacle\"", "tehntuhkuhl"}, + {"\"Envoy\"", "awnvoi"}, + {"\"Guard Captain\"", "gRd kahptuhn"}, + {"\"Husher\"", "huh$ur"}, + {"\"Terry\"", "tArE"}, + {"\"Terry\" (void)", "tArE (void)"}, + {"\"Scavenger\" (snIpur)", "skahvuhnjur (snIpur)"}, + {"\"Scavenger\" (mInur)", "skahvuhnjur (mInur)"}, + {"\"Scavenger\" (suhport)", "skahvuhnjur (suhport)"}, + {"\"Scavenger\" (stuhnur)", "skahvuhnjur (stuhnur)"}, + {"\"Fleemer\" (fehnsur)", "flEmur (fehnsur)"}, + {"\"Lost Echo\"", "lawst ehkO"}, + {"\"Voidling\"", "voidli^"}, + {"\"Frog\" (spEr) [frog]", "frawg (spEr) [frog]"}, + {"\"Frog\" [frog]", "frawg [frog]"}, + {"\"Frog\" (smawl) [frog]", "frawg (smawl) [frog]"}, + {"\"Sappharach\"", "sahfuhrahk"}, + {"\"Custodian\"", "kuhstOdEuhn"}, + {"\"Custodian\" (suhport)", "kuhstOdEuhn (suhport)"}, + {"\"Husher\" (void)", "huh$ur (void)"}, + {"\"Woodcutter\"", "woudkuhtur"}, + {"\"You...?\"", "yoo...?"}, + {"\"Administrator\"", "ahdminuhstrAtur"}, + {"\"Gunslinger\"", "guhnsli^ur"}, + {"\"Beefboy\"", "bEfboi"}, + {"\"Fleemer\" (lRj)", "flEmur (lRj)"}, + {"\"Garden Knight...?\"", "gRdin nIt...?"}, + {"gRdin nIt...\"?\"", "gRdin nIt...?"}, + {"\"Voidtouched\"", "voidtuhJd"}, + {"\"Centipede\"", "sehntipEd"}, + {"\"Shadowreaper\"", "$ahdOrEpur"}, + {"\"Phrend\" (void)", "frehnd (void)"}, + {"\"Rudeling\" (void)", "roodli^ (void)"}, + {"\"Frog...?\" (smawl) [frog]", "frawg...? (smawl) [frog]"}, + {"\"Frog...?\" (spEr) [frog]", "frawg...? (spEr) [frog]"}, + {"\"Fairy...?\"", "fArE...?"}, + {"\"Fleemer...?\"", "flEmur...?"}, + {"\"Fleemer...?\" (lRj)", "flEmur...? (lRj)"}, + {"\"Custodian...?\" (suhport)", "kuhstOdEuhn...? (suhport)"}, + {"\"Rudeling...?\"", "roodli^...?"}, + {"\"Rudeling...?\" ($Eld)", "roodli^...? ($Eld)"}, + {"\"Guard Captain...?\"", "gRd kahptuhn...?"}, + {$"kawngrahJoulA$uhnz! \"(<#e99d4c>+1 ATT<#FFFFFF>)\"", $"kawngrahJoulA$uhnz! (<#e99d4c>[arrow_up]1 uhtahk<#FFFFFF>)"}, + {$"kawngrahJoulA$uhnz! \"(<#5de7cf>+1 DEF<#FFFFFF>)\"", $"kawngrahJoulA$uhnz! (<#5de7cf>[arrow_up]1 difehns<#FFFFFF>)"}, + {$"kawngrahJoulA$uhnz! \"(<#ca7be4>+1 POTION<#FFFFFF>)\"", $"kawngrahJoulA$uhnz! (<#ca7be4>[arrow_up]1 pO$uhn<#FFFFFF>)"}, + {$"kawngrahJoulA$uhnz! \"(<#f03c67>+1 HP<#FFFFFF>)\"", $"kawngrahJoulA$uhnz! (<#f03c67>[arrow_up]1 Aj pE<#FFFFFF>)"}, + {$"kawngrahJoulA$uhnz! \"(<#8ddc6e>+1 SP<#FFFFFF>)\"", $"kawngrahJoulA$uhnz! (<#8ddc6e>[arrow_up]1 ehs pE<#FFFFFF>)"}, + {$"kawngrahJoulA$uhnz! \"(<#2a8fed>+1 MP<#FFFFFF>)\"", $"kawngrahJoulA$uhnz! (<#2a8fed>[arrow_up]1 ehm pE<#FFFFFF>)"}, + {$"\"Hero Relic - <#e99d4c>ATT\"", $"hErO rehlik \"-\" <#e99d4c>uhtahk"}, + {$"\"Hero Relic - <#5de7cf>DEF\"", $"hErO rehlik \"-\" <#5de7cf>difehns"}, + {$"\"Hero Relic - <#ca7be4>POTION\"", $"hErO rehlik \"-\" <#ca7be4>pO$uhn"}, + {$"\"Hero Relic - <#f03c67>HP\"", $"hErO rehlik \"-\" <#f03c67>Aj pE"}, + {$"\"Hero Relic - <#8ddc6e>SP\"", $"hErO rehlik \"-\" <#8ddc6e>ehs pE"}, + {$"\"Hero Relic - <#2a8fed>MP\"", $"hErO rehlik \"-\" <#2a8fed>ehm pE"}, + }; + } +} diff --git a/src/Data/VanillaCheck.cs b/src/Data/VanillaCheck.cs deleted file mode 100644 index b520e51..0000000 --- a/src/Data/VanillaCheck.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TunicArchipelago { - - public struct VanillaLocation { - public string LocationId; - public string Position; - public List> RequiredItems; - public List> RequiredItemsDoors; - public int SceneId; - public string SceneName; - } - public struct VanillaReward { - public int Amount; - public string Name; - public string Type; - } - public class VanillaCheck { - public VanillaLocation Location; - public VanillaReward Reward; - - public VanillaCheck() { } - - public VanillaCheck(VanillaLocation location, VanillaReward reward) { - Location = location; - Reward = reward; - } - } -} diff --git a/src/Patches/CustomItemBehaviors.cs b/src/Patches/CustomItemBehaviors.cs index f83a863..646b9e1 100644 --- a/src/Patches/CustomItemBehaviors.cs +++ b/src/Patches/CustomItemBehaviors.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; +using UnityEngine.SceneManagement; using static TunicArchipelago.SaveFlags; namespace TunicArchipelago { @@ -166,6 +167,29 @@ public static bool BoneItemBehavior_confirmBoneUseCallback_PrefixPatch(BoneItemB return true; } + public static bool ForcewandItemBehaviour__throwBeamCoroutine_d__32_MoveNext_PrefixPatch(ForcewandItemBehaviour._throwBeamCoroutine_d__32 __instance) { + + // Checks if the toggle for disabling ice grappling is on, and if target is a frozen enemy + if (__instance.__8__1 != null && __instance.__8__1.gameObjectToTether != null + && __instance.__8__1.gameObjectToTether.GetComponent() != null && __instance.__8__1.gameObjectToTether.GetComponent().Frozen + && TunicArchipelago.Settings.RaceMode && TunicArchipelago.Settings.DisableIceGrappling) { + // Allow the east forest slime to be grappled still + if (SceneManager.GetActiveScene().name == "East Forest Redux" && __instance.__8__1.gameObjectToTether.transform.parent != null && __instance.__8__1.gameObjectToTether.transform.parent.name == "_MONSTERS ALWAYS") { + return true; + } + + // Cancel orb animation and refund mp to player if ice grapples are disabled + __instance.__8__1.gameObjectToTether = null; + __instance.__8__1.ztargOfTetheredObject = null; + __instance.__4__this.tentacleBeamRenderer.enabled = false; + + __instance.__4__this.StopAllCoroutines(); + PlayerCharacter.SetMP(PlayerCharacter.GetMP() + __instance.__4__this.usageCostDat.IntValue); + + } + return true; + } + public static void SetupTorchItemBehaviour(PlayerCharacter instance) { instance.GetComponent().item = Inventory.GetItemByName("Dath Stone").TryCast(); List itemBehaviours = instance.itemBehaviours.ToList(); diff --git a/src/Patches/EnemyRandomizer.cs b/src/Patches/EnemyRandomizer.cs index 43393c0..957cf9a 100644 --- a/src/Patches/EnemyRandomizer.cs +++ b/src/Patches/EnemyRandomizer.cs @@ -34,9 +34,33 @@ public class EnemyRandomizer { }; public static List ExcludedScenes = new List() { + "PatrolCave", "Library Arena", "Fortress Arena", - "Spirit Arena" + "Spirit Arena", + }; + + public static List DoNotPlaceCoffeeTableHere = new List() { + "Cathedral Redux Fox enemy zombie (13)", + "Fortress Basement Spider Small (3)", + "Fortress Basement Spider Small (5)", + "Sewer Bat (7)", "Sewer Bat (8)", "Sewer Bat (9)", + "Sewer Bat (10)", "Sewer Bat (11)", "Sewer Bat (12)", + "East Forest Redux Skuladot redux (4)", + "East Forest Redux Skuladot redux (5)", + "East Forest Redux Skuladot redux (6)", + }; + public static List DoNotPlaceTurretHere = new List() { + "Cathedral Redux Fox enemy zombie (13)", + "Fortress Basement Spider Small (3)", + "Fortress Basement Spider Small (5)", + "Sewer Bat (7)", "Sewer Bat (8)", "Sewer Bat (9)", + "Sewer Bat (10)", "Sewer Bat (11)", "Sewer Bat (12)", + "East Forest Redux Skuladot redux (4)", + "East Forest Redux Skuladot redux (5)", + "East Forest Redux Skuladot redux (6)", + "East Forest Redux Spider Small (8)", + "Overworld Redux Honourguard", }; public static Dictionary> LocationEnemies = new Dictionary>() { @@ -136,6 +160,15 @@ public class EnemyRandomizer { "Cathedral Arena", new List() { "tech knight ghost", + "Frog Small_Ghost", + "Frog Spear_Ghost", + "Fairyprobe Archipelagos (Ghost)", + "bomezome_easy_ghost", + "bomezome_easy_ghost (tweaked)", + "Wizard_Support_Ghost", + "Skuladot redux_ghost", + "Skuladot redux_shield_ghost", + "Skuladot redux Big_ghost", } }, { @@ -175,6 +208,13 @@ public class EnemyRandomizer { "administrator_servant" } }, + { + "Library Arena", + new List () { + "Bat_librarian add", + "Skuladot redux_librarian add" + } + }, { "Posterity", new List () { @@ -191,26 +231,30 @@ public class EnemyRandomizer { "Blob", "Hedgehog", "Skuladot redux", + "Skuladot redux void", + "Skuladot redux_ghost", "plover", "Spinnerbot Baby", "Crabbit", - "Crabbit with Shell", "Fox enemy zombie", "BlobBig", "BlobBigger", "HedgehogBig", "Bat", + "Bat void", "Spider Small", "bomezome_easy", + "bomezome_easy_ghost", "Fairyprobe Archipelagos", + "Fairyprobe Archipelagos (Ghost)", "Fairyprobe Archipelagos (Dmg)", "Skuladot redux_shield", - "Crabbo", - "Spinnerbot Corrupted", + "Skuladot redux_shield_ghost", "Turret", "Hedgehog Trap", "administrator_servant", "Phage", + "Spinnerbot Corrupted", } }, { @@ -220,7 +264,9 @@ public class EnemyRandomizer { "sewertentacle", "Honourguard", "Skuladot redux Big", + "Skuladot redux Big_ghost", "Crow", + "Crow Voidtouched", "crocodoo", "crocodoo Voidtouched", "Scavenger", @@ -230,17 +276,22 @@ public class EnemyRandomizer { "bomezome_fencer", "Ghostfox_monster", "voidling redux", - "Frog Spear", "Frog", "Frog Small", + "Frog Small_Ghost", + "Frog Spear", + "Frog Spear_Ghost", "Spider Big", + "Crabbo", + "Crabbit with Shell", "Wizard_Sword", "Wizard_Support", - "Crow Voidtouched", + "Wizard_Support_Ghost", "woodcutter", "Fox enemy", "Centipede", "Ghost Knight", + "bomezome_easy_ghost (tweaked)", } }, { @@ -315,6 +366,17 @@ public class EnemyRandomizer { { "Voidtouched", $"\"Voidtouched\"" }, { "Centipede", $"\"Centipede\"" }, { "Shadowreaper", $"\"Shadowreaper\"" }, + { "Bat void", $"\"Phrend\" (void)" }, + { "Skuladot redux void", $"\"Rudeling\" (void)" }, + { "Frog Small_Ghost", $"\"Frog...?\" (smawl) [frog]" }, + { "Frog Spear_Ghost", $"\"Frog...?\" (spEr) [frog]" }, + { "Fairyprobe Archipelagos (Ghost)", $"\"Fairy...?\"" }, + { "bomezome_easy_ghost", $"\"Fleemer...?\"" }, + { "bomezome_easy_ghost (tweaked)", $"\"Fleemer...?\" (lRj)" }, + { "Wizard_Support_Ghost", $"\"Custodian...?\" (suhport)" }, + { "Skuladot redux_ghost", $"\"Rudeling...?\"" }, + { "Skuladot redux_shield_ghost", $"\"Rudeling...?\" ($Eld)" }, + { "Skuladot redux Big_ghost", $"\"Guard Captain...?\"" }, }; public static void CreateAreaSeeds() { @@ -333,6 +395,8 @@ public static void InitializeEnemies(string SceneName) { { "Hedgehog Trap (1)", "Hedgehog Trap" }, { "Centipede from egg (Varient)", "Centipede" }, { "Spinnerbot (3)", "Spinnerbot Corrupted" }, + { "Bat_librarian add", "Bat void" }, + { "Skuladot redux_librarian add", "Skuladot redux void" }, }; foreach (string LocationEnemy in LocationEnemies[SceneName]) { string EnemyName = LocationEnemy; @@ -360,6 +424,7 @@ public static void InitializeEnemies(string SceneName) { if (EnemyName == "Centipede") { Enemies[EnemyName].GetComponent().maxBeamDistance = 10f; Enemies[EnemyName].GetComponent().attackDistance = 5f; + Enemies[EnemyName].GetComponent().monsterAggroDistance = 20f; } if (EnemyName == "Crabbit with Shell") { Enemies["Crabbit"] = GameObject.Instantiate(Enemies[EnemyName]); @@ -383,6 +448,32 @@ public static void InitializeEnemies(string SceneName) { GameObject.DontDestroyOnLoad(Enemies["Crabbo"]); } + if (EnemyName == "Bat void") { + Enemies[EnemyName].GetComponent().monsterAggroDistance = 4; + } + + if (EnemyName == "Skuladot redux_ghost") { + Enemies[EnemyName].GetComponent().dropValue = Enemies["Skuladot redux"].GetComponent().dropValue; + } + if (EnemyName == "Skuladot redux_shield_ghost") { + Enemies[EnemyName].GetComponent().dropValue = Enemies["Skuladot redux_shield"].GetComponent().dropValue; + } + if (EnemyName == "Skuladot redux Big_ghost") { + Enemies[EnemyName].GetComponent().dropValue = Enemies["Skuladot redux Big"].GetComponent().dropValue; + } + if (EnemyName == "bomezome_easy") { + Enemies["bomezome_easy_ghost"].GetComponent().dropValue = Enemies["bomezome_easy"].GetComponent().dropValue; + } + if (EnemyName == "Frog Small") { + Enemies["Frog Small_Ghost"].GetComponent().dropValue = Enemies["Frog Small"].GetComponent().dropValue; + } + if (EnemyName == "Frog Spear") { + Enemies["Frog Spear_Ghost"].GetComponent().dropValue = Enemies["Frog Spear"].GetComponent().dropValue; + } + if (EnemyName == "Wizard_Support") { + Enemies["Wizard_Support_Ghost"].GetComponent().dropValue = Enemies["Wizard_Support"].GetComponent().dropValue; + } + Enemies[EnemyName].name = EnemyName + " Prefab"; } if (SceneName == "Archipelagos Redux") { @@ -440,6 +531,12 @@ public static void SpawnNewEnemies() { if (CurrentScene == "Forest Boss Room" && GameObject.Find("Skuladot redux") != null) { Monsters.Add(GameObject.Find("Skuladot redux")); } + if (CurrentScene == "Fortress Basement") { + Monsters.AddRange(Resources.FindObjectsOfTypeAll().Where(slorm => slorm.GetComponent() != null && slorm.gameObject.scene.name == CurrentScene && slorm.name == "Spinnerbot Baby").ToList()); + } + if (CurrentScene == "frog cave main") { + Monsters.Add(GameObject.Find("Wizard_Support")); + } if (TunicArchipelago.Settings.ExtraEnemiesEnabled && CurrentScene == "Monastery") { Resources.FindObjectsOfTypeAll().ToList()[0].gameObject.transform.parent = null; } @@ -469,6 +566,7 @@ public static void SpawnNewEnemies() { if (CurrentScene == "ziggurat2020_1" && Enemy.GetComponent() != null) { EnemyKeys.Remove("Hedgehog Trap"); EnemyKeys.Remove("administrator_servant"); + EnemyKeys.Remove("Shadowreaper"); } if (CurrentScene == "Forest Boss Room" && Enemy.GetComponent() != null) { EnemyKeys.Remove("administrator_servant"); @@ -478,10 +576,36 @@ public static void SpawnNewEnemies() { Enemy.name = Enemy.name.Replace("Spinnerbot", "Spinnerbot Corrupted"); } if (TunicArchipelago.Settings.ExtraEnemiesEnabled) { - if (Enemy.transform.parent != null && Enemy.transform.parent.name.Contains("NG+")) { + if (Enemy.transform.parent != null && (Enemy.transform.parent.name.Contains("NG+") || Enemy.transform.parent.name.ToLower().Contains("night"))) { Enemy.transform.parent.gameObject.SetActive(true); } + + if (CurrentScene == "Monastery") { + if (GameObject.Find("_NIGHT/Corruption Blocker/") != null) { + GameObject.Find("_NIGHT/Corruption Blocker/").SetActive(false); + } + if (GameObject.Find("_NIGHT/Corruption Blocker (retreat door)/") != null) { + GameObject.Find("_NIGHT/Corruption Blocker (retreat door)/").SetActive(false); + } + } } + if (DoNotPlaceCoffeeTableHere.Contains($"{CurrentScene} {Enemy.name}")) { + EnemyKeys.Remove("administrator_servant"); + } + if (DoNotPlaceTurretHere.Contains($"{CurrentScene} {Enemy.name}")) { + EnemyKeys.Remove("Turret"); + } + // Make alternate variants of certain enemies slightly less common + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Frog Small" : "Frog Small_Ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Frog Spear" : "Frog Spear_Ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Fairyprobe Archipelagos" : "Fairyprobe Archipelagos (Ghost)"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "bomezome_easy" : "bomezome_easy_ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Wizard_Support" : "Wizard_Support_Ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.50 ? "Skuladot redux void" : "Skuladot redux_ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Skuladot redux_shield" : "Skuladot redux_shield_ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Skuladot redux Big" : "Skuladot redux Big_ghost"); + EnemyKeys.Remove(Random.NextDouble() < 0.25 ? "Bat" : "Bat void"); + string NewEnemyName = ""; if (TunicArchipelago.Settings.EnemyDifficulty == RandomizerSettings.EnemyRandomizationType.RANDOM) { NewEnemy = GameObject.Instantiate(Enemies[EnemyKeys[Random.Next(EnemyKeys.Count)]]); @@ -513,13 +637,7 @@ public static void SpawnNewEnemies() { if (EnemyTypes == null) { NewEnemy = GameObject.Instantiate(Enemies[EnemyKeys[Random.Next(EnemyKeys.Count)]]); } else { - if (CurrentScene == "Cathedral Arena") { - EnemyTypes.Remove("administrator_servant"); - EnemyTypes.Remove("Hedgehog Trap"); - if (Inventory.GetItemByName("Wand").Quantity == 0) { - EnemyTypes.Remove("Crabbit with Shell"); - } - } + EnemyTypes = EnemyTypes.Where(x => EnemyKeys.Contains(x)).ToList(); NewEnemy = GameObject.Instantiate(Enemies[EnemyTypes[Random.Next(EnemyTypes.Count)]]); } } else { @@ -552,7 +670,7 @@ public static void SpawnNewEnemies() { TopLine.text = $"\"Enemy\""; foreach (string Key in ProperEnemyNames.Keys) { if (NewEnemy.name.Replace(" Prefab", "").Replace("(Clone)", "") == Key) { - TopLine.text = ProperEnemyNames[Key]; + TopLine.text = Translations.TranslateDefaultNoQuotes(ProperEnemyNames[Key]); if (NewEnemy.name.Contains("crocodoo")) { BottomLine.text = $"#uh wuhn ahnd OnlE"; } else if (EnemyRankings["Average"].Contains(Key)) { @@ -612,6 +730,10 @@ public static void SpawnNewEnemies() { } } + if (NewEnemy.GetComponent() != null && NewEnemy.name.ToLower().Contains("servant")) { + NewEnemy.GetComponent().extents /= 2; + } + NewEnemy.name += $" {i}"; EnemiesInCurrentScene.Add(NewEnemy.name, NewEnemy.transform.position.ToString()); @@ -709,9 +831,8 @@ private static GameObject CreateRune(string name, Transform parent, Vector3 loca } public static bool Monster_Die_MoveNext_PrefixPatch(Monster._Die_d__77 __instance, ref bool __result) { - if (__instance.__4__this.GetComponent() != null) { + if (__instance.__4__this.GetComponent() != null && IsArchipelago()) { if (SceneManager.GetActiveScene().name == "Forest Boss Room") { - StateVariable.GetStateVariableByName("SV_Forest Boss Room_Skuladot redux Big").BoolValue = true; Archipelago.instance.UpdateDataStorage("Defeated Guard Captain", true); } if (__instance.__4__this.GetComponent() != null) { @@ -728,6 +849,11 @@ public static bool Monster_Die_MoveNext_PrefixPatch(Monster._Die_d__77 __instanc } } + if (SceneLoaderPatches.SceneName == "Forest Boss Room" && __instance.__4__this.GetComponent() != null) { + StateVariable.GetStateVariableByName("SV_Forest Boss Room_Skuladot redux Big").BoolValue = true; + } + + if (__instance.__4__this.GetComponent() != null) { CoinSpawner.SpawnCoins(50, __instance.__4__this.transform.position); MPPickup.Drop(100f, __instance.__4__this.transform.position); diff --git a/src/Patches/FairyTargets.cs b/src/Patches/FairyTargets.cs index c61c831..821abb5 100644 --- a/src/Patches/FairyTargets.cs +++ b/src/Patches/FairyTargets.cs @@ -1,23 +1,34 @@ -using System; +using BepInEx.Logging; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using UnhollowerBaseLib; using UnityEngine; +using UnityEngine.SceneManagement; namespace TunicArchipelago { public class FairyTargets { + public static Il2CppSystem.Collections.Generic.List EntranceTargets = new Il2CppSystem.Collections.Generic.List { }; + public static Il2CppSystem.Collections.Generic.List ItemTargets = new Il2CppSystem.Collections.Generic.List { }; public static void CreateFairyTargets() { + foreach (FairyTarget FairyTarget in Resources.FindObjectsOfTypeAll()) { GameObject.Destroy(FairyTarget); } - if (ItemLookup.ItemList.Count > 0) { - List ItemIdsInScene = Locations.VanillaLocations.Keys.Where(ItemId => Locations.VanillaLocations[ItemId].Location.SceneName == SceneLoaderPatches.SceneName && SaveFile.GetInt($"randomizer picked up {ItemId}") == 0 && (TunicArchipelago.Settings.CollectReflectsInWorld ? SaveFile.GetInt($"randomizer {ItemId} was collected") == 0 : true)).ToList(); + + if (ItemLookup.ItemList.Count > 0 || Locations.RandomizedLocations.Count > 0) { + + List ItemIdsInScene = Locations.VanillaLocations.Keys.Where(ItemId => Locations.VanillaLocations[ItemId].Location.SceneName == SceneManager.GetActiveScene().name + && SaveFile.GetInt($"randomizer picked up {ItemId}") == 0 && + ((SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld) ? SaveFile.GetInt($"randomizer {ItemId} was collected") == 0 : true)).ToList(); + if (ItemIdsInScene.Count > 0) { foreach (string ItemId in ItemIdsInScene) { - VanillaLocation Location = Locations.VanillaLocations[ItemId].Location; + Location Location = Locations.VanillaLocations[ItemId].Location; if (GameObject.Find($"fairy target {ItemId}") == null) { CreateFairyTarget($"fairy target {ItemId}", StringToVector3(Location.Position)); @@ -25,11 +36,16 @@ public static void CreateFairyTargets() { } if (GameObject.FindObjectOfType() != null) { int CoinCount = Inventory.GetItemByName("Trinket Coin").Quantity + TunicArchipelago.Tracker.ImportantItems["Coins Tossed"]; - Dictionary CoinLevels = new Dictionary() { { 0, 3 }, { 1, 6 }, { 2, 10 }, { 3, 15 }, { 4, 20 } }; - int CoinsNeededForNextReward = CoinLevels[Locations.VanillaLocations.Keys.Where(ItemId => Locations.VanillaLocations[ItemId].Location.SceneName == "Trinket Well" && (SaveFile.GetInt($"randomizer picked up {ItemId}") == 1 || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1))).ToList().Count]; + List CoinLevels = new List() { 3, 6, 10, 15, 20 }; + int CoinsNeededForNextReward = 3; + for (int i = 0; i < CoinLevels.Count - 1; i++) { + if (SaveFile.GetInt($"randomizer picked up Well Reward ({CoinLevels[i]} Coins) [Trinket Well]") == 1) { + CoinsNeededForNextReward = CoinLevels[i + 1]; + } + } - if ((Inventory.GetItemByName("Trinket Coin").Quantity + TunicArchipelago.Tracker.ImportantItems["Coins Tossed"]) > CoinsNeededForNextReward) { - CreateFairyTarget($"fairy target Well Reward ({CoinsNeededForNextReward} Coins)", GameObject.FindObjectOfType().transform.position); + if ((Inventory.GetItemByName("Trinket Coin").Quantity + TunicArchipelago.Tracker.ImportantItems["Coins Tossed"]) >= CoinsNeededForNextReward) { + CreateFairyTarget($"fairy target Well Reward ({CoinsNeededForNextReward} Coins) [Trinket Well]", GameObject.FindObjectOfType().transform.position); } } } else { @@ -45,17 +61,28 @@ public static void CreateLoadZoneTargets() { FairyTarget.enabled = false; } - foreach (string ItemId in Locations.VanillaLocations.Keys.Where(itemId => Locations.VanillaLocations[itemId].Location.SceneName != SceneLoaderPatches.SceneName && SaveFile.GetInt($"randomizer picked up {itemId}") == 0 && (TunicArchipelago.Settings.CollectReflectsInWorld ? SaveFile.GetInt($"randomizer {itemId} was collected") == 0 : true))) { + foreach (string ItemId in Locations.VanillaLocations.Keys.Where(itemId => Locations.VanillaLocations[itemId].Location.SceneName != SceneLoaderPatches.SceneName && (SaveFile.GetInt($"randomizer picked up {itemId}") == 0 && + ((SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld) ? SaveFile.GetInt($"randomizer {itemId} was collected") == 0 : true)))) { ScenesWithItems.Add(Locations.VanillaLocations[ItemId].Location.SceneName); } foreach (ScenePortal ScenePortal in Resources.FindObjectsOfTypeAll()) { + if (ScenePortal.id.Contains("heirfasttravel")) { continue; } if (ScenesWithItems.Contains(ScenePortal.destinationSceneName)) { CreateFairyTarget($"fairy target {ScenePortal.destinationSceneName}", ScenePortal.transform.position); } } } + public static void CreateEntranceTargets() { + foreach (ScenePortal ScenePortal in Resources.FindObjectsOfTypeAll()) { + if (ScenePortal.id.Contains("heirfasttravel")) { continue; } + if (ScenePortal.isActiveAndEnabled && SaveFile.GetInt("randomizer entered portal " + ScenePortal.name) != 1) { + CreateFairyTarget($"entrance target {ScenePortal.destinationSceneName}", ScenePortal.transform.position); + } + } + } + private static void CreateFairyTarget(string Name, Vector3 Position) { GameObject FairyTarget = new GameObject(Name); FairyTarget.SetActive(true); @@ -64,6 +91,21 @@ private static void CreateFairyTarget(string Name, Vector3 Position) { FairyTarget.transform.position = Position; } + public static void FindFairyTargets() { + ItemTargets.Clear(); + EntranceTargets.Clear(); + foreach (FairyTarget fairyTarget in Resources.FindObjectsOfTypeAll()) { + if (fairyTarget.isActiveAndEnabled) { + if (fairyTarget.name.StartsWith("entrance")) { + EntranceTargets.Add(fairyTarget); + } else { + ItemTargets.Add(fairyTarget); + } + } + } + FairyTarget.registered = ItemTargets; + } + private static Vector3 StringToVector3(string Position) { Position = Position.Replace("(", "").Replace(")", ""); string[] coords = Position.Split(','); @@ -72,4 +114,34 @@ private static Vector3 StringToVector3(string Position) { } } + + public class EntranceSeekerSpell : FairySpell { + public static List CustomInputs = new List() { }; + + public EntranceSeekerSpell(IntPtr ptr) : base(ptr) { } + + private void Awake() { + base.inputsToCast = new UnhollowerBaseLib.Il2CppStructArray(1L); + + CustomInputs = new List() { DPAD.RIGHT, DPAD.DOWN, DPAD.RIGHT, DPAD.UP, DPAD.LEFT, DPAD.UP }; + } + + public override bool CheckInput(Il2CppStructArray inputs, int length) { + if (length == CustomInputs.Count) { + for (int i = 0; i < length; i++) { + if (inputs[i] != CustomInputs[i]) { + return false; + } + } + DoSpell(); + } + return false; + } + + public void DoSpell() { + FairyTarget.registered = FairyTargets.EntranceTargets; + PlayerCharacter.instance.GetComponent().SpellEffect(); + FairyTarget.registered = FairyTargets.ItemTargets; + } + } } diff --git a/src/Patches/GhostHints.cs b/src/Patches/GhostHints.cs index c4371bf..26ae883 100644 --- a/src/Patches/GhostHints.cs +++ b/src/Patches/GhostHints.cs @@ -4,6 +4,7 @@ using UnityEngine; using BepInEx.Logging; using static TunicArchipelago.SaveFlags; +using static TunicArchipelago.GhostHints; namespace TunicArchipelago { @@ -28,6 +29,7 @@ public class HintGhost { public Quaternion Rotation; public NPC.NPCAnimState AnimState; public string Dialogue; + public string TrunicDialogue; public string Hint; public string HintedItem; public string OptionalCheckID; @@ -41,6 +43,19 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot Position = position; Rotation = rotation; Dialogue = dialogue; + TrunicDialogue = dialogue; + AnimState = animState; + Hint = ""; + HintedItem = ""; + OptionalCheckID = ""; + } + public HintGhost(string name, string sceneName, Vector3 position, Quaternion rotation, NPC.NPCAnimState animState, string dialogue, string trunicDialogue) { + Name = name; + SceneName = sceneName; + Position = position; + Rotation = rotation; + Dialogue = dialogue; + TrunicDialogue = trunicDialogue; AnimState = animState; Hint = ""; HintedItem = ""; @@ -52,7 +67,7 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot public static GameObject GhostFox; - public static List Vowels = new List() { 'A', 'E', 'I', 'O', 'U' }; + public static List Vowels = new List() { 'A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u' }; public static List<(string, string, string)> LocationHints = new List<(string, string, string)>(); public static List<(string, string, string)> ItemHints = new List<(string, string, string)>(); public static List<(string, string, string)> BarrenAndTreasureHints = new List<(string, string, string)>(); @@ -68,7 +83,7 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot { "Vault Key (Red) [Fortress Arena]", "SIEGE ENGINE" }, { "Hexagon Green [Library Arena]", "LIBRARIAN" }, { "Hexagon Blue [ziggurat2020_3]", "SCAVENGER BOSS" }, - { "Hexagon Red [Fortress Arena]", "VAULT KEY" }, + { "Hexagon Red [Fortress Arena]", "VAULT KEY PLINTH" }, { "1007 [Waterfall]", "20 FAIRIES" }, { "Well Reward (10 Coins) [Trinket Well]", "10 COIN TOSSES" }, { "Well Reward (15 Coins) [Trinket Well]", "15 COIN TOSSES" }, @@ -95,6 +110,24 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot "Dath Stone", }; + public static List HintableItemNamesSinglePlayer = new List() { + "Stick", + "Sword", + "Sword Progression", + "Shotgun", + "Shield", + "SlowmoItem", + "Mask", + "Key (House)", + "Relic - Hero Sword", + "Relic - Hero Pendant MP", + "Relic - Hero Water", + "Relic - Hero Pendant HP", + "Relic - Hero Crown", + "Relic - Hero Pendant SP", + "Dath Stone", + }; + public static List BarrenItemNames = new List() { "Firecracker x2", "Firecracker x3", @@ -135,6 +168,19 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot "Money x255", }; + public static List BarrenItemNamesSinglePlayer = new List() { + "Firecracker", + "Ice Bomb", + "Firebomb", + "Pepper", + "Ivy", + "Bait", + "money", + "Piggybank L1", + "Berry_MP", + "Berry_HP" + }; + public static Dictionary> GhostLocations = new Dictionary>() { { "Sword Cave", new List() { new HintGhost("Hint Ghost Sword Cave", "Sword Cave", new Vector3(5.1151f, 0.0637f, 12.6657f), new Quaternion(0f, 0.9642988f, 0f, 0.2648164f), NPC.NPCAnimState.SIT, $"its dAnjuris too gO uhlOn, tAk #is hint:"), } @@ -144,10 +190,10 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot new HintGhost("Hint Ghost Far Shore 2", "Transit", new Vector3(-18.6177f, 8.0314f, -81.6153f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.SIT, "I stoud awn #aht skwAr ahnd ehndid uhp hEr suhmhow.\nwAr igzahktlE R wE?" ) } }, { "Ruined Passage", new List() { - new HintGhost("Hint Ghost Ruins Passage", "Ruins Passage", new Vector3(184.1698f, 17.3268f, 40.54981f), new Quaternion(0f, 0.9659258f, 0f, 0.2588191f), NPC.NPCAnimState.TIRED, $"nahp tIm! haw haw haw... geht it?") } + new HintGhost("Hint Ghost Ruins Passage", "Ruins Passage", new Vector3(184.1698f, 17.3268f, 40.54981f), new Quaternion(0f, 0.9659258f, 0f, 0.2588191f), NPC.NPCAnimState.TIRED, $"nahp tIm! z z z z z z z. . .") } }, { "Windmill", new List() { - new HintGhost("Hint Ghost Windmill", "Windmill", new Vector3(-58.33329f, 54.0833f, -27.8653f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.SIT, $"viziti^ #uh \"SHOPKEEPER?\" doo nawt bE uhlRmd, #A R\nA frehnd.") } + new HintGhost("Hint Ghost Windmill", "Windmill", new Vector3(-58.33329f, 54.0833f, -27.8653f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.SIT, $"viziti^ #uh \"SHOPKEEPER?\" doo nawt bE uhlRmd, #A R\nA frehnd.", $"viziti^ #uh $awpkEpur? doo nawt bE uhlRmd, #A R A frehnd.") } }, { "Old House Back", new List() { new HintGhost("Hint Ghost Overworld Interiors 1", "Overworld Interiors", new Vector3(11.0359f, 29.0833f, -7.3707f), new Quaternion(0f, 0.8660254f, 0f, -0.5000001f), NPC.NPCAnimState.PRAY, $"nuh%i^ wurks! mAbE #Arz suhm trik too #is dor...") } @@ -157,7 +203,7 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot new HintGhost("Hint Ghost Overworld Interiors 3", "Overworld Interiors", new Vector3(12.0368f, 21.1446f, -72.81052f), new Quaternion(0f, 0.8660254f, 0f, -0.5000001f), NPC.NPCAnimState.SIT, $"wuht R #Ez pehduhstuhlz for? doo yoo nO?") } }, { "Overworld Above Ruins", new List() { - new HintGhost("Hint Ghost Overworld Above Ruins 1", "Overworld Redux", new Vector3(28.53184f, 36.0833f, -108.3734f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.IDLE, $"I wuhz hIdi^ fruhm #uh \"SLIMES,\" buht yoo dOnt louk\nlIk wuhn uhv #ehm."), + new HintGhost("Hint Ghost Overworld Above Ruins 1", "Overworld Redux", new Vector3(28.53184f, 36.0833f, -108.3734f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.IDLE, $"I wuhz hIdi^ fruhm #uh \"SLIMES,\" buht yoo dOnt louk\nlIk wuhn uhv #ehm.", $"I wuhz hIdi^ fruhm #uh slImz, buht yoo dOnt louk\nlIk wuhn uhv #ehm."), new HintGhost("Hint Ghost Overworld Above Ruins 2", "Overworld Redux", new Vector3(22.3667f, 27.9833f, -126.3728f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.SIT, $"wAr did I lEv #aht kE..."), new HintGhost("Hint Ghost Overworld Above Ruins 3", "Overworld Redux", new Vector3(51.20462f, 28.00694f, -129.722f), new Quaternion(0f, 1f, 0f, -4.371139E-08f), NPC.NPCAnimState.SIT, $"I %awt #aht Jehst wuhz ehmptE. how suhspi$is.") } }, @@ -166,15 +212,15 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot new HintGhost("Hint Ghost Early Overworld Spawns 2", "Overworld Redux", new Vector3(-34.0649f, 37.9833f, -59.2506f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.GAZE, $"sO mehnE roodli^z. Im stAi^ uhp hEr.") } }, { "Inside Temple", new List() { - new HintGhost("Hint Ghost Inside Temple 1", "Temple", new Vector3(7.067f, -0.224f, 59.9285f), new Quaternion(0f, 1f, 0f, -4.371139E-08f), NPC.NPCAnimState.IDLE, $"yur naht uh \"RUIN SEEKER,\" R yoo? mAbE yoo $oud gO\nsuhmwAr ehls."), - new HintGhost("Hint Ghost Inside Temple 2", "Temple", new Vector3(0.9350182f, 4.076f, 133.7965f), new Quaternion(0f, 0.8660254f, 0f, 0.5f), NPC.NPCAnimState.GAZE_UP, $"yur guhnuh frE \"THE HEIR?\" iznt #aht... bahd?") } + new HintGhost("Hint Ghost Inside Temple 1", "Temple", new Vector3(7.067f, -0.224f, 59.9285f), new Quaternion(0f, 1f, 0f, -4.371139E-08f), NPC.NPCAnimState.IDLE, $"yur naht uh \"RUIN SEEKER,\" R yoo? mAbE yoo $oud gO\nsuhmwAr ehls.", $"yur naht uh rooin sEkur, R yoo? mAbE yoo $oud gO suhmwAr ehls."), + new HintGhost("Hint Ghost Inside Temple 2", "Temple", new Vector3(0.9350182f, 4.076f, 133.7965f), new Quaternion(0f, 0.8660254f, 0f, 0.5f), NPC.NPCAnimState.GAZE_UP, $"yur guhnuh frE \"THE HEIR?\" iznt #aht... bahd?", $"yur guhnuh frE #uh Ar? iznt #aht... bahd?") } }, { "Ruined Shop", new List() { new HintGhost("Hint Ghost Ruined Shop 1", "Ruined Shop", new Vector3(16.5333f, 8.983299f, -45.60382f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.SIT, $"hehlO. wuht iz yor nAm?---...tuhnk? wuht A strAnj nAm."), new HintGhost("Hint Ghost Ruined Shop 2", "Ruined Shop", new Vector3(9.8111f, 8.0833f, -37.52119f), new Quaternion(0f, 0.9659258f, 0f, 0.2588191f), NPC.NPCAnimState.IDLE, $"wehl, if yur nawt bIi^ ehnE%i^..." ) } }, { "West Filigree", new List() { - new HintGhost("Hint Ghost West Filigree", "Town_FiligreeRoom", new Vector3(-79.4348f, 22.0379f, -59.8104f), new Quaternion(0f, 1f, 0f, -4.371139E-08f), NPC.NPCAnimState.PRAY, $"wow, yoo hahv #uh powur uhv #uh \"Holy Cross!\"") } + new HintGhost("Hint Ghost West Filigree", "Town_FiligreeRoom", new Vector3(-79.4348f, 22.0379f, -59.8104f), new Quaternion(0f, 1f, 0f, -4.371139E-08f), NPC.NPCAnimState.PRAY, $"wow, yoo hahv #uh powur uhv #uh \"Holy Cross!\"", $"wow, yoo hahv #uh powur uhv #uh hOlE kraws!") } }, { "East Filigree", new List() { new HintGhost("Hint Ghost East Filigree", "EastFiligreeCache", new Vector3(14.3719f, 0.0167f, -8.8614f), new Quaternion(0f, 0.7071068f, 0f, -0.7071068f), NPC.NPCAnimState.SIT, $"wAt, how did yoo Opehn #aht dOr?") } @@ -206,7 +252,7 @@ public HintGhost(string name, string sceneName, Vector3 position, Quaternion rot new HintGhost("Hint Ghost Furnace", "Furnace", new Vector3(-131.9886f, 12.0833f, -51.0197f), new Quaternion(0f, 0f, 0f, 1f), NPC.NPCAnimState.GAZE_UP, $"#Ez powur sorsehz... I dOnt truhst #ehm.") } }, { "Golden Obelisk", new List() { - new HintGhost("Hint Ghost Golden Obelisk", "Overworld Redux", new Vector3(-94.5973f, 70.0937f, 36.38749f), new Quaternion(0f, 0f, 0f, 1f), NPC.NPCAnimState.FISHING, $"pEpuhl yoost too wur$ip #is. it rehprEzehnts #uh\n\"Holy Cross.\"") } + new HintGhost("Hint Ghost Golden Obelisk", "Overworld Redux", new Vector3(-94.5973f, 70.0937f, 36.38749f), new Quaternion(0f, 0f, 0f, 1f), NPC.NPCAnimState.FISHING, $"pEpuhl yoost too wur$ip #is. it rehprEzehnts #uh\n\"Holy Cross.\"", $"pEpuhl yoost too wur$ip #is. it rehprEzehnts #uh\nhOlE kraws.") } }, { "Overworld Before Garden", new List(){ new HintGhost("Hint Ghost Overworld Before Garden", "Overworld Redux", new Vector3(-146.1464f, 11.6929f, -67.55009f), new Quaternion(0f, 0.3007058f, 0f, 0.9537169f), NPC.NPCAnimState.IDLE, "A vi$is baws blawks #uh wA too #uh behl uhp #Ar.\nbE kArfuhl, it wil kil yoo.") } @@ -282,7 +328,7 @@ public static void SpawnHintGhosts(string SceneName) { NewGhostFox.transform.position = HintGhost.Position; NewGhostFox.transform.rotation = HintGhost.Rotation; LanguageLine HintText = ScriptableObject.CreateInstance(); - HintText.text = $"{HintGhost.Dialogue}---{HintGhost.Hint}"; + HintText.text = $"{(TunicArchipelago.Settings.UseTrunicTranslations ? HintGhost.TrunicDialogue : HintGhost.Dialogue)}---{HintGhost.Hint}"; NewGhostFox.GetComponent().script = HintText; if (PaletteEditor.CelShadingEnabled && PaletteEditor.ToonFox != null) { @@ -383,16 +429,35 @@ public static void GenerateLocationHints() { HintableLocations.Remove("final [Mountaintop]"); } foreach (string Key in HintableLocations) { - ArchipelagoItem Item = ItemLookup.ItemList[Key]; string Location = HintableLocationIds[Key]; string LocationSuffix = Location[Location.Length - 1] == 'S' ? "R" : "iz"; - string ItemPrefix = Item.ItemName.Contains("Money") ? "suhm" : Vowels.Contains(Item.ItemName.ToUpper()[0]) ? "ahn" : "uh"; - string PlayerName = Archipelago.instance.GetPlayerName(Item.Player); - string ItemToDisplay = Archipelago.instance.IsTunicPlayer(Item.Player) && TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(Item.ItemName) - ? TextBuilderPatches.ItemNameToAbbreviation[Item.ItemName] : "[archipelago]"; - string Hint = $"bI #uh wA, I hurd #aht \"{HintableLocationIds[Key].Replace(" ", "\" \"")}\" {LocationSuffix} gRdi^ {ItemToDisplay} \"{PlayerName.ToUpper().Replace(" ", "\" \"")}'S {Item.ItemName.ToUpper().Replace(" ", "\" \"").Replace("_", "\" \"")}.\""; - string ItemForHint = Archipelago.instance.IsTunicPlayer(Item.Player) ? Item.ItemName : "Archipelago Item"; - LocationHints.Add((WordWrapString(Hint), ItemForHint, Locations.LocationIdToDescription[Key])); + + if (SaveFlags.IsArchipelago()) { + ArchipelagoItem Item = ItemLookup.ItemList[Key]; + string ItemPrefix = Item.ItemName.Contains("Money") ? "suhm" : Vowels.Contains(Item.ItemName.ToUpper()[0]) ? "ahn" : "uh"; + string PlayerName = Archipelago.instance.GetPlayerName(Item.Player); + bool IsTunicItem = Archipelago.instance.IsTunicPlayer(Item.Player); + string ItemToDisplay = IsTunicItem && TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(Item.ItemName) + ? TextBuilderPatches.ItemNameToAbbreviation[Item.ItemName] : "[archipelago]"; + string Hint = $"bI #uh wA, I hurd #aht \"{HintableLocationIds[Key].Replace(" ", "\" \"")}\" {LocationSuffix} gRdi^ {ItemToDisplay} \"{PlayerName.ToUpper().Replace(" ", "\" \"")}'S {Item.ItemName.ToUpper().Replace(" ", "\" \"").Replace("_", "\" \"")}.\""; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"bI #uh wA, I hurd #aht {Translations.Translate(HintableLocationIds[Key], false)} {LocationSuffix} gRdi^ \"{PlayerName.ToUpper().Replace(" ", "\" \"")}'S\" {(IsTunicItem ? Translations.Translate(ItemLookup.SimplifiedItemNames[ItemLookup.Items[Item.ItemName].ItemNameForInventory], false) + "." : $"\"{Item.ItemName.ToUpper().Replace(" ", "\" \"").Replace("_", "\" \"")}.\"")}"; + } + string ItemForHint = Archipelago.instance.IsTunicPlayer(Item.Player) ? Item.ItemName : "Archipelago Item"; + LocationHints.Add((WordWrapString(Hint), ItemForHint, Locations.LocationIdToDescription[Key])); + } else if (IsSinglePlayer()) { + Check Check = Locations.RandomizedLocations[Key]; + string ItemName = ItemLookup.GetItemDataFromCheck(Check).Name; + string ItemPrefix = ItemName == "Money" ? "suhm" : Vowels.Contains(ItemName.ToUpper()[0]) ? "ahn" : "uh"; + string ItemToDisplay = TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(ItemName) + ? TextBuilderPatches.ItemNameToAbbreviation[ItemName] : ""; + string Hint = $"bI #uh wA, I hurd #aht \"{HintableLocationIds[Key].Replace(" ", "\" \"")}\" {LocationSuffix} gRdi^ {ItemPrefix} {ItemToDisplay} \"{ItemName.ToUpper().Replace(" ", "\" \"").Replace("_", "\" \"")}.\""; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"bI #uh wA, I hurd #aht {Translations.Translate(HintableLocationIds[Key], false)} {LocationSuffix} gRdi^ {ItemPrefix} {Translations.Translate(ItemLookup.SimplifiedItemNames[Check.Reward.Name], false)}."; + } + + LocationHints.Add((WordWrapString(Hint), ItemName, Locations.LocationIdToDescription[Key])); + } } } @@ -402,34 +467,59 @@ public static void GenerateItemHints() { string Hint = ""; List HintableItems = new List(HintableItemNames); + List HintableItemsSolo = new List(HintableItemNamesSinglePlayer); if (SaveFile.GetInt(AbilityShuffle) == 1) { - HintableItems.Add("Pages 52-53 (Ice Rod)"); + HintableItems.Add("Pages 52-53 (Icebolt)"); + HintableItemsSolo.Add("26"); } for (int i = 0; i < HintableItems.Count; i++) { string Item = HintableItems[i]; - - List ItemLocations = Locations.MajorItemLocations[Item]; - foreach(ArchipelagoHint HintLocation in ItemLocations) { - if (HintLocation.Player == Archipelago.instance.GetPlayerSlot()) { - string Scene = HintLocation.Location == "Your Pocket" ? HintLocation.Location.ToUpper() : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[HintLocation.Location]].Location.SceneName].ToUpper(); - string ScenePrefix = Scene == "Trinket Well" ? "%rOi^" : "aht #uh"; - string ItemToDisplay = TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(HintLocation.Item) ? TextBuilderPatches.ItemNameToAbbreviation[HintLocation.Item] : ""; - Hint = $"bI #uh wA, I saw A {ItemToDisplay} \"{Item.ToUpper().Replace(" ", "\" \"")}\" #uh lahst tIm I wuhs {ScenePrefix} \"{Scene.Replace(" ", "\" \"")}.\""; - - ItemHints.Add((WordWrapString(Hint), HintLocation.Item, "")); + if (SaveFlags.IsArchipelago()) { + List ItemLocations = Locations.MajorItemLocations[Item]; + foreach(ArchipelagoHint HintLocation in ItemLocations) { + if (HintLocation.Player == Archipelago.instance.GetPlayerSlot()) { + string Scene = HintLocation.Location == "Your Pocket" ? HintLocation.Location : Locations.SimplifiedSceneNames[Locations.VanillaLocations[Locations.LocationDescriptionToId[HintLocation.Location]].Location.SceneName]; + string ScenePrefix = Scene == "Trinket Well" ? "%rOi^" : "aht #uh"; + string ItemToDisplay = TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(HintLocation.Item) ? TextBuilderPatches.ItemNameToAbbreviation[HintLocation.Item] : ""; + Hint = $"bI #uh wA, I saw A {ItemToDisplay} \"{Item.ToUpper().Replace(" ", "\" \"")}\" #uh lahst tIm I wuhs {ScenePrefix} \"{Scene.ToUpper().Replace(" ", "\" \"")}.\""; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"bI #uh wA, I saw A {Translations.Translate(Item, false)} #uh lahst tIm I wuhs {ScenePrefix} {Translations.Translate(Scene, false)}."; + } + ItemHints.Add((WordWrapString(Hint), HintLocation.Item, "")); + } + } + } else if (SaveFlags.IsSinglePlayer()) { + List Items = ItemRandomizer.FindAllRandomizedItemsByName(HintableItemsSolo[i]); + foreach (Check Check in Items) { + string ScenePrefix = Check.Location.SceneName == "Trinket Well" ? "%rOi^" : "aht #uh"; + string Scene = Locations.SimplifiedSceneNames[Check.Location.SceneName]; + string TrunicHint = $""; + ItemData ItemData = ItemLookup.GetItemDataFromCheck(Check); + string ItemToDisplay = TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(ItemData.Name) ? TextBuilderPatches.ItemNameToAbbreviation[ItemData.Name] : ""; + + Hint = $"bI #uh wA, I saw A {ItemToDisplay} \"{Item.ToUpper().Replace(" ", "\" \"")}\" #uh lahst tIm I wuhs {ScenePrefix} \"{Scene.ToUpper().Replace(" ", "\" \"")}.\""; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"bI #uh wA, I saw A {Translations.Translate(ItemLookup.SimplifiedItemNames[Check.Reward.Name], false)} #uh lahst tIm I wuhs {ScenePrefix} {Translations.Translate(Scene, false)}."; + } + ItemHints.Add((WordWrapString(Hint), ItemData.Name, "")); } } } if (SaveFile.GetInt(HexagonQuestEnabled) == 1 && SaveFile.GetInt(AbilityShuffle) == 1) { string prayerHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestPrayer)} GOLD QUESTAGONS\"\nwil grahnt yoo #uh powur uhv \"PRAYER.\""; string holyCrossHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestHolyCross)} GOLD QUESTAGONS\"\nwil grahnt yoo #uh powur uhv #uh \"HOLY CROSS.\""; - string iceRodHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestIceRod)} GOLD QUESTAGONS\"\nwil grahnt yoo #uh #uh powur uhv #uh \"ICE ROD.\""; + string iceboltHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestIcebolt)} GOLD QUESTAGONS\"\nwil grahnt yoo #uh #uh powur uhv #uh \"ICEBOLT.\""; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + prayerHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestPrayer)}\" gOld kwehstuhgawn\nwil grahnt yoo #uh powur uhv prAr."; + holyCrossHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestHolyCross)}\" gOld kwehstuhgawn\nwil grahnt yoo #uh powur uhv #uh hOlE kraws."; + iceboltHint = $"bI #uh wA, I hurd #aht [goldhex] \"{SaveFile.GetInt(HexagonQuestIcebolt)}\" gOld kwehstuhgawn\nwil grahnt yoo #uh #uh powur uhv #uh IsbOlt."; + } ItemHints.Add((prayerHint, "", "")); ItemHints.Add((holyCrossHint, "", "")); - ItemHints.Add((iceRodHint, "", "")); + ItemHints.Add((iceboltHint, "", "")); HexQuestHintLookup.Add(prayerHint, "Prayer"); HexQuestHintLookup.Add(holyCrossHint, "Holy Cross"); - HexQuestHintLookup.Add(iceRodHint, "Ice Rod"); + HexQuestHintLookup.Add(iceboltHint, "Icebolt"); } } @@ -466,14 +556,24 @@ public static void GenerateBarrenAndMoneySceneHints() { string Scene = Locations.SimplifiedSceneNames[Key]; int SceneItemCount = 0; int MoneyInScene = 0; - foreach (string ItemKey in ItemLookup.ItemList.Keys.Where(item => Locations.VanillaLocations[item].Location.SceneName == Key).ToList()) { - ArchipelagoItem Item = ItemLookup.ItemList[ItemKey]; - ItemsInScene.Add(Item.ItemName); - APItemsInScene.Add(Item); - if (Item.Player == Archipelago.instance.GetPlayerSlot() && ItemLookup.Items[Item.ItemName].Type == ItemTypes.MONEY) { - MoneyInScene += ItemLookup.Items[Item.ItemName].QuantityToGive; + if (SaveFlags.IsArchipelago()) { + foreach (string ItemKey in ItemLookup.ItemList.Keys.Where(item => Locations.VanillaLocations[item].Location.SceneName == Key).ToList()) { + ArchipelagoItem Item = ItemLookup.ItemList[ItemKey]; + ItemsInScene.Add(Item.ItemName); + APItemsInScene.Add(Item); + if (Item.Player == Archipelago.instance.GetPlayerSlot() && ItemLookup.Items[Item.ItemName].Type == ItemTypes.MONEY) { + MoneyInScene += ItemLookup.Items[Item.ItemName].QuantityToGive; + } + SceneItemCount++; + } + } else if (SaveFlags.IsSinglePlayer()) { + foreach (Check Item in Locations.RandomizedLocations.Values.Where(item => item.Location.SceneName == Key).ToList()) { + ItemsInScene.Add(Item.Reward.Name); + if (Item.Reward.Name == "money") { + MoneyInScene += Item.Reward.Amount; + } + SceneItemCount++; } - SceneItemCount++; } if (SceneItemCount == 0) { @@ -482,7 +582,11 @@ public static void GenerateBarrenAndMoneySceneHints() { if (MoneyInScene >= 200 && SceneItemCount < 10) { string ScenePrefix = Vowels.Contains(Scene[0]) ? "#E" : "#uh"; - BarrenAndTreasureHints.Add(($"ahn EzE plAs too fInd A [realmoney] \"LOT OF MONEY\" iz {ScenePrefix}\n\"{Scene.ToUpper()}.\"", "", "")); + string Hint = $"ahn EzE plAs too fInd A [realmoney] \"LOT OF MONEY\" iz {ScenePrefix}\n\"{Scene.ToUpper()}.\""; + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"ahn EzE plAs too fInd A lawt uhv muhnE iz\n{ScenePrefix} {Translations.Translate(Scene, false)}."; + } + BarrenAndTreasureHints.Add((Hint, "", "")); } else { bool BarrenArea = true; foreach(ArchipelagoItem Item in APItemsInScene) { @@ -494,11 +598,17 @@ public static void GenerateBarrenAndMoneySceneHints() { BarrenArea = false; break; } - if (HintGhosts.Where(HintGhost => HintGhost.Value.SceneName == Key).ToList().Count > 0) { + } + foreach (string Item in ItemsInScene) { + if (!BarrenItemNamesSinglePlayer.Contains(Item)) { BarrenArea = false; break; } } + if (HintGhosts.Where(HintGhost => HintGhost.Value.SceneName == Key).ToList().Count > 0) { + BarrenArea = false; + break; + } if(BarrenArea) { string Hint = ""; if (Scene.Length > 15) { @@ -507,6 +617,9 @@ public static void GenerateBarrenAndMoneySceneHints() { } else { Hint = $"if I wur yoo, I woud uhvoid \"{Scene.ToUpper()}.\"\n#aht plAs iz \"NOT IMPORTANT.\""; } + if (TunicArchipelago.Settings.UseTrunicTranslations) { + Hint = $"if I wur yoo, I woud uhvoid {Translations.Translate(Scene, false)}.\n#aht plAs iz nawt importahnt."; + } BarrenAndTreasureHints.Add((Hint, "", "")); } } diff --git a/src/Patches/InteractionPatches.cs b/src/Patches/InteractionPatches.cs index 8714aa8..c8dca8f 100644 --- a/src/Patches/InteractionPatches.cs +++ b/src/Patches/InteractionPatches.cs @@ -21,12 +21,17 @@ public static bool InteractionTrigger_Interact_PrefixPatch(Item item, Interactio } } + if (GhostHints.HintGhosts.ContainsKey(__instance.name)) { + GhostHints.HintGhost hintGhost = GhostHints.HintGhosts[__instance.name]; + __instance.GetComponent().script.text = $"{(TunicArchipelago.Settings.UseTrunicTranslations ? hintGhost.TrunicDialogue : hintGhost.Dialogue)}---{hintGhost.Hint}"; + } + if (GhostHints.HintGhosts.ContainsKey(__instance.name) && GhostHints.HexQuestHintLookup.ContainsKey(GhostHints.HintGhosts[__instance.name].Hint)) { SaveFile.SetInt($"randomizer hex quest read {GhostHints.HexQuestHintLookup[GhostHints.HintGhosts[__instance.name].Hint]} hint", 1); ItemStatsHUD.UpdateAbilitySection(); } - if (TunicArchipelago.Settings.SendHintsToServer) { + if (IsArchipelago() && TunicArchipelago.Settings.SendHintsToServer) { GhostHints.CheckForServerHint(__instance.name); } } @@ -93,6 +98,10 @@ private static void ChangeDayNightHourglass() { if (isNight) { CycleController.AnimateSunrise(); } else { + SaveFile.SetString("last campfire scene name", "Overworld Interiors"); + SaveFile.SetString("last campfire id", "bed"); + SaveFile.SetString("randomizer last campfire scene name for dath stone", "Overworld Interiors"); + SaveFile.SetString("randomizer last campfire id for dath stone", "bed"); CycleController.AnimateSunset(); } CycleController.IsNight = !isNight; diff --git a/src/Patches/ItemPatches.cs b/src/Patches/ItemPatches.cs index bfeb1d5..1912308 100644 --- a/src/Patches/ItemPatches.cs +++ b/src/Patches/ItemPatches.cs @@ -44,7 +44,11 @@ public static void Chest_openSequence_MoveNext_PostfixPatch(Chest._openSequence_ SaveFile.SetInt($"randomizer opened fairy chest {FairyId}", 1); } Logger.LogInfo("Checking Location: " + LocationId + " - " + Locations.LocationIdToDescription[LocationId]); - Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + if (IsArchipelago()) { + Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + } else if (IsSinglePlayer()) { + GiveItem(Locations.RandomizedLocations[LocationId]); + } } } @@ -53,9 +57,7 @@ public static bool Chest_InterruptOpening_PrefixPatch(Chest __instance) { if (TunicArchipelago.Settings.DisableChestInterruption) { return false; } -/* if (__instance.chestID == 0 || __instance.chestID == 5) { - return false; - }*/ + return true; } @@ -104,7 +106,11 @@ public static bool ItemPickup_onGetIt_PrefixPatch(ItemPickup __instance) { } string LocationId = $"{__instance.itemToGive.name} [{SceneLoaderPatches.SceneName}]"; - Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + if (IsArchipelago()) { + Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + } else if (IsSinglePlayer()) { + GiveItem(Locations.RandomizedLocations[LocationId]); + } __instance.pickupStateVar.BoolValue = true; return false; @@ -112,7 +118,11 @@ public static bool ItemPickup_onGetIt_PrefixPatch(ItemPickup __instance) { public static bool HeroRelicPickup_onGetIt_PrefixPatch(HeroRelicPickup __instance) { string LocationId = $"{__instance.name} [{SceneLoaderPatches.SceneName}]"; - Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + if (IsArchipelago()) { + Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + } else if (IsSinglePlayer()) { + GiveItem(Locations.RandomizedLocations[LocationId]); + } __instance.pickupStateVar.BoolValue = true; __instance.destroyOrDisable(); @@ -121,7 +131,11 @@ public static bool HeroRelicPickup_onGetIt_PrefixPatch(HeroRelicPickup __instanc public static bool PagePickup_onGetIt_PrefixPatch(PagePickup __instance) { string LocationId = $"{__instance.pageName} [{SceneLoaderPatches.SceneName}]"; - Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + if (IsArchipelago()) { + Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + } else if (IsSinglePlayer()) { + GiveItem(Locations.RandomizedLocations[LocationId]); + } SaveFile.SetInt($"unlocked page {PagePickup.LeafNameToLeafNumber(__instance.pageName)}", 1); SaveFile.SetInt($"randomizer picked up page {PagePickup.LeafNameToLeafNumber(__instance.pageName)}", 1); @@ -132,12 +146,22 @@ public static bool ShopItem_IInteractionReceiver_Interact_PrefixPatch(Item i, Sh string LocationId = $"{__instance.name} [Shop]"; if (Locations.LocationIdToDescription.ContainsKey(LocationId)) { int Price = TunicArchipelago.Settings.CheaperShopItemsEnabled ? 300 : __instance.price; - ArchipelagoItem ShopItem = ItemLookup.ItemList[LocationId]; - string itemToDisplay = Archipelago.instance.IsTunicPlayer(ShopItem.Player) && TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(ShopItem.ItemName) ? TextBuilderPatches.ItemNameToAbbreviation[ShopItem.ItemName] : "[archipelago]"; - __instance.confirmPurchaseFormattedLanguageLine.text = $"bI for {Price} [money]?\n {itemToDisplay} " + GhostHints.WordWrapString($"\"{Archipelago.instance.GetPlayerName(ShopItem.Player).ToUpper().Replace(" ", "\" \"")}'S\" \"{ShopItem.ItemName.ToUpper().Replace($" ", $"\" \"")}\""); + string itemToDisplay = ""; + if (IsArchipelago()) { + ArchipelagoItem ShopItem = ItemLookup.ItemList[LocationId]; + itemToDisplay = Archipelago.instance.IsTunicPlayer(ShopItem.Player) && TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(ShopItem.ItemName) ? TextBuilderPatches.ItemNameToAbbreviation[ShopItem.ItemName] : "[archipelago]"; + __instance.confirmPurchaseFormattedLanguageLine.text = $"bI for {Price} [money]?\n {itemToDisplay} " + GhostHints.WordWrapString($"\"{Archipelago.instance.GetPlayerName(ShopItem.Player).ToUpper().Replace(" ", "\" \"")}'S\" \"{ShopItem.ItemName.ToUpper().Replace($" ", $"\" \"")}\""); + } else if (IsSinglePlayer()) { + ItemData itemData = ItemLookup.GetItemDataFromCheck(Locations.RandomizedLocations[LocationId]); + itemToDisplay = TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(itemData.Name) ? TextBuilderPatches.ItemNameToAbbreviation[itemData.Name] : ""; + __instance.confirmPurchaseFormattedLanguageLine.text = $"bI for {Price} [money]?"; + if (TunicArchipelago.Settings.ShowItemsEnabled) { + __instance.confirmPurchaseFormattedLanguageLine.text += $"\n{itemToDisplay} \"{itemData.Name}\""; + } + } string CheckName = Locations.LocationIdToDescription[LocationId]; - if (TunicArchipelago.Settings.SendHintsToServer && SaveFile.GetInt($"archipelago sent optional hint to server {CheckName}") == 0) { + if (IsArchipelago() && TunicArchipelago.Settings.SendHintsToServer && SaveFile.GetInt($"archipelago sent optional hint to server {CheckName}") == 0) { Archipelago.instance.integration.session.Locations.ScoutLocationsAsync(true, Archipelago.instance.GetLocationId(CheckName)); SaveFile.SetInt($"archipelago sent optional hint to server {CheckName}", 1); } @@ -163,8 +187,11 @@ public static bool ShopItem_buy_PrefixPatch(ShopItem __instance) { GenericMessage.ShowMessage($"nawt Enuhf [money]..."); } else { Inventory.GetItemByName("MoneySmall").Quantity -= Price; - - Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + if (IsArchipelago()) { + Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + } else if (IsSinglePlayer()) { + GiveItem(Locations.RandomizedLocations[LocationId]); + } __instance.boughtStatevar.BoolValue = true; } @@ -179,8 +206,10 @@ public static void TrinketWell_TossedInCoin_PostfixPatch(TrinketWell __instance) public static bool TrinketWell_giveTrinketUpgrade_PrefixPatch(TrinketWell._giveTrinketUpgrade_d__14 __instance) { string LocationId = $"Well Reward ({StateVariable.GetStateVariableByName("Trinket Coins Tossed").IntValue} Coins) [Trinket Well]"; - if (Locations.LocationIdToDescription.ContainsKey(LocationId)) { + if (IsArchipelago() && Locations.LocationIdToDescription.ContainsKey(LocationId)) { Archipelago.instance.ActivateCheck(Locations.LocationIdToDescription[LocationId]); + } else if (IsSinglePlayer()) { + GiveItem(Locations.RandomizedLocations[LocationId]); } return false; } @@ -256,7 +285,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { } } GameObject.Instantiate(ModelSwaps.FairyAnimation, PlayerCharacter.instance.transform.position, Quaternion.identity).SetActive(true); - NotificationBottom = $"\"{TunicArchipelago.Tracker.ImportantItems["Fairies"] + 1}/20\" fArEz fownd."; + NotificationBottom = $"\"{(TunicArchipelago.Tracker.ImportantItems["Fairies"] + 1)}/20\" fArEz fownd."; } if (Item.Type == ItemTypes.PAGE) { @@ -275,7 +304,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { Dictionary pagesForAbilities = new Dictionary() { { "12", (PrayerUnlocked, PrayerUnlockedTime, ItemLookup.PrayerUnlockedLine) }, { "21", (HolyCrossUnlocked, HolyCrossUnlockedTime, ItemLookup.HolyCrossUnlockedLine) }, - { "26", (IceRodUnlocked, IceRodUnlockedTime, ItemLookup.IceRodUnlockedLine) }, + { "26", (IceBoltUnlocked, IceboltUnlockedTime, ItemLookup.IceboltUnlockedLine) }, }; if (pagesForAbilities.ContainsKey(Item.ItemNameForInventory)) { SaveFile.SetInt(pagesForAbilities[Item.ItemNameForInventory].Item1, 1); @@ -302,7 +331,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { // Apply bonus upgrade text if (TunicArchipelago.Settings.BonusStatUpgradesEnabled) { GoldenTrophy.collectionMessage = ScriptableObject.CreateInstance(); - GoldenTrophy.collectionMessage.text = ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage; + GoldenTrophy.collectionMessage.text = Translations.TranslateDefaultNoQuotes(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage, true); Inventory.GetItemByName(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].LevelUp).Quantity += 1; } else { GoldenTrophy.collectionMessage = ScriptableObject.CreateInstance(); @@ -310,6 +339,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { } ItemPresentation.PresentItem(GoldenTrophy); + NotificationBottom = TunicArchipelago.Settings.BonusStatUpgradesEnabled ? Translations.TranslateDefaultNoQuotes(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage, true) : $"kawngrahJoulA$uhnz!"; } if (Item.Type == ItemTypes.RELIC) { @@ -322,7 +352,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { // Apply custom pickup text RelicItem.collectionMessage = new LanguageLine(); - RelicItem.collectionMessage.text = ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage; + RelicItem.collectionMessage.text = Translations.Translate(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage, true); ItemPresentation.PresentItem(RelicItem); } @@ -340,7 +370,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { Dictionary hexesForAbilities = new Dictionary() { { SaveFile.GetInt(HexagonQuestPrayer), (PrayerUnlocked, PrayerUnlockedTime, ItemLookup.PrayerUnlockedLine) }, { SaveFile.GetInt(HexagonQuestHolyCross), (HolyCrossUnlocked, HolyCrossUnlockedTime, ItemLookup.HolyCrossUnlockedLine) }, - { SaveFile.GetInt(HexagonQuestIceRod), (IceRodUnlocked, IceRodUnlockedTime, ItemLookup.IceRodUnlockedLine) }, + { SaveFile.GetInt(HexagonQuestIcebolt), (IceBoltUnlocked, IceboltUnlockedTime, ItemLookup.IceboltUnlockedLine) }, }; if (hexesForAbilities.ContainsKey(GoldHexes)) { SaveFile.SetInt(hexesForAbilities[GoldHexes].Item1, 1); @@ -365,7 +395,7 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { if (Item.Type == ItemTypes.PAGE) { SaveFile.SetFloat($"randomizer {Item.Name} 1 time", SpeedrunData.inGameTime); } else { - SaveFile.SetFloat($"randomizer {Item.Name} {TunicArchipelago.Tracker.ImportantItems[Item.ItemNameForInventory] + 1} time", SpeedrunData.inGameTime); + SaveFile.SetFloat($"randomizer {Item.Name} {(TunicArchipelago.Tracker.ImportantItems[Item.ItemNameForInventory] + 1)} time", SpeedrunData.inGameTime); } } @@ -396,6 +426,213 @@ public static ItemResult GiveItem(string ItemName, NetworkItem networkItem) { return ItemResult.Success; } + public static void GiveItem(Check Check) { + + string NotificationTop = ""; + string NotificationBottom = ""; + bool DisplayMessageAnyway = false; + + ItemData Item = ItemLookup.GetItemDataFromCheck(Check); + + if (Item.Type == ItemTypes.MONEY) { + int AmountToGive = Check.Reward.Amount; + + Dictionary OriginalShopPrices = new Dictionary() { + { "Potion (First)", 300 }, + { "Potion (West Garden)", 1000 }, + { "Trinket Coin 1 (day)", 999 }, + { "Trinket Coin 2 (night)", 999 } + }; + if (OriginalShopPrices.ContainsKey(Check.Location.LocationId)) { + AmountToGive += TunicArchipelago.Settings.CheaperShopItemsEnabled ? 300 : OriginalShopPrices[Check.Location.LocationId]; + } + + CoinSpawner.SpawnCoins(AmountToGive, PlayerCharacter.instance.transform.position); + } + + if (Item.Type == ItemTypes.INVENTORY || Item.Type == ItemTypes.TRINKET) { + Item InventoryItem = Inventory.GetItemByName(Item.ItemNameForInventory); + InventoryItem.Quantity += Check.Reward.Amount; + if (Item.Name == "Stick" || Item.Name == "Sword") { + InventoryItem.collectionMessage = ScriptableObject.CreateInstance(); + InventoryItem.collectionMessage.text = $"fownd ahn Itehm!"; + } + if (Item.Name == "Dath Stone") { + Inventory.GetItemByName("Torch").Quantity = 1; + } + ItemPresentation.PresentItem(InventoryItem, Check.Reward.Amount); + if (TunicArchipelago.Settings.SkipItemAnimations && Item.Name == "Flask Shard" && Inventory.GetItemByName("Flask Shard").Quantity >= 3) { + Inventory.GetItemByName("Flask Shard").Quantity -= 3; + Inventory.GetItemByName("Flask Container").Quantity += 1; + } + } + + if (Item.Type == ItemTypes.SWORDUPGRADE) { + + if (SaveFile.GetInt(SwordProgressionEnabled) == 1 && Item.Name == "Sword Upgrade") { + int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); + SwordProgression.UpgradeSword(SwordLevel + 1); + } + if (TunicArchipelago.Settings.ShowItemsEnabled) { + ModelSwaps.SwapItemsInScene(); + } + } + + if (Item.Type == ItemTypes.FAIRY) { + foreach (string Fairy in ItemLookup.FairyLookup.Keys) { + if (SaveFile.GetInt($"randomizer obtained fairy {Fairy}") == 0) { + SaveFile.SetInt($"randomizer obtained fairy {Fairy}", 1); + break; + } + } + GameObject.Instantiate(ModelSwaps.FairyAnimation, PlayerCharacter.instance.transform.position, Quaternion.identity).SetActive(true); + NotificationBottom = $"\"{(TunicArchipelago.Tracker.ImportantItems["Fairies"] + 1)}/20\" fArEz fownd."; + } + + if (Item.Type == ItemTypes.PAGE) { + SaveFile.SetInt($"randomizer obtained page {Item.ItemNameForInventory}", 1); + bool HasAllPages = true; + for (int i = 0; i < 28; i++) { + if (SaveFile.GetInt($"randomizer obtained page {i}") == 0) { + HasAllPages = false; + break; + } + } + if (!StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue && HasAllPages) { + StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue = HasAllPages; + } + if (SaveFile.GetInt(AbilityShuffle) == 1) { + Dictionary pagesForAbilities = new Dictionary() { + { "12", (PrayerUnlocked, PrayerUnlockedTime, ItemLookup.PrayerUnlockedLine) }, + { "21", (HolyCrossUnlocked, HolyCrossUnlockedTime, ItemLookup.HolyCrossUnlockedLine) }, + { "26", (IceBoltUnlocked, IceboltUnlockedTime, ItemLookup.IceboltUnlockedLine) }, + }; + if (pagesForAbilities.ContainsKey(Item.ItemNameForInventory)) { + SaveFile.SetInt(pagesForAbilities[Item.ItemNameForInventory].Item1, 1); + SaveFile.SetFloat(pagesForAbilities[Item.ItemNameForInventory].Item2, SpeedrunData.inGameTime); + NotificationBottom = pagesForAbilities[Item.ItemNameForInventory].Item3; + DisplayMessageAnyway = true; + if (Item.ItemNameForInventory == "21") { + ToggleHolyCrossObjects(true); + } + ItemStatsHUD.UpdateAbilitySection(); + } + } + if (!TunicArchipelago.Settings.SkipItemAnimations) { + PageDisplay.ShowPage(int.Parse(Item.ItemNameForInventory, CultureInfo.InvariantCulture)); + } else { + SaveFile.SetInt("last page viewed", int.Parse(Item.ItemNameForInventory, CultureInfo.InvariantCulture)); + } + } + + if (Item.Type == ItemTypes.GOLDENTROPHY) { + + Item GoldenTrophy = Inventory.GetItemByName(Item.ItemNameForInventory); + GoldenTrophy.Quantity += Check.Reward.Amount; + // Apply bonus upgrade text + if (TunicArchipelago.Settings.BonusStatUpgradesEnabled) { + GoldenTrophy.collectionMessage = ScriptableObject.CreateInstance(); + GoldenTrophy.collectionMessage.text = Translations.TranslateDefaultNoQuotes(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage, true); + Inventory.GetItemByName(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].LevelUp).Quantity += 1; + } else { + GoldenTrophy.collectionMessage = ScriptableObject.CreateInstance(); + GoldenTrophy.collectionMessage.text = $"kawngrahJoulA$uhnz!"; + } + + ItemPresentation.PresentItem(GoldenTrophy); + NotificationBottom = TunicArchipelago.Settings.BonusStatUpgradesEnabled ? Translations.TranslateDefaultNoQuotes(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage, true) : $"kawngrahJoulA$uhnz!"; + } + + if (Item.Type == ItemTypes.RELIC) { + + Item RelicItem = Inventory.GetItemByName(Item.ItemNameForInventory); + RelicItem.Quantity += Check.Reward.Amount; + if (TunicArchipelago.Settings.BonusStatUpgradesEnabled) { + Inventory.GetItemByName(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].LevelUp).Quantity += 1; + } + + // Apply custom pickup text + RelicItem.collectionMessage = new LanguageLine(); + RelicItem.collectionMessage.text = Translations.Translate(ItemLookup.BonusUpgrades[Item.ItemNameForInventory].CustomPickupMessage, true); + + ItemPresentation.PresentItem(RelicItem); + } + + if (Item.Type == ItemTypes.FOOLTRAP) { + (NotificationTop, NotificationBottom) = ApplyFoolEffect(-1); + DisplayMessageAnyway = true; + } + + if (Item.Type == ItemTypes.HEXAGONQUEST) { + Inventory.GetItemByName("Hexagon Gold").Quantity += 1; + int GoldHexes = Inventory.GetItemByName("Hexagon Gold").Quantity; + + if (SaveFile.GetInt(AbilityShuffle) == 1) { + Dictionary hexesForAbilities = new Dictionary() { + { SaveFile.GetInt(HexagonQuestPrayer), (PrayerUnlocked, PrayerUnlockedTime, ItemLookup.PrayerUnlockedLine) }, + { SaveFile.GetInt(HexagonQuestHolyCross), (HolyCrossUnlocked, HolyCrossUnlockedTime, ItemLookup.HolyCrossUnlockedLine) }, + { SaveFile.GetInt(HexagonQuestIcebolt), (IceBoltUnlocked, IceboltUnlockedTime, ItemLookup.IceboltUnlockedLine) }, + }; + if (hexesForAbilities.ContainsKey(GoldHexes)) { + SaveFile.SetInt(hexesForAbilities[GoldHexes].Item1, 1); + SaveFile.SetFloat(hexesForAbilities[GoldHexes].Item2, SpeedrunData.inGameTime); + NotificationBottom = hexesForAbilities[GoldHexes].Item3; + DisplayMessageAnyway = true; + if (GoldHexes == SaveFile.GetInt(HexagonQuestHolyCross)) { + ToggleHolyCrossObjects(true); + } + + ItemStatsHUD.UpdateAbilitySection(); + } + } + + ItemPresentation.PresentItem(Inventory.GetItemByName(Item.ItemNameForInventory)); + } + + if (ItemLookup.MajorItems.Contains(Item.Name)) { + if (Item.Type == ItemTypes.SWORDUPGRADE && SaveFile.GetInt(SwordProgressionEnabled) == 1) { + SaveFile.SetFloat($"randomizer Sword Progression {SaveFile.GetInt(SwordProgressionLevel)} time", SpeedrunData.inGameTime); + } else if (Item.Type == ItemTypes.PAGE) { + SaveFile.SetFloat($"randomizer {Item.Name} 1 time", SpeedrunData.inGameTime); + } else { + SaveFile.SetFloat($"randomizer {Item.Name} {(TunicArchipelago.Tracker.ImportantItems[Item.ItemNameForInventory] + 1)} time", SpeedrunData.inGameTime); + } + } + + if (TunicArchipelago.Settings.SkipItemAnimations || DisplayMessageAnyway) { + NotificationTop = NotificationTop == "" ? $"yoo fownd {(TextBuilderPatches.ItemNameToAbbreviation.ContainsKey(Item.Name) ? TextBuilderPatches.ItemNameToAbbreviation[Item.Name] : "")} \"{Item.Name}!\"" : NotificationTop; + NotificationBottom = NotificationBottom == "" ? $"$oud bE yoosfuhl!" : NotificationBottom; + Notifications.Show(NotificationTop, NotificationBottom); + } + + string slotLoc = $"{Check.Location.LocationId} [{Check.Location.SceneName}]"; + if (Hints.HeroGraveHints.Values.Where(hint => hint.PathHintId == slotLoc || hint.RelicHintId == slotLoc).Any()) { + SaveFile.SetInt($"randomizer hint found {slotLoc}", 1); + } + if (Hints.HeroGraveHints.Values.Where(hint => SaveFile.GetInt($"randomizer hint found {hint.PathHintId}") == 1).Count() == 6) { + StateVariable.GetStateVariableByName("randomizer got all 6 grave items").BoolValue = true; + } + + TunicArchipelago.Tracker.SetCollectedItem(Item.Name, true); + + string CheckId = $"{Check.Location.LocationId} [{Check.Location.SceneName}]"; + Logger.LogInfo("Picked up item " + CheckId + " (" + Item.Name + ")"); + + Locations.CheckedLocations[CheckId] = true; + SaveFile.SetInt($"randomizer picked up {CheckId}", 1); + GameObject FairyTarget = GameObject.Find($"fairy target {CheckId}"); + if (FairyTarget != null) { + GameObject.Destroy(FairyTarget); + } + if (Locations.VanillaLocations.Keys.Where(key => Locations.VanillaLocations[key].Location.SceneName == SceneLoaderPatches.SceneName && !Locations.CheckedLocations[key]).ToList().Count == 0) { + FairyTargets.CreateLoadZoneTargets(); + } + + if (TunicArchipelago.Settings.CreateSpoilerLog && !TunicArchipelago.Settings.RaceMode) { + ItemTracker.PopulateSpoilerLog(); + } + } + private static (string, string) ApplyFoolEffect(int Player) { System.Random Random = new System.Random(); int FoolType = PlayerCharacterPatches.StungByBee ? Random.Next(21, 100) : Random.Next(100); @@ -425,13 +662,28 @@ private static (string, string) ApplyFoolEffect(int Player) { FoolMessageBottom = $"hahvi^ ahn Is tIm?"; } - if (Player != Archipelago.instance.GetPlayerSlot()) { + if (Player == -1 && IsSinglePlayer()) { + + } else if (IsArchipelago() && Player != Archipelago.instance.GetPlayerSlot()) { FoolMessageTop = $"\"{Archipelago.instance.GetPlayerName(Player)}\" %i^ks {FoolMessageTop}"; } return (FoolMessageTop, FoolMessageBottom); } + public static void CheckFoolTrapSetting(string RewardId) { + Reward Reward = Locations.RandomizedLocations[RewardId].Reward; + if (Reward.Type == "MONEY") { + if ((TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.NORMAL && Reward.Amount < 20) + || (TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.DOUBLE && Reward.Amount <= 20) + || (TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.ONSLAUGHT && Reward.Amount <= 30)) { + Reward.Name = "Fool"; + Reward.Type = "FOOL"; + Reward.Amount = 1; + } + } + } + public static void ToggleHolyCrossObjects(bool isEnabled) { foreach (ToggleObjectBySpell SpellToggle in Resources.FindObjectsOfTypeAll()) { foreach (ToggleObjectBySpell Spell in SpellToggle.gameObject.GetComponents()) { @@ -448,7 +700,9 @@ public static void PotionCombine_Show_PostFixPatch(PotionCombine __instance) { public static void ButtonAssignableItem_CheckFreeItemSpell_PostfixPatch(ButtonAssignableItem __instance, ref string s) { if (ItemLookup.BombCodes.ContainsKey(s) && StateVariable.GetStateVariableByName(ItemLookup.BombCodes[s]).BoolValue) { if (SaveFile.GetInt($"randomizer used free bomb code {s}") == 0) { - Archipelago.instance.UpdateDataStorage(ItemLookup.BombCodes[s], true); + if (IsArchipelago()) { + Archipelago.instance.UpdateDataStorage(ItemLookup.BombCodes[s], true); + } SaveFile.SetInt($"randomizer used free bomb code {s}", 1); if (TunicArchipelago.Settings.SkipItemAnimations) { switch(ItemLookup.BombCodes[s]) { @@ -469,8 +723,18 @@ public static void ButtonAssignableItem_CheckFreeItemSpell_PostfixPatch(ButtonAs } } + public static bool UpgradeMenu___Buy_PrefixPatch(UpgradeMenu __instance) { + + if (TunicArchipelago.Settings.RaceMode && TunicArchipelago.Settings.DisableUpgradeStealing && UpgradeAltar.nearbyThings.Count == 0) { + UpgradeMenu.instance.__Exit(); + return false; + } + + return true; + } public static bool UpgradeAltar_DoOfferingSequence_PrefixPatch(UpgradeAltar __instance, OfferingItem offeringItemToOffer) { + if (TunicArchipelago.Settings.FasterUpgrades) { Notifications.Show($"{TextBuilderPatches.SpriteNameToAbbreviation[offeringItemToOffer.icon.name]} \"{offeringItemToOffer.statLabelLocKey}\" wehnt uhp fruhm {offeringItemToOffer.upgradeItemReceived.Quantity} [arrow_right] {offeringItemToOffer.upgradeItemReceived.Quantity+1}!", $"#E Ar ahksehpts yor awfuri^."); UpgradeMenu.instance.__Exit(); diff --git a/src/Patches/ItemRandomizer.cs b/src/Patches/ItemRandomizer.cs new file mode 100644 index 0000000..951608f --- /dev/null +++ b/src/Patches/ItemRandomizer.cs @@ -0,0 +1,469 @@ +using BepInEx.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static UnityEngine.InputSystem.LowLevel.InputStateHistory; +using UnityEngine.InputSystem.Utilities; +using Newtonsoft.Json; + +namespace TunicArchipelago { + public class ItemRandomizer { + private static ManualLogSource Logger = TunicArchipelago.Logger; + + public static Dictionary SphereZero = new Dictionary(); + + public static void PopulateSphereZero() { + SphereZero.Clear(); + if (SaveFile.GetInt("randomizer shuffled abilities") == 0) { + SphereZero.Add("12", 1); + SphereZero.Add("21", 1); + SphereZero.Add("26", 1); + } + if (SaveFile.GetInt("randomizer started with sword") == 1) { + SphereZero.Add("Sword", 1); + } + } + + public static void RandomizeAndPlaceItems() { + Logger.LogInfo("randomize and place items starting"); + + System.Random random = new System.Random(SaveFile.GetInt("seed")); + Locations.RandomizedLocations.Clear(); + Locations.CheckedLocations.Clear(); + + List ProgressionNames = new List{ "Hyperdash", "Wand", "Techbow", "Stundagger", "Trinket Coin", "Lantern", "Stick", "Sword", "Sword Progression", "Key", "Key (House)", "Mask", "Vault Key (Red)" }; + if (SaveFile.GetInt("randomizer shuffled abilities") == 1) { + if (SaveFile.GetInt(SaveFlags.HexagonQuestEnabled) == 1) { + ProgressionNames.Add("Hexagon Gold"); + } else { + ProgressionNames.Add("12"); // Prayer + ProgressionNames.Add("21"); // Holy Cross + ProgressionNames.Add("26"); // Icebolt + } + } + if (SaveFile.GetInt(SaveFlags.LanternlessLogic) == 1) { + ProgressionNames.Remove("Lantern"); + } + if (SaveFile.GetInt(SaveFlags.MasklessLogic) == 1) { + ProgressionNames.Remove("Mask"); + } + + List InitialItems = JsonConvert.DeserializeObject>(ItemListJson.ItemList); + List InitialRewards = new List(); + List InitialLocations = new List(); + List Hexagons = new List(); + Check Laurels = new Check(); + List ProgressionRewards = new List(); + Dictionary UnplacedInventory = new Dictionary(SphereZero); + Dictionary SphereZeroInventory = new Dictionary(SphereZero); + Dictionary ProgressionLocations = new Dictionary { }; + int GoldHexagonsAdded = 0; + int HexagonsToAdd = (int)Math.Round((100f + SaveFile.GetInt("randomizer hexagon quest extras")) / 100f * SaveFile.GetInt("randomizer hexagon quest goal")); + if (SaveFile.GetInt(SaveFlags.HexagonQuestEnabled) == 1 && SaveFile.GetInt("randomizer shuffled abilities") == 1) { + int HexGoal = SaveFile.GetInt("randomizer hexagon quest goal"); + List abilities = new List() { "prayer", "holy cross", "icebolt" }.OrderBy(r => random.Next()).ToList(); + List ability_unlocks = new List() { (int)(HexGoal / 4f), (int)((HexGoal / 4f) * 2), (int)((HexGoal / 4f) * 3) }.OrderBy(r => random.Next()).ToList(); + for (int i = 0; i < 3; i++) { + int index = random.Next(abilities.Count); + int index2 = random.Next(ability_unlocks.Count); + SaveFile.SetInt($"randomizer hexagon quest {abilities[index]} requirement", ability_unlocks[index2]); + abilities.RemoveAt(index); + ability_unlocks.RemoveAt(index2); + } + } + Shuffle(InitialItems, random); + foreach (Check Item in InitialItems) { + + if (SaveFile.GetInt(SaveFlags.MasklessLogic) == 1 || SaveFile.GetInt(SaveFlags.LanternlessLogic) == 1) { + if (Item.Location.RequiredItems.Count > 0 && Item.Location.RequiredItems.Where(dict => dict.ContainsKey("Mask") || dict.ContainsKey("Lantern")).Count() > 0) { + for (int i = 0; i < Item.Location.RequiredItems.Count; i++) { + if (Item.Location.RequiredItems[i].ContainsKey("Mask") && SaveFile.GetInt(SaveFlags.MasklessLogic) == 1) { + Item.Location.RequiredItems[i].Remove("Mask"); + } + if (Item.Location.RequiredItems[i].ContainsKey("Lantern") && SaveFile.GetInt(SaveFlags.LanternlessLogic) == 1) { + Item.Location.RequiredItems[i].Remove("Lantern"); + } + } + } + } + + if (SaveFile.GetInt("randomizer keys behind bosses") != 0 && (Item.Reward.Name.Contains("Hexagon") || Item.Reward.Name == "Vault Key (Red)")) { + if (Item.Reward.Name == "Hexagon Green" || Item.Reward.Name == "Hexagon Blue") { + Hexagons.Add(Item); + } else if (Item.Reward.Name == "Vault Key (Red)") { + Item.Reward.Name = "Hexagon Red"; + Hexagons.Add(Item); + } else if (Item.Reward.Name == "Hexagon Red") { + Item.Reward.Name = "Vault Key (Red)"; + InitialRewards.Add(Item.Reward); + InitialLocations.Add(Item.Location); + } + } else if ((SaveFile.GetInt("randomizer laurels location") == 1 && Item.Location.LocationId == "Well Reward (6 Coins)") + || (SaveFile.GetInt("randomizer laurels location") == 2 && Item.Location.LocationId == "Well Reward (10 Coins)") + || (SaveFile.GetInt("randomizer laurels location") == 3 && Item.Location.LocationId == "waterfall")) { + InitialRewards.Add(Item.Reward); + Laurels.Location = Item.Location; + } else if (SaveFile.GetInt("randomizer laurels location") != 0 && Item.Reward.Name == "Hyperdash") { + InitialLocations.Add(Item.Location); + Laurels.Reward = Item.Reward; + } else { + if (SaveFile.GetInt("randomizer sword progression enabled") != 0 && (Item.Reward.Name == "Stick" || Item.Reward.Name == "Sword" || Item.Location.LocationId == "5")) { + Item.Reward.Name = "Sword Progression"; + Item.Reward.Type = "SPECIAL"; + } + if (SaveFile.GetInt(SaveFlags.HexagonQuestEnabled) == 1) { + if (Item.Reward.Type == "PAGE" || Item.Reward.Name.Contains("Hexagon")) { + string FillerItem = ItemLookup.FillerItems.Keys.ToList()[random.Next(ItemLookup.FillerItems.Count)]; + Item.Reward.Name = FillerItem; + Item.Reward.Type = FillerItem == "money" ? "MONEY" : "INVENTORY"; + Item.Reward.Amount = ItemLookup.FillerItems[FillerItem][random.Next(ItemLookup.FillerItems[FillerItem].Count)]; + } + if (ItemLookup.FillerItems.ContainsKey(Item.Reward.Name) && ItemLookup.FillerItems[Item.Reward.Name].Contains(Item.Reward.Amount) && GoldHexagonsAdded < HexagonsToAdd) { + Item.Reward.Name = "Hexagon Gold"; + Item.Reward.Type = "SPECIAL"; + Item.Reward.Amount = 1; + GoldHexagonsAdded++; + } + if (SaveFile.GetInt("randomizer shuffled abilities") == 1) { + if (Item.Location.RequiredItems.Count > 0) { + for (int i = 0; i < Item.Location.RequiredItems.Count; i++) { + if (Item.Location.RequiredItems[i].ContainsKey("12") && Item.Location.RequiredItems[i].ContainsKey("21")) { + int amt = Math.Max(SaveFile.GetInt($"randomizer hexagon quest prayer requirement"), SaveFile.GetInt($"randomizer hexagon quest holy cross requirement")); + Item.Location.RequiredItems[i].Remove("12"); + Item.Location.RequiredItems[i].Remove("21"); + Item.Location.RequiredItems[i].Add("Hexagon Gold", amt); + } + if (Item.Location.RequiredItems[i].ContainsKey("12")) { + Item.Location.RequiredItems[i].Remove("12"); + Item.Location.RequiredItems[i].Add("Hexagon Gold", SaveFile.GetInt($"randomizer hexagon quest prayer requirement")); + } + if (Item.Location.RequiredItems[i].ContainsKey("21")) { + Item.Location.RequiredItems[i].Remove("21"); + Item.Location.RequiredItems[i].Add("Hexagon Gold", SaveFile.GetInt($"randomizer hexagon quest holy cross requirement")); + } + if (Item.Location.RequiredItems[i].ContainsKey("26")) { + Item.Location.RequiredItems[i].Remove("26"); + Item.Location.RequiredItems[i].Add("Hexagon Gold", SaveFile.GetInt($"randomizer hexagon quest icebolt requirement")); + } + } + } + if (Item.Location.RequiredItemsDoors.Count > 0) { + for (int i = 0; i < Item.Location.RequiredItemsDoors.Count; i++) { + if (Item.Location.RequiredItemsDoors[i].ContainsKey("12") && Item.Location.RequiredItemsDoors[i].ContainsKey("21")) { + int amt = Math.Max(SaveFile.GetInt($"randomizer hexagon quest prayer requirement"), SaveFile.GetInt($"randomizer hexagon quest holy cross requirement")); + Item.Location.RequiredItemsDoors[i].Remove("12"); + Item.Location.RequiredItemsDoors[i].Remove("21"); + Item.Location.RequiredItemsDoors[i].Add("Hexagon Gold", amt); + } + if (Item.Location.RequiredItemsDoors[i].ContainsKey("12")) { + Item.Location.RequiredItemsDoors[i].Remove("12"); + Item.Location.RequiredItemsDoors[i].Add("Hexagon Gold", SaveFile.GetInt($"randomizer hexagon quest prayer requirement")); + } + if (Item.Location.RequiredItemsDoors[i].ContainsKey("21")) { + Item.Location.RequiredItemsDoors[i].Remove("21"); + Item.Location.RequiredItemsDoors[i].Add("Hexagon Gold", SaveFile.GetInt($"randomizer hexagon quest holy cross requirement")); + } + if (Item.Location.RequiredItemsDoors[i].ContainsKey("26")) { + Item.Location.RequiredItemsDoors[i].Remove("26"); + Item.Location.RequiredItemsDoors[i].Add("Hexagon Gold", SaveFile.GetInt($"randomizer hexagon quest icebolt requirement")); + } + } + } + } + } + if (ProgressionNames.Contains(Item.Reward.Name) || ItemLookup.FairyLookup.Keys.Contains(Item.Reward.Name)) { + ProgressionRewards.Add(Item.Reward); + } else { + InitialRewards.Add(Item.Reward); + } + InitialLocations.Add(Item.Location); + } + } + + // adding the progression rewards to the start inventory, so we can reverse fill + foreach (Reward item in ProgressionRewards) { + string itemName = ItemLookup.FairyLookup.Keys.Contains(item.Name) ? "Fairy" : item.Name; + if (UnplacedInventory.ContainsKey(itemName)) { + UnplacedInventory[itemName] += 1; + } else { + UnplacedInventory.Add(itemName, 1); + } + } + // if laurels location is on, manually add laurels to the unplaced inventory + if (!UnplacedInventory.ContainsKey("Hyperdash")) { + UnplacedInventory.Add("Hyperdash", 1); + } + + // make a scene inventory, so we can keep the item inventory separated. Add overworld to start (change later if we do start rando) + Dictionary FullInventory = new Dictionary(); + TunicPortals.RandomizePortals(SaveFile.GetInt("seed")); + int fairyCount = 0; + bool laurelsPlaced = false; + + // put progression items in locations + foreach (Reward item in ProgressionRewards.OrderBy(r => random.Next())) { + + // pick an item + string itemName = ItemLookup.FairyLookup.Keys.Contains(item.Name) ? "Fairy" : item.Name; + // remove item from inventory for reachability checks + if (UnplacedInventory.Keys.Contains(itemName)) { + UnplacedInventory[itemName] -= 1; + } + if (UnplacedInventory[itemName] == 0) { + UnplacedInventory.Remove(itemName); + } + + if (itemName == "Fairy") { + fairyCount++; + } + if (SaveFile.GetInt("randomizer laurels location") != 0 && !laurelsPlaced && ( + (SaveFile.GetInt("randomizer laurels location") == 1 && UnplacedInventory["Trinket Coin"] == 10) + || (SaveFile.GetInt("randomizer laurels location") == 2 && UnplacedInventory["Trinket Coin"] == 6) + || (SaveFile.GetInt("randomizer laurels location") == 3 && fairyCount == 11))) { + // laurels will no longer be accessible, remove it from the pool + laurelsPlaced = true; + UnplacedInventory.Remove("Hyperdash"); + } + + // door rando time + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { + // this should keep looping until every portal either doesn't give a reward, or has already given its reward + + FullInventory.Clear(); + FullInventory.Add("Overworld", 1); + foreach (KeyValuePair placedItem in UnplacedInventory) { + FullInventory.Add(placedItem.Key, placedItem.Value); + } + + // fill up our FullInventory with regions until we stop getting new regions -- these are the portals and regions we can currently reach + while (true) { + int start_num = FullInventory.Count; + FullInventory = TunicPortals.UpdateReachableRegions(FullInventory); + foreach (PortalCombo portalCombo in TunicPortals.RandomizedPortals.Values) { + FullInventory = portalCombo.AddComboRegions(FullInventory); + } + int end_num = FullInventory.Count; + if (start_num == end_num) { + break; + } + } + } + + // pick a location + int l; + l = random.Next(InitialLocations.Count); + + //foreach (Location loc in InitialLocations) + //{ if (!loc.reachable(CombinedInventory)) { Logger.LogInfo("location " + loc.SceneName + " " + loc.LocationId + " is not reachable"); } } + //Logger.LogInfo(InitialLocations[l].SceneName + " " + InitialLocations[l].LocationId); + // if location isn't reachable with current inventory excluding the item to be placed, pick a new location + while (!InitialLocations[l].reachable(FullInventory)) { + l = random.Next(InitialLocations.Count); + } + + // prepare matched list of progression items and locations + string DictionaryId = $"{InitialLocations[l].LocationId} [{InitialLocations[l].SceneName}]"; + Check Check = new Check(item, InitialLocations[l]); + ProgressionLocations.Add(DictionaryId, Check); + + InitialLocations.Remove(InitialLocations[l]); + } + + SphereZero = FullInventory; + + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { + List sphere_zero_list = GetERSphereOne(); + foreach (string sphere_zero_item in sphere_zero_list) { + if (!SphereZero.ContainsKey(sphere_zero_item)) { + SphereZero.Add(sphere_zero_item, 1); + } + } + } + + // shuffle remaining rewards and locations + Shuffle(InitialRewards, InitialLocations, random); + + for (int i = 0; i < InitialRewards.Count; i++) { + string DictionaryId = $"{InitialLocations[i].LocationId} [{InitialLocations[i].SceneName}]"; + Check Check = new Check(InitialRewards[i], InitialLocations[i]); + Locations.RandomizedLocations.Add(DictionaryId, Check); + } + + // add progression items and locations back + foreach (string key in ProgressionLocations.Keys) { + Locations.RandomizedLocations.Add(key, ProgressionLocations[key]); + } + + if (SaveFile.GetInt("randomizer keys behind bosses") != 0) { + foreach (Check Hexagon in Hexagons) { + if (SaveFile.GetInt(SaveFlags.HexagonQuestEnabled) == 1) { + Hexagon.Reward.Name = "Hexagon Gold"; + Hexagon.Reward.Type = "SPECIAL"; + } + string DictionaryId = $"{Hexagon.Location.LocationId} [{Hexagon.Location.SceneName}]"; + Locations.RandomizedLocations.Add(DictionaryId, Hexagon); + } + } + + if (SaveFile.GetInt("randomizer laurels location") != 0) { + string DictionaryId = $"{Laurels.Location.LocationId} [{Laurels.Location.SceneName}]"; + Locations.RandomizedLocations.Add(DictionaryId, Laurels); + } + + if (SaveFile.GetString("randomizer game mode") == "VANILLA") { + Locations.RandomizedLocations.Clear(); + foreach (Check item in JsonConvert.DeserializeObject>(ItemListJson.ItemList)) { + if (SaveFile.GetInt("randomizer sword progression enabled") != 0) { + if (item.Reward.Name == "Stick" || item.Reward.Name == "Sword" || item.Location.LocationId == "5") { + item.Reward.Name = "Sword Progression"; + item.Reward.Type = "SPECIAL"; + } + } + string DictionaryId = $"{item.Location.LocationId} [{item.Location.SceneName}]"; + Locations.RandomizedLocations.Add(DictionaryId, item); + } + } + + foreach (string key in Locations.RandomizedLocations.Keys.ToList()) { + Check check = Locations.RandomizedLocations[key]; + if (check.Reward.Type == "MONEY") { + if ((TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.NORMAL && check.Reward.Amount < 20) + || (TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.DOUBLE && check.Reward.Amount <= 20) + || (TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.ONSLAUGHT && check.Reward.Amount <= 30)) { + check.Reward.Name = "Fool Trap"; + check.Reward.Type = "FOOL"; + } + } + } + + foreach (string Key in Locations.RandomizedLocations.Keys) { + int ItemPickedUp = SaveFile.GetInt($"randomizer picked up {Key}"); + Locations.CheckedLocations.Add(Key, ItemPickedUp == 1 ? true : false); + } + if (TunicArchipelago.Tracker.ItemsCollected.Count == 0) { + foreach (KeyValuePair PickedUpItem in Locations.CheckedLocations.Where(item => item.Value)) { + Check check = Locations.RandomizedLocations[PickedUpItem.Key]; + ItemData itemData = ItemLookup.GetItemDataFromCheck(check); + TunicArchipelago.Tracker.SetCollectedItem(itemData.Name, false); + } + ItemTracker.SaveTrackerFile(); + TunicArchipelago.Tracker.ImportantItems["Flask Container"] += TunicArchipelago.Tracker.ItemsCollected.Where(Item => Item.Name == "Flask Shard").Count() / 3; + if (SaveFile.GetInt("randomizer started with sword") == 1) { + TunicArchipelago.Tracker.ImportantItems["Sword"] += 1; + } + } + } + + private static void Shuffle(List Rewards, List Locations, System.Random random) { + int n = Rewards.Count; + int r; + int l; + while (n > 1) { + n--; + r = random.Next(n + 1); + l = random.Next(n + 1); + + Reward Reward = Rewards[r]; + Rewards[r] = Rewards[n]; + Rewards[n] = Reward; + + Location Location = Locations[l]; + Locations[l] = Locations[n]; + Locations[n] = Location; + } + } + + private static void Shuffle(List list, System.Random random) { + int n = list.Count; + int r; + while (n > 1) { + n--; + r = random.Next(n + 1); + + Check holder = list[r]; + list[r] = list[n]; + list[n] = holder; + } + } + + public static Check FindRandomizedItemByName(string Name) { + foreach (Check Check in Locations.RandomizedLocations.Values) { + if (Check.Reward.Name == Name) { + return Check; + } + } + return null; + } + + public static List FindAllRandomizedItemsByName(string Name) { + List results = new List(); + + foreach (Check Check in Locations.RandomizedLocations.Values) { + if (Check.Reward.Name == Name) { + results.Add(Check); + } + } + + return results; + } + + public static List FindAllRandomizedItemsByType(string type) { + List results = new List(); + + foreach (Check Check in Locations.RandomizedLocations.Values) { + if (Check.Reward.Type == type) { + results.Add(Check); + } + } + + return results; + } + + // In ER, we want sphere 1 to be in Overworld or adjacent to Overworld + public static List GetERSphereOne() { + List PortalInventory = new List(); + List CombinedInventory = new List { "Overworld" }; + + // add starting sword and abilities as applicable + if (SaveFile.GetInt("randomizer started with sword") == 1) { + CombinedInventory.Add("Sword"); + } + if (SaveFile.GetInt("randomizer shuffled abilities") == 0) { + CombinedInventory.Add("12"); + CombinedInventory.Add("21"); + CombinedInventory.Add("Overworld Fountain Cross Door"); + CombinedInventory.Add("Overworld Southeast Cross Door"); + CombinedInventory.Add("Overworld Town Portal"); + CombinedInventory.Add("Overworld Spawn Portal"); + } + // add these too if you're ignoring them in logic + if (SaveFile.GetInt(SaveFlags.MasklessLogic) == 1) { + CombinedInventory.Add("Mask"); + } + if (SaveFile.GetInt(SaveFlags.LanternlessLogic) == 1) { + CombinedInventory.Add("Lantern"); + } + + // find which portals you can reach from spawn without additional progression + foreach (PortalCombo portalCombo in TunicPortals.RandomizedPortals.Values) { + if (CombinedInventory.Contains(portalCombo.Portal1.Region)) { + PortalInventory.Add(portalCombo.Portal2); + } + if (CombinedInventory.Contains(portalCombo.Portal2.Region)) { + PortalInventory.Add(portalCombo.Portal1); + } + } + + // add the regions you can reach as your first steps to the inventory + foreach (Portal portal in PortalInventory) { + if (!CombinedInventory.Contains(portal.Region)) { + CombinedInventory.Add(portal.Region); + } + } + CombinedInventory = TunicPortals.UpdateReachableRegions(CombinedInventory); + return CombinedInventory; + } + } +} diff --git a/src/Patches/ItemStatsHUD.cs b/src/Patches/ItemStatsHUD.cs index 5774370..a4be784 100644 --- a/src/Patches/ItemStatsHUD.cs +++ b/src/Patches/ItemStatsHUD.cs @@ -153,6 +153,9 @@ public static void Initialize() { if (Screen.width <= 1280 && Screen.height <= 800) { Stats.transform.localScale = new Vector3(3.6f, 3.6f, 3.6f); } + if (Screen.width == 1920 && Screen.height == 1440) { + Stats.transform.localScale = new Vector3(3.6f, 3.6f, 3.6f); + } } Loaded = true; } @@ -235,7 +238,7 @@ public static void CreateAbilitySection() { icon.SetActive(false); } - List abilities = new List() { "Prayer", "Holy Cross", "Ice Rod" }; + List abilities = new List() { "Prayer", "Holy Cross", "Icebolt" }; TMP_FontAsset odin = Resources.FindObjectsOfTypeAll().Where(Font => Font.name == "Latin Rounded").ToList()[0]; Material fontMaterial = ModelSwaps.FindMaterial("Latin Rounded - Quantity Outline"); for (int i = 0; i < 3; i++) { @@ -294,7 +297,7 @@ public static void UpdateAbilitySection() { bool HasPrayer = SaveFile.GetInt(PrayerUnlocked) == 1; bool HasHolyCross = SaveFile.GetInt(HolyCrossUnlocked) == 1; - bool HasIceRod = SaveFile.GetInt(IceRodUnlocked) == 1; + bool HasIcebolt = SaveFile.GetInt(IceBoltUnlocked) == 1; Color Full = new Color(1, 1, 1, 1); Color Faded = new Color(1, 1, 1, 0.5f); bool isHexQuest = SaveFile.GetInt(HexagonQuestEnabled) == 1; @@ -303,7 +306,7 @@ public static void UpdateAbilitySection() { SortedDictionary HexUnlocks = new SortedDictionary() { { SaveFile.GetInt(HexagonQuestPrayer), "Prayer" }, { SaveFile.GetInt(HexagonQuestHolyCross), "Holy Cross" }, - { SaveFile.GetInt(HexagonQuestIceRod), "Ice Rod" }, + { SaveFile.GetInt(HexagonQuestIcebolt), "Icebolt" }, }; int GoldHexes = Inventory.GetItemByName("Hexagon Gold").Quantity; @@ -345,11 +348,11 @@ public static void UpdateAbilitySection() { } else { AbilityShuffle.transform.GetChild(13).GetComponent().text = "Prayer"; AbilityShuffle.transform.GetChild(14).GetComponent().text = "Holy Cross"; - AbilityShuffle.transform.GetChild(15).GetComponent().text = "Ice Rod"; + AbilityShuffle.transform.GetChild(15).GetComponent().text = "Icebolt"; AbilityShuffle.transform.GetChild(13).GetComponent().color = HasPrayer ? Full : Faded; AbilityShuffle.transform.GetChild(14).GetComponent().color = HasHolyCross ? Full : Faded; - AbilityShuffle.transform.GetChild(15).GetComponent().color = HasIceRod ? Full : Faded; + AbilityShuffle.transform.GetChild(15).GetComponent().color = HasIcebolt ? Full : Faded; AbilityShuffle.transform.GetChild(16).GetComponent().text = "24-25"; AbilityShuffle.transform.GetChild(17).GetComponent().text = "42-43"; @@ -362,11 +365,11 @@ public static void UpdateAbilitySection() { AbilityShuffle.transform.GetChild(16).GetComponent().color = HasPrayer ? Full : Faded; AbilityShuffle.transform.GetChild(17).GetComponent().color = HasHolyCross ? Full : Faded; - AbilityShuffle.transform.GetChild(18).GetComponent().color = HasIceRod ? Full : Faded; + AbilityShuffle.transform.GetChild(18).GetComponent().color = HasIcebolt ? Full : Faded; AbilityShuffle.transform.GetChild(7).GetComponent().color = HasPrayer ? Full : Faded; AbilityShuffle.transform.GetChild(8).GetComponent().color = HasHolyCross ? Full : Faded; - AbilityShuffle.transform.GetChild(9).GetComponent().color = HasIceRod ? Full : Faded; + AbilityShuffle.transform.GetChild(9).GetComponent().color = HasIcebolt ? Full : Faded; } } @@ -374,8 +377,8 @@ public static void UpdateAbilitySection() { public static void Update() { try { if (Locations.VanillaLocations.Count > 0) { - int ObtainedItemCount = Locations.VanillaLocations.Keys.Where(loc => Locations.CheckedLocations[loc] || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {loc} was collected") == 1)).ToList().Count; - int ObtainedItemCountInCurrentScene = Locations.VanillaLocations.Keys.Where(loc => Locations.VanillaLocations[loc].Location.SceneName == SceneLoaderPatches.SceneName && (Locations.CheckedLocations[loc] || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {loc} was collected") == 1))).ToList().Count; + int ObtainedItemCount = Locations.VanillaLocations.Keys.Where(loc => Locations.CheckedLocations[loc] || (SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {loc} was collected") == 1)).ToList().Count; + int ObtainedItemCountInCurrentScene = Locations.VanillaLocations.Keys.Where(loc => Locations.VanillaLocations[loc].Location.SceneName == SceneLoaderPatches.SceneName && (Locations.CheckedLocations[loc] || (SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {loc} was collected") == 1))).ToList().Count; int TotalItemCountInCurrentScene = Locations.VanillaLocations.Keys.Where(loc => Locations.VanillaLocations[loc].Location.SceneName == SceneLoaderPatches.SceneName).ToList().Count; Pages.GetComponent().text = $"Pages:\t\t{TunicArchipelago.Tracker.ImportantItems["Pages"]}/28"; Pages.GetComponent().color = TunicArchipelago.Tracker.ImportantItems["Pages"] == 28 ? new Color(0.917f, 0.65f, .08f) : Color.white; diff --git a/src/Patches/ModelSwaps.cs b/src/Patches/ModelSwaps.cs index 01e1c0a..7e016a9 100644 --- a/src/Patches/ModelSwaps.cs +++ b/src/Patches/ModelSwaps.cs @@ -338,7 +338,9 @@ public static void SwapItemsInScene() { } } - CheckCollectedItemFlags(); + if (IsArchipelago()) { + CheckCollectedItemFlags(); + } if (SceneLoaderPatches.SceneName == "Shop") { SetupShopItems(); @@ -347,7 +349,7 @@ public static void SwapItemsInScene() { foreach (ItemPickup ItemPickup in Resources.FindObjectsOfTypeAll()) { if (ItemPickup != null && ItemPickup.itemToGive != null) { string checkId = $"{ItemPickup.itemToGive.name} [{SceneLoaderPatches.SceneName}]"; - if(ItemLookup.ItemList.ContainsKey(checkId)) { + if(ItemLookup.ItemList.ContainsKey(checkId) || Locations.RandomizedLocations.ContainsKey(checkId)) { if (ItemPickup.itemToGive.name == "Hexagon Red") { SetupRedHexagonPlinth(); } else if (ItemPickup.itemToGive.name == "Hexagon Blue") { @@ -361,7 +363,7 @@ public static void SwapItemsInScene() { foreach (PagePickup PagePickup in Resources.FindObjectsOfTypeAll()) { string ItemId = $"{PagePickup.pageName} [{SceneLoaderPatches.SceneName}]"; - if(ItemLookup.ItemList.ContainsKey(ItemId)) { + if(ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId)) { SetupPagePickup(PagePickup); } } @@ -387,11 +389,11 @@ public static void SwapItemsInScene() { public static void ApplyChestTexture(Chest Chest) { if (Chest != null) { - int Player = Archipelago.instance.GetPlayerSlot(); - string ItemId = Chest.chestID == 0 ? $"{SceneLoaderPatches.SceneName}-{Chest.transform.position.ToString()} [{SceneLoaderPatches.SceneName}]" : $"{Chest.chestID} [{SceneLoaderPatches.SceneName}]"; - if (ItemLookup.ItemList.ContainsKey(ItemId)) { + string ItemName = "Stick"; + if (IsArchipelago() && ItemLookup.ItemList.ContainsKey(ItemId)) { ArchipelagoItem APItem = ItemLookup.ItemList[ItemId]; + ItemName = APItem.ItemName; if (!Archipelago.instance.IsTunicPlayer(APItem.Player) || !ItemLookup.Items.ContainsKey(APItem.ItemName)) { Chest.transform.GetChild(1).GetComponent().materials = Chests["Normal"].GetComponent().materials; Chest.GetComponent().EventReference = Chests["Normal"].GetComponent().EventReference; @@ -399,25 +401,30 @@ public static void ApplyChestTexture(Chest Chest) { ApplyAPChestTexture(Chest, APItem); return; } - ItemData Item = ItemLookup.Items[APItem.ItemName]; - if (Item.Type == ItemTypes.FAIRY) { - Chest.transform.GetChild(1).GetComponent().materials = Chests["Fairy"].GetComponent().materials; - Chest.GetComponent().EventReference = Chests["Fairy"].GetComponent().EventReference; - } else if (Item.Type == ItemTypes.GOLDENTROPHY) { - Chest.transform.GetChild(1).GetComponent().materials = Chests["GoldenTrophy"].GetComponent().materials; - Chest.GetComponent().EventReference = Chests["GoldenTrophy"].GetComponent().EventReference; - } else if (Item.ItemNameForInventory == "Hyperdash") { - Chest.transform.GetChild(1).GetComponent().materials = Chests["Hyperdash"].GetComponent().materials; - Chest.GetComponent().EventReference = Chests["Hyperdash"].GetComponent().EventReference; - } else if (Item.ItemNameForInventory.Contains("Hexagon") && Item.Type != ItemTypes.HEXAGONQUEST) { - Material[] Mats = new Material[] { Items[Item.ItemNameForInventory].GetComponent().material, Items[Item.ItemNameForInventory].GetComponent().material }; - Chest.transform.GetChild(1).GetComponent().materials = Mats; - Chest.GetComponent().EventReference = Chests["Normal"].GetComponent().EventReference; - } else { - Chest.transform.GetChild(1).GetComponent().materials = Chests["Normal"].GetComponent().materials; - Chest.GetComponent().EventReference = Chests["Normal"].GetComponent().EventReference; - } + } else if (IsSinglePlayer() && Locations.RandomizedLocations.ContainsKey(ItemId)) { + Check check = Locations.RandomizedLocations[ItemId]; + ItemName = ItemLookup.GetItemDataFromCheck(check).Name; + } + + ItemData Item = ItemLookup.Items[ItemName]; + if (Item.Type == ItemTypes.FAIRY) { + Chest.transform.GetChild(1).GetComponent().materials = Chests["Fairy"].GetComponent().materials; + Chest.GetComponent().EventReference = Chests["Fairy"].GetComponent().EventReference; + } else if (Item.Type == ItemTypes.GOLDENTROPHY) { + Chest.transform.GetChild(1).GetComponent().materials = Chests["GoldenTrophy"].GetComponent().materials; + Chest.GetComponent().EventReference = Chests["GoldenTrophy"].GetComponent().EventReference; + } else if (Item.ItemNameForInventory == "Hyperdash") { + Chest.transform.GetChild(1).GetComponent().materials = Chests["Hyperdash"].GetComponent().materials; + Chest.GetComponent().EventReference = Chests["Hyperdash"].GetComponent().EventReference; + } else if (Item.ItemNameForInventory.Contains("Hexagon") && Item.Type != ItemTypes.HEXAGONQUEST) { + Material[] Mats = new Material[] { Items[Item.ItemNameForInventory].GetComponent().material, Items[Item.ItemNameForInventory].GetComponent().material }; + Chest.transform.GetChild(1).GetComponent().materials = Mats; + Chest.GetComponent().EventReference = Chests["Normal"].GetComponent().EventReference; + } else { + Chest.transform.GetChild(1).GetComponent().materials = Chests["Normal"].GetComponent().materials; + Chest.GetComponent().EventReference = Chests["Normal"].GetComponent().EventReference; } + } } @@ -462,7 +469,6 @@ public static void CheckCollectedItemFlags() { } } - foreach (ItemPickup ItemPickup in Resources.FindObjectsOfTypeAll()) { if (ItemPickup != null && ItemPickup.itemToGive != null) { string ItemId = $"{ItemPickup.itemToGive.name} [{Scene}]"; @@ -497,11 +503,21 @@ public static void SetupItemPickup(ItemPickup ItemPickup) { if (ItemPickup != null && ItemPickup.itemToGive != null) { string ItemId = $"{ItemPickup.itemToGive.name} [{SceneLoaderPatches.SceneName}]"; - int Player = Archipelago.instance.GetPlayerSlot(); - - if (ItemLookup.ItemList.ContainsKey(ItemId)) { - ArchipelagoItem Item = ItemLookup.ItemList[ItemId]; + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData ItemData = null; + if (ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId)) { + if (IsArchipelago()) { + ApItem = ItemLookup.ItemList[ItemId]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + ItemData = ItemLookup.Items[ApItem.ItemName]; + } + } + if (IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ItemId]; + ItemData = ItemLookup.GetItemDataFromCheck(Check); + } if (Locations.CheckedLocations[ItemId] || SaveFile.GetInt($"{ItemPatches.SaveFileCollectedKey} {ItemId}") == 1) { return; } @@ -514,8 +530,7 @@ public static void SetupItemPickup(ItemPickup ItemPickup) { GameObject.Destroy(ItemPickup.GetComponent()); } - - GameObject NewItem = SetupItemBase(ItemPickup.transform, Item); + GameObject NewItem = SetupItemBase(ItemPickup.transform, ApItem, Check); TransformData TransformData; @@ -526,10 +541,9 @@ public static void SetupItemPickup(ItemPickup ItemPickup) { if (ItemPickup.itemToGive.name == "Key" || ItemPickup.itemToGive.name == "Key (House)") { NewItem.transform.parent.localRotation = SceneLoaderPatches.SceneName == "Overworld Redux" ? new Quaternion(0f, 0f, 0f, 0f) : new Quaternion(0f, 0.7071f, 0f, 0.7071f); } - if (!Archipelago.instance.IsTunicPlayer(Item.Player) || !ItemLookup.Items.ContainsKey(Item.ItemName)) { + if (IsArchipelago() && ItemData == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { TransformData = ItemPositions.SpecificItemPlacement[ItemPickup.itemToGive.name]["Other World"]; } else { - ItemData ItemData = ItemLookup.Items[Item.ItemName]; if (ItemData.ItemNameForInventory.Contains("Trinket - ") || ItemData.ItemNameForInventory == "Mask") { TransformData = ItemPositions.SpecificItemPlacement[ItemPickup.itemToGive.name]["Trinket Card"]; } else if (ItemData.Type == ItemTypes.PAGE || ItemData.Type == ItemTypes.FAIRY) { @@ -542,7 +556,7 @@ public static void SetupItemPickup(ItemPickup ItemPickup) { } else { TransformData = ItemPositions.SpecificItemPlacement[ItemPickup.itemToGive.name]["money large"]; } - } else if (ItemData.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && Item.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (ItemData.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData = ItemPositions.SpecificItemPlacement[ItemPickup.itemToGive.name].ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.SpecificItemPlacement[ItemPickup.itemToGive.name][$"Sword Progression {SwordLevel}"] : ItemPositions.SpecificItemPlacement[ItemPickup.itemToGive.name][ItemData.ItemNameForInventory]; } else { @@ -571,18 +585,35 @@ public static void SetupPagePickup(PagePickup PagePickup) { if (PagePickup != null) { GameObject Page = PagePickup.gameObject.transform.GetChild(2).GetChild(0).gameObject; string ItemId = $"{PagePickup.pageName} [{SceneLoaderPatches.SceneName}]"; - if (ItemLookup.ItemList.ContainsKey(ItemId)) { + + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData Item = null; + + if (ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId)) { if (Locations.CheckedLocations[ItemId] || SaveFile.GetInt($"{ItemPatches.SaveFileCollectedKey} {ItemId}") == 1) { GameObject.Destroy(PagePickup.gameObject); return; } - int Player = Archipelago.instance.GetPlayerSlot(); - ArchipelagoItem APItem = ItemLookup.ItemList[ItemId]; - - if (APItem.Player == Player && ItemLookup.Items.ContainsKey(APItem.ItemName) && ItemLookup.Items[APItem.ItemName].Type == ItemTypes.PAGE) { - return; + if (IsArchipelago()) { + int Player = Archipelago.instance.GetPlayerSlot(); + ApItem = ItemLookup.ItemList[ItemId]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + Item = ItemLookup.Items[ApItem.ItemName]; + if (ItemLookup.Items.ContainsKey(ApItem.ItemName) && ItemLookup.Items[ApItem.ItemName].Type == ItemTypes.PAGE) { + return; + } + } + } + if (IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ItemId]; + Item = ItemLookup.GetItemDataFromCheck(Check); + if (Item.Type == ItemTypes.PAGE) { + return; + } } + Page.transform.localScale = Vector3.one; GameObject.Destroy(Page.transform.GetChild(1)); GameObject.Destroy(Page.GetComponent()); @@ -592,16 +623,14 @@ public static void SetupPagePickup(PagePickup PagePickup) { Page.transform.GetChild(i).gameObject.SetActive(false); } - GameObject NewItem = SetupItemBase(Page.transform, APItem); + GameObject NewItem = SetupItemBase(Page.transform, ApItem, Check); Page.transform.localRotation = Quaternion.Euler(0, 0, 0); TransformData TransformData; - if (!Archipelago.instance.IsTunicPlayer(APItem.Player) || !ItemLookup.Items.ContainsKey(APItem.ItemName)) { + if (IsArchipelago() && Item == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { TransformData = ItemPositions.Techbow["Other World"]; PagePickup.transform.GetChild(2).GetComponent().eulerAnglesPerSecond = new Vector3(0f, 45f, 0f); } else { - ItemData Item = ItemLookup.Items[APItem.ItemName]; - if (Item.Type == ItemTypes.TRINKET) { TransformData = ItemPositions.Techbow["Trinket Card"]; } else if (Item.Type == ItemTypes.MONEY || Item.Type == ItemTypes.FOOLTRAP) { @@ -612,7 +641,7 @@ public static void SetupPagePickup(PagePickup PagePickup) { } else { TransformData = ItemPositions.Techbow["money large"]; } - } else if (Item.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && APItem.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (Item.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData = ItemPositions.Techbow.ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.Techbow[$"Sword Progression {SwordLevel}"] : ItemPositions.Techbow[Item.ItemNameForInventory]; } else { @@ -633,10 +662,9 @@ public static void SetupPagePickup(PagePickup PagePickup) { } - public static GameObject SetupItemBase(Transform Parent, ArchipelagoItem APItem) { + public static GameObject SetupItemBase(Transform Parent, ArchipelagoItem APItem = null, Check Check = null) { GameObject NewItem; - int Player = Archipelago.instance.GetPlayerSlot(); - if (!Archipelago.instance.IsTunicPlayer(APItem.Player) || !ItemLookup.Items.ContainsKey(APItem.ItemName)) { + if (IsArchipelago() && (!Archipelago.instance.IsTunicPlayer(APItem.Player) || !ItemLookup.Items.ContainsKey(APItem.ItemName))) { ItemFlags flag = APItem.Classification; if (flag == ItemFlags.Trap) { flag = new List() { ItemFlags.Advancement, ItemFlags.NeverExclude, ItemFlags.None}[new System.Random().Next(3)]; @@ -649,7 +677,12 @@ public static GameObject SetupItemBase(Transform Parent, ArchipelagoItem APItem) } } } else { - ItemData Item = ItemLookup.Items[APItem.ItemName]; + ItemData Item = ItemLookup.Items["Stick"]; + if (IsArchipelago() && APItem != null) { + Item = ItemLookup.Items[APItem.ItemName]; + } else if (IsSinglePlayer() && Check != null) { + Item = ItemLookup.GetItemDataFromCheck(Check); + } if (Item.Type == ItemTypes.TRINKET) { NewItem = GameObject.Instantiate(Items["Trinket Card"], Parent.transform.position, Parent.transform.rotation); @@ -663,7 +696,7 @@ public static GameObject SetupItemBase(Transform Parent, ArchipelagoItem APItem) NewItem = GameObject.Instantiate(PagePickup, Parent.transform.position, Parent.transform.rotation); } else if (Item.Type == ItemTypes.FAIRY) { NewItem = GameObject.Instantiate(Chests["Fairy"], Parent.transform.position, Parent.transform.rotation); - } else if (Item.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && APItem.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (Item.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || APItem.Player == Archipelago.instance.GetPlayerSlot())) { NewItem = SwordProgressionObject(Parent); } else { NewItem = GameObject.Instantiate(Items[Item.ItemNameForInventory], Parent.transform.position, Parent.transform.rotation); @@ -708,18 +741,34 @@ private static GameObject SwordProgressionObject(Transform Parent) { public static void SetupRedHexagonPlinth() { GameObject Plinth = GameObject.Find("_Hexagon Plinth Assembly/hexagon plinth/PRISM/questagon"); string ItemId = "Hexagon Red [Fortress Arena]"; - int Player = Archipelago.instance.GetPlayerSlot(); - if (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1) { + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData HexagonItem = null; + + if (IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1) { GameObject.Destroy(Plinth); return; } - if (Plinth != null && ItemLookup.ItemList.ContainsKey(ItemId)) { - ArchipelagoItem Item = ItemLookup.ItemList[ItemId]; - if (Item.Player == Player && ItemLookup.Items.ContainsKey(Item.ItemName) && ItemLookup.Items[Item.ItemName].ItemNameForInventory == "Hexagon Red") { - return; + if (Plinth != null && (ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId))) { + if (IsArchipelago()) { + ApItem = ItemLookup.ItemList[ItemId]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + HexagonItem = ItemLookup.Items[ApItem.ItemName]; + if (HexagonItem.ItemNameForInventory == "Hexagon Red") { + return; + } + } } + if(IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ItemId]; + HexagonItem = ItemLookup.GetItemDataFromCheck(Check); + if (HexagonItem.ItemNameForInventory == "Hexagon Red") { + return; + } + } + if (Plinth.GetComponent() != null) { GameObject.Destroy(Plinth.GetComponent()); GameObject.Destroy(Plinth.GetComponent()); @@ -729,13 +778,12 @@ public static void SetupRedHexagonPlinth() { Plinth.transform.GetChild(i).gameObject.SetActive(false); } - GameObject NewItem = SetupItemBase(Plinth.transform, Item); + GameObject NewItem = SetupItemBase(Plinth.transform, ApItem, Check); TransformData TransformData; - if (!Archipelago.instance.IsTunicPlayer(Item.Player) || !ItemLookup.Items.ContainsKey(Item.ItemName)) { + if (IsArchipelago() && HexagonItem == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { TransformData = ItemPositions.HexagonRed["Other World"]; } else { - ItemData HexagonItem = ItemLookup.Items[Item.ItemName]; if (HexagonItem.Type == ItemTypes.TRINKET) { TransformData = ItemPositions.HexagonRed["Trinket Card"]; } else if (HexagonItem.Type == ItemTypes.MONEY || HexagonItem.Type == ItemTypes.FOOLTRAP) { @@ -746,7 +794,7 @@ public static void SetupRedHexagonPlinth() { } else { TransformData = ItemPositions.HexagonRed["money large"]; } - } else if (HexagonItem.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && Item.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (HexagonItem.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData = ItemPositions.HexagonRed.ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.HexagonRed[$"Sword Progression {SwordLevel}"] : ItemPositions.HexagonRed[HexagonItem.ItemNameForInventory]; } else { @@ -766,16 +814,34 @@ public static void SetupRedHexagonPlinth() { public static void SetupBlueHexagonPlinth() { GameObject Plinth = GameObject.Find("_Plinth/turn off when taken/questagon"); string ItemId = "Hexagon Blue [ziggurat2020_3]"; - int Player = Archipelago.instance.GetPlayerSlot(); - if (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1) { + + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData HexagonItem = null; + + if (IsArchipelago() && (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1)) { GameObject.Destroy(Plinth); return; } - if (Plinth != null && ItemLookup.ItemList.ContainsKey(ItemId)) { - ArchipelagoItem Item = ItemLookup.ItemList[ItemId]; - if (Item.Player == Player && ItemLookup.Items.ContainsKey(Item.ItemName) && ItemLookup.Items[Item.ItemName].ItemNameForInventory == "Hexagon Blue") { - return; + if (Plinth != null && (ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId))) { + if (IsArchipelago()) { + int Player = Archipelago.instance.GetPlayerSlot(); + ApItem = ItemLookup.ItemList[ItemId]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + HexagonItem = ItemLookup.Items[ApItem.ItemName]; + if (HexagonItem.ItemNameForInventory == "Hexagon Blue") { + return; + } + } + } + if (IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ItemId]; + HexagonItem = ItemLookup.GetItemDataFromCheck(Check); + if (HexagonItem.ItemNameForInventory == "Hexagon Blue") { + return; + } } + if (Plinth.GetComponent() != null) { GameObject.Destroy(Plinth.GetComponent()); GameObject.Destroy(Plinth.GetComponent()); @@ -785,13 +851,12 @@ public static void SetupBlueHexagonPlinth() { Plinth.transform.GetChild(i).gameObject.SetActive(false); } - GameObject NewItem = SetupItemBase(Plinth.transform, Item); + GameObject NewItem = SetupItemBase(Plinth.transform, ApItem, Check); TransformData TransformData; - if (!Archipelago.instance.IsTunicPlayer(Item.Player) || !ItemLookup.Items.ContainsKey(Item.ItemName)) { + if (IsArchipelago() && HexagonItem == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { TransformData = ItemPositions.HexagonRed["Other World"]; } else { - ItemData HexagonItem = ItemLookup.Items[Item.ItemName]; if (HexagonItem.Type == ItemTypes.TRINKET) { TransformData = ItemPositions.HexagonRed["Trinket Card"]; } else if (HexagonItem.Type == ItemTypes.MONEY || HexagonItem.Type == ItemTypes.FOOLTRAP) { @@ -802,7 +867,7 @@ public static void SetupBlueHexagonPlinth() { } else { TransformData = ItemPositions.HexagonRed["money large"]; } - } else if (HexagonItem.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && Item.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (HexagonItem.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData = ItemPositions.HexagonRed.ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.HexagonRed[$"Sword Progression {SwordLevel}"] : ItemPositions.HexagonRed[HexagonItem.ItemNameForInventory]; } else { @@ -822,18 +887,31 @@ public static void SetupBlueHexagonPlinth() { public static void SwapSiegeEngineCrown() { GameObject VaultKey = GameObject.Find("Spidertank/Spidertank_skeleton/root/thorax/vault key graphic"); + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData VaultKeyItem = null; + string ItemId = "Vault Key (Red) [Fortress Arena]"; - if (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1) { + if (IsArchipelago() && (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {ItemId} was collected") == 1)) { GameObject.Destroy(VaultKey); return; } - if (VaultKey != null && ItemLookup.ItemList.ContainsKey(ItemId)) { - ArchipelagoItem Item = ItemLookup.ItemList[ItemId]; - - int Player = Archipelago.instance.GetPlayerSlot(); - - if (Item.Player == Player && ItemLookup.Items.ContainsKey(Item.ItemName) && ItemLookup.Items[Item.ItemName].ItemNameForInventory == "Vault Key (Red)") { - return; + if (VaultKey != null && (ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId))) { + if (IsArchipelago()) { + ApItem = ItemLookup.ItemList[ItemId]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + VaultKeyItem = ItemLookup.Items[ApItem.ItemName]; + if (VaultKeyItem.ItemNameForInventory == "Vault Key (Red)") { + return; + } + } + } + if (IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ItemId]; + VaultKeyItem = ItemLookup.GetItemDataFromCheck(Check); + if (VaultKeyItem.ItemNameForInventory == "Vault Key (Red)") { + return; + } } if (VaultKey.GetComponent() != null) { GameObject.Destroy(VaultKey.GetComponent()); @@ -844,13 +922,12 @@ public static void SwapSiegeEngineCrown() { VaultKey.transform.GetChild(i).gameObject.SetActive(false); } - GameObject NewItem = SetupItemBase(VaultKey.transform, Item); + GameObject NewItem = SetupItemBase(VaultKey.transform, ApItem, Check); TransformData TransformData; - if (!Archipelago.instance.IsTunicPlayer(Item.Player) || !ItemLookup.Items.ContainsKey(Item.ItemName)) { + if (IsArchipelago() && VaultKeyItem == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { TransformData = ItemPositions.VaultKeyRed["Other World"]; } else { - ItemData VaultKeyItem = ItemLookup.Items[Item.ItemName]; if (VaultKeyItem.Type == ItemTypes.TRINKET) { TransformData = ItemPositions.VaultKeyRed["Trinket Card"]; } else if (VaultKeyItem.Type == ItemTypes.MONEY || VaultKeyItem.Type == ItemTypes.FOOLTRAP) { @@ -861,7 +938,7 @@ public static void SwapSiegeEngineCrown() { } else { TransformData = ItemPositions.VaultKeyRed["money large"]; } - } else if (VaultKeyItem.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && Item.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (VaultKeyItem.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData = ItemPositions.VaultKeyRed.ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.VaultKeyRed[$"Sword Progression {SwordLevel}"] : ItemPositions.VaultKeyRed[VaultKeyItem.ItemNameForInventory]; } else { @@ -879,9 +956,12 @@ public static void SwapSiegeEngineCrown() { public static void SetupShopItems() { - int Player = Archipelago.instance.GetPlayerSlot(); - for (int i = 0; i < ShopItemIDs.Count; i++) { + + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData Item = null; + if (!Locations.CheckedLocations[ShopItemIDs[i]]) { GameObject ItemHolder = GameObject.Find(ShopGameObjectIDs[i]); @@ -897,15 +977,23 @@ public static void SetupShopItems() { for (int j = 1; j < ItemHolder.transform.parent.childCount; j++) { GameObject.Destroy(ItemHolder.transform.parent.GetChild(j).gameObject); } - ArchipelagoItem APItem = ItemLookup.ItemList[ShopItemIDs[i]]; + if (IsArchipelago()) { + ApItem = ItemLookup.ItemList[ShopItemIDs[i]]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + Item = ItemLookup.Items[ApItem.ItemName]; + } + } + if (IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ShopItemIDs[i]]; + Item = ItemLookup.GetItemDataFromCheck(Check); + } - NewItem = SetupItemBase(ItemHolder.transform, APItem); + NewItem = SetupItemBase(ItemHolder.transform, ApItem, Check); TransformData TransformData; - if (!Archipelago.instance.IsTunicPlayer(APItem.Player) || !ItemLookup.Items.ContainsKey(APItem.ItemName)) { + if (IsArchipelago() && Item == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { TransformData = ItemPositions.Shop["Other World"]; } else { - ItemData Item = ItemLookup.Items[APItem.ItemName]; if (Item.Type == ItemTypes.TRINKET) { TransformData = ItemPositions.Shop["Trinket Card"]; } else if (Item.Type == ItemTypes.MONEY || Item.Type == ItemTypes.FOOLTRAP) { @@ -916,7 +1004,7 @@ public static void SetupShopItems() { } else { TransformData = ItemPositions.Shop["money large"]; } - } else if (Item.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && APItem.Player == Archipelago.instance.GetPlayerSlot()) { + } else if (Item.Type == ItemTypes.SWORDUPGRADE && (SaveFile.GetInt(SwordProgressionEnabled) == 1) && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData = ItemPositions.Shop.ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.Shop[$"Sword Progression {SwordLevel}"] : ItemPositions.Shop[Item.ItemNameForInventory]; } else { @@ -941,7 +1029,7 @@ public static void SetupShopItems() { NewItem.transform.GetChild(j).gameObject.layer = 12; } - if (Archipelago.instance.IsTunicPlayer(APItem.Player) && APItem.ItemName.Contains("Ice Bomb")) { + if ((IsSinglePlayer() || Archipelago.instance.IsTunicPlayer(ApItem.Player)) && Item.Name.Contains("Ice Bomb")) { NewItem.transform.GetChild(0).gameObject.SetActive(false); } @@ -952,9 +1040,11 @@ public static void SetupShopItems() { public static void SetupHeroRelicPickup(HeroRelicPickup HeroRelicPickup) { string ItemId = $"{HeroRelicPickup.name} [{SceneLoaderPatches.SceneName}]"; - if (ItemLookup.ItemList.ContainsKey(ItemId)) { + if (ItemLookup.ItemList.ContainsKey(ItemId) || Locations.RandomizedLocations.ContainsKey(ItemId)) { - int Player = Archipelago.instance.GetPlayerSlot(); + ArchipelagoItem ApItem = null; + Check Check = null; + ItemData Item = null; for (int i = 0; i < HeroRelicPickup.transform.childCount; i++) { HeroRelicPickup.transform.GetChild(i).gameObject.SetActive(false); @@ -963,22 +1053,28 @@ public static void SetupHeroRelicPickup(HeroRelicPickup HeroRelicPickup) { GameObject.Destroy(HeroRelicPickup.GetComponent()); GameObject.Destroy(HeroRelicPickup.GetComponent()); } + if (IsArchipelago()) { + ApItem = ItemLookup.ItemList[ItemId]; + if (Archipelago.instance.IsTunicPlayer(ApItem.Player)) { + Item = ItemLookup.Items[ApItem.ItemName]; + } + } + if (IsSinglePlayer()) { + Check = Locations.RandomizedLocations[ItemId]; + Item = ItemLookup.GetItemDataFromCheck(Check); + } + GameObject NewItem = SetupItemBase(HeroRelicPickup.transform, ApItem, Check); - ArchipelagoItem APItem = ItemLookup.ItemList[ItemId]; - GameObject NewItem = SetupItemBase(HeroRelicPickup.transform, APItem); - - if (!Archipelago.instance.IsTunicPlayer(APItem.Player) || !ItemLookup.Items.ContainsKey(APItem.ItemName)) { + if (IsArchipelago() && Item == null && (ApItem != null && !Archipelago.instance.IsTunicPlayer(ApItem.Player) || !ItemLookup.Items.ContainsKey(ApItem.ItemName))) { if (NewItem.GetComponent() == null) { NewItem.AddComponent().eulerAnglesPerSecond = new Vector3(0f, 45f, 0f); } NewItem.transform.localRotation = Quaternion.identity; NewItem.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f); } else { - ItemData Item = ItemLookup.Items[APItem.ItemName]; - NewItem.transform.localRotation = ItemPositions.ItemPickupRotations.ContainsKey(Item.ItemNameForInventory) ? ItemPositions.ItemPickupRotations[Item.ItemNameForInventory] : Quaternion.Euler(0, 0, 0); NewItem.transform.localPosition = ItemPositions.ItemPickupPositions.ContainsKey(Item.ItemNameForInventory) ? ItemPositions.ItemPickupPositions[Item.ItemNameForInventory] : Vector3.zero; - if (Item.Type == ItemTypes.SWORDUPGRADE && SaveFile.GetInt(SwordProgressionEnabled) == 1 && APItem.Player == Archipelago.instance.GetPlayerSlot()) { + if (Item.Type == ItemTypes.SWORDUPGRADE && SaveFile.GetInt(SwordProgressionEnabled) == 1 && (IsSinglePlayer() || ApItem.Player == Archipelago.instance.GetPlayerSlot())) { int SwordLevel = SaveFile.GetInt(SwordProgressionLevel); TransformData TransformData = ItemPositions.Techbow.ContainsKey($"Sword Progression {SwordLevel}") ? ItemPositions.Techbow[$"Sword Progression {SwordLevel}"] : ItemPositions.Techbow[Item.ItemNameForInventory]; NewItem.transform.localPosition = TransformData.pos; @@ -1062,7 +1158,8 @@ public static void ShopManager_entrySequence_MoveNext_PostfixPatch(ShopManager._ __instance.__4__this.transform.GetChild(0).GetChild(10).GetChild(0).GetChild(i).GetChild(0).gameObject.SetActive(true); foreach (ShopItem shopItem in ShopManager.cachedShopItems) { if (ShopItemIDs.Contains($"{shopItem.name} [Shop]") && !Locations.CheckedLocations[$"{shopItem.name} [Shop]"] - && ItemLookup.ItemList[$"{shopItem.name} [Shop]"].ItemName.Contains("Ice Bomb") && Archipelago.instance.IsTunicPlayer(ItemLookup.ItemList[$"{shopItem.name} [Shop]"].Player)) { + && ((IsSinglePlayer() && Locations.RandomizedLocations[$"{shopItem.name} [Shop]"].Reward.Name.Contains("Ice Bomb")) + || (IsArchipelago() && ItemLookup.ItemList[$"{shopItem.name} [Shop]"].ItemName.Contains("Ice Bomb") && Archipelago.instance.IsTunicPlayer(ItemLookup.ItemList[$"{shopItem.name} [Shop]"].Player)))) { shopItem.transform.GetChild(0).GetChild(1).GetChild(0).gameObject.SetActive(true); } } diff --git a/src/Patches/OptionsGUIPatches.cs b/src/Patches/OptionsGUIPatches.cs index 58a9896..adf26be 100644 --- a/src/Patches/OptionsGUIPatches.cs +++ b/src/Patches/OptionsGUIPatches.cs @@ -10,6 +10,8 @@ using BepInEx.Logging; using Newtonsoft.Json; using static TunicArchipelago.SaveFlags; +using static TunicArchipelago.RandomizerSettings; +using UnityEngine.SceneManagement; namespace TunicArchipelago { public class OptionsGUIPatches { @@ -26,14 +28,14 @@ public static bool OptionsGUI_page_root_PrefixPatch(OptionsGUI __instance) { public static void RandomizerSettingsPage() { OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); OptionsGUI.setHeading("Randomizer"); + addPageButton("General Settings", GeneralSettingsPage); + addPageButton("Single Player Settings", LogicSettingsPage); addPageButton("Archipelago Settings", ArchipelagoSettingsPage); - if (SceneLoaderPatches.SceneName != "TitleScreen") { - addPageButton("Logic Settings", LogicSettingsPage); - } addPageButton("Hint Settings", HintsSettingsPage); - addPageButton("General Settings", GeneralSettingsPage); addPageButton("Enemy Randomizer Settings", EnemyRandomizerSettings); addPageButton("Fox Customization", CustomFoxSettingsPage); + addPageButton("Race Mode Settings", RaceSettingsPage); + addPageButton("Other Settings", OtherSettingsPage); } public static void ArchipelagoSettingsPage() { @@ -41,20 +43,54 @@ public static void ArchipelagoSettingsPage() { OptionsGUI.setHeading("Archipelago"); OptionsGUI.addToggle("Death Link", "Off", "On", TunicArchipelago.Settings.DeathLinkEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleDeathLink); OptionsGUI.addToggle("Auto-open !collect-ed Checks", "Off", "On", TunicArchipelago.Settings.CollectReflectsInWorld ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleUpdateOnCollect); - OptionsGUI.addToggle("Skip Item Animations", "Off", "On", TunicArchipelago.Settings.SkipItemAnimations ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleSkipItemAnimations); OptionsGUI.addToggle("Send Hints to Server", "Off", "On", TunicArchipelago.Settings.SendHintsToServer ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleSendHintsToServer); - OptionsGUI.addButton("Open Local Spoiler Log", (Action)OpenLocalSpoilerLog); } public static void LogicSettingsPage() { OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); - OptionsGUI.addButton("Sword Progression", SaveFile.GetInt(SwordProgressionEnabled) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); - OptionsGUI.addButton("Keys Behind Bosses", SaveFile.GetInt(KeysBehindBosses) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); - OptionsGUI.addButton("Hexagon Quest", SaveFile.GetInt(HexagonQuestEnabled) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); - OptionsGUI.addButton("Started With Sword", SaveFile.GetInt("randomizer started with sword") == 1 ? "<#00ff00>Yes" : "<#ff0000>No", null); - OptionsGUI.addButton("Shuffled Abilities", SaveFile.GetInt(AbilityShuffle) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); - OptionsGUI.addButton("Entrance Randomizer", SaveFile.GetInt(EntranceRando) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); - OptionsGUI.setHeading("Logic"); + Il2CppStringArray GameModes = (Il2CppStringArray)new string[] { "<#FFA300>Randomizer", "<#ffd700>Hexagon Quest", "<#4FF5D4>Vanilla" }; + Il2CppStringArray LaurelsLocations = (Il2CppStringArray)new string[] { "<#FFA300>Random", "<#ffd700>6 Coins", "<#ffd700>10 Coins", "<#ffd700>10 Fairies" }; + Il2CppStringArray FoolTrapOptions = (Il2CppStringArray)new string[] { "<#FFFFFF>None", "<#4FF5D4>Normal", "<#E3D457>Double", "<#FF3333>Onslaught" }; + + OptionsGUI.setHeading("Single Player"); + + if (SceneManager.GetActiveScene().name == "TitleScreen" || IsArchipelago()) { + OptionsGUI.addMultiSelect("Game Mode", GameModes, GetGameModeIndex(), (OptionsGUIMultiSelect.MultiSelectAction)ChangeGameMode).wrap = true; + OptionsGUI.addToggle("Keys Behind Bosses", "Off", "On", TunicArchipelago.Settings.KeysBehindBosses ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleKeysBehindBosses); + OptionsGUI.addToggle("Sword Progression", "Off", "On", TunicArchipelago.Settings.SwordProgressionEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleSwordProgression); + OptionsGUI.addToggle("Start With Sword", "Off", "On", TunicArchipelago.Settings.StartWithSwordEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleStartWithSword); + OptionsGUI.addToggle("Shuffle Abilities", "Off", "On", TunicArchipelago.Settings.ShuffleAbilities ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleAbilityShuffling); + OptionsGUI.addToggle("Entrance Randomizer", "Off", "On", TunicArchipelago.Settings.EntranceRandoEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleEntranceRando); + OptionsGUI.addToggle("Entrance Randomizer: Fewer Shops", "Off", "On", TunicArchipelago.Settings.EntranceRandoEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleFixedShop); + OptionsGUI.addMultiSelect("Fool Traps", FoolTrapOptions, GetFoolTrapIndex(), (OptionsGUIMultiSelect.MultiSelectAction)ChangeFoolTrapFrequency).wrap = true; + OptionsGUI.addMultiSelect("Laurels Location", LaurelsLocations, GetLaurelsLocationIndex(), (OptionsGUIMultiSelect.MultiSelectAction)ChangeLaurelsLocation).wrap = true; + OptionsGUI.addToggle("Lanternless Logic", "Off", "On", TunicArchipelago.Settings.Lanternless ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleLanternless); + OptionsGUI.addToggle("Maskless Logic", "Off", "On", TunicArchipelago.Settings.Maskless ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleMaskless); + OptionsGUI.addToggle("Mystery Seed", "Off", "On", TunicArchipelago.Settings.MysterySeed ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleMysterySeed); + + } else { + if (SaveFile.GetInt("randomizer mystery seed") == 1) { + OptionsGUI.addButton("Mystery Seed", "<#00ff00>On", null); + return; + } + OptionsGUI.addButton("Game Mode", SaveFile.GetString("randomizer game mode"), null); + if (SaveFile.GetInt("randomizer hexagon quest enabled") == 1) { + OptionsGUI.addButton("Hexagon Quest Goal", SaveFile.GetInt("randomizer hexagon quest goal").ToString(), null); + OptionsGUI.addButton("Hexagons in Item Pool", ((int)Math.Round((100f + SaveFile.GetInt("randomizer hexagon quest extras")) / 100f * SaveFile.GetInt("randomizer hexagon quest goal"))).ToString(), null); + } + OptionsGUI.addButton("Keys Behind Bosses", SaveFile.GetInt("randomizer keys behind bosses") == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + OptionsGUI.addButton("Sword Progression", SaveFile.GetInt("randomizer sword progression enabled") == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + OptionsGUI.addButton("Started With Sword", SaveFile.GetInt("randomizer started with sword") == 1 ? "<#00ff00>Yes" : "<#ff0000>No", null); + OptionsGUI.addButton("Shuffled Abilities", SaveFile.GetInt("randomizer shuffled abilities") == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + OptionsGUI.addButton("Entrance Randomizer", SaveFile.GetInt("randomizer entrance rando enabled") == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1 && IsSinglePlayer()) { + OptionsGUI.addButton("Entrance Randomizer: Fewer Shops", SaveFile.GetInt("randomizer ER fixed shop") == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + } + OptionsGUI.addButton("Laurels Location", LaurelsLocations[SaveFile.GetInt("randomizer laurels location")], null); + OptionsGUI.addButton("Lanternless Logic", SaveFile.GetInt(LanternlessLogic) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + OptionsGUI.addButton("Maskless Logic", SaveFile.GetInt(MasklessLogic) == 1 ? "<#00ff00>On" : "<#ff0000>Off", null); + OptionsGUI.addMultiSelect("Fool Traps", FoolTrapOptions, GetFoolTrapIndex(), (OptionsGUIMultiSelect.MultiSelectAction)ChangeFoolTrapFrequency).wrap = true; + } } public static void HintsSettingsPage() { @@ -63,12 +99,13 @@ public static void HintsSettingsPage() { OptionsGUI.addToggle("Ghost Fox Hints", "Off", "On", TunicArchipelago.Settings.GhostFoxHintsEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleGhostFoxHints); OptionsGUI.addToggle("Freestanding Items Match Contents", "Off", "On", TunicArchipelago.Settings.ShowItemsEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleShowItems); OptionsGUI.addToggle("Chests Match Contents", "Off", "On", TunicArchipelago.Settings.ChestsMatchContentsEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleChestsMatchContents); + OptionsGUI.addToggle("Display Hints in Trunic", "Off", "On", TunicArchipelago.Settings.UseTrunicTranslations ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleTrunicHints); + OptionsGUI.addToggle("Spoiler Log", "Off", "On", TunicArchipelago.Settings.CreateSpoilerLog ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleSpoilerLog); + OptionsGUI.addButton("Open Spoiler Log", (Action)OpenLocalSpoilerLog); OptionsGUI.setHeading("Hints"); } public static void GeneralSettingsPage() { - Il2CppStringArray FoolTrapOptions = (Il2CppStringArray)new string[] { "<#FFFFFF>None", "<#4FF5D4>Normal", "<#E3D457>Double", "<#FF3333>Onslaught" }; - OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); OptionsGUI.setHeading("General"); OptionsGUI.addToggle("Easier Heir Fight", "Off", "On", TunicArchipelago.Settings.HeirAssistModeEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleHeirAssistMode); @@ -76,10 +113,8 @@ public static void GeneralSettingsPage() { OptionsGUI.addToggle("Cheaper Shop Items", "Off", "On", TunicArchipelago.Settings.CheaperShopItemsEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleCheaperShopItems); OptionsGUI.addToggle("Bonus Upgrades", "Off", "On", TunicArchipelago.Settings.BonusStatUpgradesEnabled ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleBonusStatUpgrades); OptionsGUI.addToggle("Disable Chest Interruption", "Off", "On", TunicArchipelago.Settings.DisableChestInterruption ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleChestInterruption); - OptionsGUI.addToggle("Skip Upgrade Animation", "Off", "On", TunicArchipelago.Settings.FasterUpgrades ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleFasterUpgrades); - OptionsGUI.addToggle("???", "Off", "On", CameraController.Flip ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleWeirdMode); - OptionsGUI.addToggle("More Skulls", "Off", "On", TunicArchipelago.Settings.MoreSkulls ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleMoreSkulls); - OptionsGUI.addToggle("Arachnophobia Mode", "Off", "On", TunicArchipelago.Settings.ArachnophobiaMode ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleArachnophobiaMode); + OptionsGUI.addToggle("Skip Item Popups", "Off", "On", TunicArchipelago.Settings.SkipItemAnimations ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleSkipItemAnimations); + OptionsGUI.addToggle("Skip Upgrade Animations", "Off", "On", TunicArchipelago.Settings.FasterUpgrades ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleFasterUpgrades); } public static void EnemyRandomizerSettings() { @@ -112,6 +147,26 @@ public static void CustomFoxSettingsPage() { OptionsGUI.addButton("Reset to Defaults", (Action)ResetToDefaults); } + public static void RaceSettingsPage() { + OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); + OptionsGUI.setHeading("Race Time"); + OptionsGUI.addToggle("Race Mode (Enables Race Options)", "Off", "On", TunicArchipelago.Settings.RaceMode ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleRaceMode); + OptionsGUI.addToggle("Disable Icebolt in Heir Fight", "Off", "On", TunicArchipelago.Settings.DisableIceboltInHeirFight ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleDisableHeirIcebolt); + OptionsGUI.addToggle("Disable Distant West Bell Shot", "Off", "On", TunicArchipelago.Settings.DisableDistantBellShots ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleDisableDistantDong); + OptionsGUI.addToggle("Disable Ice Grappling Enemies", "Off", "On", TunicArchipelago.Settings.DisableIceGrappling ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleDisableIceGrapples); + OptionsGUI.addToggle("Disable Ladder Storage", "Off", "On", TunicArchipelago.Settings.DisableLadderStorage ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleDisableLadderStorage); + OptionsGUI.addToggle("Disable Upgrade Stealing", "Off", "On", TunicArchipelago.Settings.DisableUpgradeStealing ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleDisableUpgradeStealing); + } + + public static void OtherSettingsPage() { + OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); + OptionsGUI.setHeading("Other"); + OptionsGUI.addToggle("???", "Off", "On", CameraController.Flip ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleWeirdMode); + OptionsGUI.addToggle("More Skulls", "Off", "On", TunicArchipelago.Settings.MoreSkulls ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleMoreSkulls); + OptionsGUI.addToggle("Arachnophobia Mode", "Off", "On", TunicArchipelago.Settings.ArachnophobiaMode ? 1 : 0, (OptionsGUIMultiSelect.MultiSelectAction)ToggleArachnophobiaMode); + + } + public static void addPageButton(string pageName, Action pageMethod) { Action pushPageAction = new Action(pushPage); OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); @@ -119,13 +174,88 @@ public static void addPageButton(string pageName, Action pageMethod) { pushPageAction.Invoke(pageMethod); }); } - + public static void pushPage(Action pageMethod) { OptionsGUI OptionsGUI = GameObject.FindObjectOfType(); OptionsGUI.pushPage(DelegateSupport.ConvertDelegate(pageMethod)); OptionsGUI.addButton("Return", new Action(OptionsGUI.popPage)); } + // Logic Settings + + public static void ChangeGameMode(int index) { + TunicArchipelago.Settings.GameMode = (RandomizerSettings.GameModes)index; + SaveSettings(); + } + + public static int GetGameModeIndex() { + return (int)TunicArchipelago.Settings.GameMode; + } + + public static void ChangeLaurelsLocation(int index) { + TunicArchipelago.Settings.FixedLaurelsOption = (RandomizerSettings.FixedLaurelsType)index; + SaveSettings(); + } + + public static int GetLaurelsLocationIndex() { + return (int)TunicArchipelago.Settings.FixedLaurelsOption; + } + + public static void ToggleKeysBehindBosses(int index) { + TunicArchipelago.Settings.KeysBehindBosses = !TunicArchipelago.Settings.KeysBehindBosses; + SaveSettings(); + } + + public static void ToggleStartWithSword(int index) { + TunicArchipelago.Settings.StartWithSwordEnabled = !TunicArchipelago.Settings.StartWithSwordEnabled; + SaveSettings(); + } + + public static void ToggleSwordProgression(int index) { + TunicArchipelago.Settings.SwordProgressionEnabled = !TunicArchipelago.Settings.SwordProgressionEnabled; + SaveSettings(); + } + + public static void ToggleAbilityShuffling(int index) { + TunicArchipelago.Settings.ShuffleAbilities = !TunicArchipelago.Settings.ShuffleAbilities; + SaveSettings(); + } + + public static void ToggleEntranceRando(int index) { + TunicArchipelago.Settings.EntranceRandoEnabled = !TunicArchipelago.Settings.EntranceRandoEnabled; + SaveSettings(); + } + + public static void ToggleFixedShop(int index) { + TunicArchipelago.Settings.ERFixedShop = !TunicArchipelago.Settings.ERFixedShop; + SaveSettings(); + } + + public static void ToggleLanternless(int index) { + TunicArchipelago.Settings.Lanternless = !TunicArchipelago.Settings.Lanternless; + SaveSettings(); + } + + public static void ToggleMaskless(int index) { + TunicArchipelago.Settings.Maskless = !TunicArchipelago.Settings.Maskless; + SaveSettings(); + } + + public static void ToggleMysterySeed(int index) { + TunicArchipelago.Settings.MysterySeed = !TunicArchipelago.Settings.MysterySeed; + SaveSettings(); + } + + public static int GetFoolTrapIndex() { + return (int)TunicArchipelago.Settings.FoolTrapIntensity; + } + + public static void ChangeFoolTrapFrequency(int index) { + + TunicArchipelago.Settings.FoolTrapIntensity = (RandomizerSettings.FoolTrapOption)index; + SaveSettings(); + } + public static void ToggleDeathLink(int index) { TunicArchipelago.Settings.DeathLinkEnabled = !TunicArchipelago.Settings.DeathLinkEnabled; @@ -145,11 +275,6 @@ public static void ToggleUpdateOnCollect(int index) { SaveSettings(); } - public static void ToggleSkipItemAnimations(int index) { - TunicArchipelago.Settings.SkipItemAnimations = !TunicArchipelago.Settings.SkipItemAnimations; - SaveSettings(); - } - public static void ToggleSendHintsToServer(int index) { TunicArchipelago.Settings.SendHintsToServer = !TunicArchipelago.Settings.SendHintsToServer; SaveSettings(); @@ -208,6 +333,21 @@ public static void ToggleChestsMatchContents(int index) { SaveSettings(); } + public static void ToggleTrunicHints(int index) { + TunicArchipelago.Settings.UseTrunicTranslations = !TunicArchipelago.Settings.UseTrunicTranslations; + if (SceneManager.GetActiveScene().name != "TitleScreen") { + Hints.PopulateHints(); + GhostHints.GenerateHints(); + Hints.SetupHeroGraveToggle(); + } + SaveSettings(); + } + + public static void ToggleSpoilerLog(int index) { + TunicArchipelago.Settings.CreateSpoilerLog = !TunicArchipelago.Settings.CreateSpoilerLog; + SaveSettings(); + } + // Gameplay public static void ToggleHeirAssistMode(int index) { @@ -235,6 +375,12 @@ public static void ToggleChestInterruption(int index) { SaveSettings(); } + public static void ToggleSkipItemAnimations(int index) { + TunicArchipelago.Settings.SkipItemAnimations = !TunicArchipelago.Settings.SkipItemAnimations; + SaveSettings(); + } + + public static void ToggleFasterUpgrades(int index) { TunicArchipelago.Settings.FasterUpgrades = !TunicArchipelago.Settings.FasterUpgrades; SaveSettings(); @@ -304,14 +450,18 @@ public static void TogglePaletteEditor(int index) { public static void ToggleCustomTexture(int index) { TunicArchipelago.Settings.UseCustomTexture = !TunicArchipelago.Settings.UseCustomTexture; - if (TunicArchipelago.Settings.UseCustomTexture) { - PaletteEditor.LoadCustomTexture(); - } else { - if (TunicArchipelago.Settings.RandomFoxColorsEnabled) { - PaletteEditor.RandomizeFoxColors(); + try { + if (TunicArchipelago.Settings.UseCustomTexture) { + PaletteEditor.LoadCustomTexture(); } else { - PaletteEditor.RevertFoxColors(); + if (TunicArchipelago.Settings.RandomFoxColorsEnabled) { + PaletteEditor.RandomizeFoxColors(); + } else { + PaletteEditor.RevertFoxColors(); + } } + } catch (Exception e) { + } } @@ -341,9 +491,39 @@ public static void ResetToDefaults() { PaletteEditor.RevertFoxColors(); } + // Race Settings + public static void ToggleRaceMode(int index) { + TunicArchipelago.Settings.RaceMode = !TunicArchipelago.Settings.RaceMode; + SaveSettings(); + } + + public static void ToggleDisableHeirIcebolt(int index) { + TunicArchipelago.Settings.DisableIceboltInHeirFight = !TunicArchipelago.Settings.DisableIceboltInHeirFight; + SaveSettings(); + } + + public static void ToggleDisableDistantDong(int index) { + TunicArchipelago.Settings.DisableDistantBellShots = !TunicArchipelago.Settings.DisableDistantBellShots; + SaveSettings(); + } + + public static void ToggleDisableIceGrapples(int index) { + TunicArchipelago.Settings.DisableIceGrappling = !TunicArchipelago.Settings.DisableIceGrappling; + SaveSettings(); + } + + public static void ToggleDisableLadderStorage(int index) { + TunicArchipelago.Settings.DisableLadderStorage = !TunicArchipelago.Settings.DisableLadderStorage; + SaveSettings(); + } + public static void ToggleDisableUpgradeStealing(int index) { + TunicArchipelago.Settings.DisableUpgradeStealing = !TunicArchipelago.Settings.DisableUpgradeStealing; + SaveSettings(); + } + public static void SaveFile_GetNewSaveFileName_PostfixPatch(SaveFile __instance, ref string __result) { - __result = $"{__result.Split('.')[0]}-archipelago.tunic"; + __result = $"{__result.Split('.')[0]}-{(TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO ? "archipelago" : "randomizer")}.tunic"; } public static void FileManagementGUI_rePopulateList_PostfixPatch(FileManagementGUI __instance) { diff --git a/src/Patches/PaletteEditor.cs b/src/Patches/PaletteEditor.cs index 614fa30..1b1cf4d 100644 --- a/src/Patches/PaletteEditor.cs +++ b/src/Patches/PaletteEditor.cs @@ -418,7 +418,7 @@ public static void SetupFoxCape(PlayerCharacter player) { GameObject CapePresentation = Resources.FindObjectsOfTypeAll().Where(ipg => ipg.name == "cape").First().gameObject; CapePresentation.GetComponent().mesh = player.transform.GetChild(2).GetComponent().sharedMesh; CapePresentation.GetComponent().materials = player.transform.GetChild(1).GetComponent().originalMaterials; - + ChangeCapeColor(new Color(0.9882353f, 0.4431373f, 0.945098f)); } } diff --git a/src/Patches/PlayerCharacterPatches.cs b/src/Patches/PlayerCharacterPatches.cs index bfb7eac..1076415 100644 --- a/src/Patches/PlayerCharacterPatches.cs +++ b/src/Patches/PlayerCharacterPatches.cs @@ -37,6 +37,13 @@ public class PlayerCharacterPatches { public static float FinishLineSwordTimer = 0.0f; public static float CompletionTimer = 0.0f; public static float ResetDayNightTimer = -1.0f; + public static LadderEnd LastLadder = null; + + public static void PlayerCharacter_creature_Awake_PostfixPatch(PlayerCharacter __instance) { + + __instance.gameObject.AddComponent(); + __instance.gameObject.AddComponent(); + } public static void PlayerCharacter_Update_PostfixPatch(PlayerCharacter __instance) { Cheats.FastForward = Input.GetKey(KeyCode.Backslash); @@ -53,12 +60,31 @@ public static void PlayerCharacter_Update_PostfixPatch(PlayerCharacter __instanc SpeedrunFinishlineDisplayPatches.CompletionCanvas.SetActive(!SpeedrunFinishlineDisplayPatches.CompletionCanvas.active); } } - - if (Input.GetKeyDown(KeyCode.R)) { + + if (Input.GetKeyDown(KeyCode.Alpha2) && IsSinglePlayer()) { + if (SaveFile.GetInt("randomizer mystery seed") == 1) { + GenericPrompt.ShowPrompt($"\"Copy Current Game Settings?\"\n\"-----------------\"\n" + + $"\"Seed.................{SaveFile.GetInt("seed").ToString().PadLeft(12, '.')}\"\n" + + $"\"Mystery Seed.........{"<#00ff00>On".PadLeft(21, '.')}\"", + (Il2CppSystem.Action)QuickSettings.CopyQuickSettingsInGame, null); + } else { + GenericPrompt.ShowPrompt($"\"Copy Current Game Settings?\"\n\"-----------------\"\n" + + $"\"Seed.................{SaveFile.GetInt("seed").ToString().PadLeft(12, '.')}\"\n" + + $"\"Game Mode............{SaveFile.GetString("randomizer game mode").PadLeft(12, '.')}\"\n" + + $"\"Keys Behind Bosses...{(SaveFile.GetInt("randomizer keys behind bosses") == 0 ? "<#ff0000>Off" : "<#00ff00>On").PadLeft(21, '.')}\"\n" + + $"\"Sword Progression....{(SaveFile.GetInt("randomizer sword progression enabled") == 0 ? "<#ff0000>Off" : "<#00ff00>On").PadLeft(21, '.')}\"\n" + + $"\"Started With Sword...{(SaveFile.GetInt("randomizer started with sword") == 0 ? "<#ff0000>No" : "<#00ff00>Yes").PadLeft(21, '.')}\"\n" + + $"\"Shuffled Abilities...{(SaveFile.GetInt("randomizer shuffled abilities") == 0 ? "<#ff0000>Off" : "<#00ff00>On").PadLeft(21, '.')}\"\n" + + $"\"Entrance Randomizer..{(SaveFile.GetInt("randomizer entrance rando enabled") == 0 ? "<#ff0000>Off" : "<#00ff00>On").PadLeft(21, '.')}\"", + (Il2CppSystem.Action)QuickSettings.CopyQuickSettingsInGame, null); + } + } + + if (Input.GetKeyDown(KeyCode.R) && IsArchipelago()) { Archipelago.instance.Release(); } - if (Input.GetKeyDown(KeyCode.C)) { + if (Input.GetKeyDown(KeyCode.C) && IsArchipelago()) { Archipelago.instance.Collect(); } @@ -102,6 +128,10 @@ public static void PlayerCharacter_Update_PostfixPatch(PlayerCharacter __instanc ResetDayNightTimer = -1.0f; } } + if (SpeedrunData.gameComplete != 0 && !SpeedrunFinishlineDisplayPatches.GameCompleted) { + SpeedrunFinishlineDisplayPatches.GameCompleted = true; + SpeedrunFinishlineDisplayPatches.SetupCompletionStatsDisplay(); + } if (SpeedrunFinishlineDisplayPatches.ShowCompletionStatsAfterDelay) { CompletionTimer += Time.fixedUnscaledDeltaTime; if (CompletionTimer > 6.0f) { @@ -129,11 +159,43 @@ public static void PlayerCharacter_Update_PostfixPatch(PlayerCharacter __instanc if(SaveFile.GetInt(PrayerUnlocked) == 0) { __instance.prayerBeginTimer = 0; } - if(SaveFile.GetInt(IceRodUnlocked) == 0) { + if(SaveFile.GetInt(IceBoltUnlocked) == 0) { TechbowItemBehaviour.kIceShotWindow = 0; } } + if (TunicArchipelago.Settings.RaceMode) { + // Disables icebolt in heir arena + if (TunicArchipelago.Settings.DisableIceboltInHeirFight && SceneManager.GetActiveScene().name == "Spirit Arena") { + TechbowItemBehaviour.kIceShotWindow = 0; + } + // Prevents ladder storage from being used + if (TunicArchipelago.Settings.DisableLadderStorage && __instance.currentLadder != null) { + if (__instance.cachedAnimator.GetBool("climbing") && __instance.cachedAnimator.GetBool("sprint")) { + if (__instance.transform.position.x > LastLadder.transform.position.x + 5 || __instance.transform.position.x < LastLadder.transform.position.x - 5 + || __instance.transform.position.z > LastLadder.transform.position.z + 5 || __instance.transform.position.z < LastLadder.transform.position.z - 5) { + + if (LastLadder != null) { + __instance.currentLadder.ClimbOn(LastLadder); + } else { + __instance.cachedAnimator.SetBool("climbing", false); + __instance.currentLadder = null; + __instance.Flinch(true); + } + } + } + if (__instance.cachedAnimator.GetBool("climbing") && (__instance.cachedAnimator.GetBool("swing sword") || __instance.cachedAnimator.GetBool("swing stick"))) { + __instance.cachedAnimator.SetBool("climbing", false); + __instance.currentLadder = null; + __instance.Flinch(true); + } + } + } + + if (__instance.currentLadder == null && LastLadder != null) { + LastLadder = null; + } + if (PaletteEditor.FoxCape != null) { PaletteEditor.FoxCape.GetComponent().UseSpecialGhostMat = __instance.transform.GetChild(1).GetComponent().UseSpecialGhostMat; } @@ -145,7 +207,6 @@ public static void PlayerCharacter_Update_PostfixPatch(PlayerCharacter __instanc } - public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance) { SceneLoaderPatches.TimeOfLastSceneTransition = SaveFile.GetFloat("playtime"); @@ -158,15 +219,6 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance InvButton.SetActive(false); } - if (!Archipelago.instance.integration.connected) { - Archipelago.instance.Connect(); - } else { - if (TunicArchipelago.Settings.DeathLinkEnabled) { - Archipelago.instance.integration.EnableDeathLink(); - } else { - Archipelago.instance.integration.DisableDeathLink(); - } - } if (Locations.AllScenes.Count == 0) { for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++) { string SceneName = Path.GetFileNameWithoutExtension(SceneUtility.GetScenePathByBuildIndex(i)); @@ -174,10 +226,6 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance } } - if (PaletteEditor.ToonFox.GetComponent() == null) { - PaletteEditor.ToonFox.AddComponent().material = __instance.transform.GetChild(25).GetComponent().material; - } - StateVariable.GetStateVariableByName("SV_ShopTrigger_Fortress").BoolValue = true; StateVariable.GetStateVariableByName("SV_ShopTrigger_Sewer").BoolValue = true; StateVariable.GetStateVariableByName("SV_ShopTrigger_Swamp(Night)").BoolValue = true; @@ -200,19 +248,200 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance LoadSwords = true; + ItemPresentationPatches.SwitchDathStonePresentation(); + + int seed = SaveFile.GetInt("seed"); + + if (seed == 0 && SaveFile.GetInt("archipelago") == 0 && SaveFile.GetInt("randomizer") == 0) { + if (TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER) { + SaveFile.SetInt("randomizer", 1); + } else if (TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO) { + SaveFile.SetInt("archipelago", 1); + } + SaveFile.SaveToDisk(); + } + + if (IsSinglePlayer()) { + Archipelago.instance.Disconnect(); + PlayerCharacter_Start_SinglePlayerSetup(); + } else if (IsArchipelago()) { + PlayerCharacter_Start_ArchipelagoSetup(); + } + + if (TunicArchipelago.Settings.CreateSpoilerLog && !TunicArchipelago.Settings.RaceMode) { + ItemTracker.PopulateSpoilerLog(); + } + + Hints.PopulateHints(); + + GhostHints.GenerateHints(); + + if (Hints.HeroGraveHints.Count != 0) { + Hints.SetupHeroGraveToggle(); + } + + if (SaveFile.GetInt(AbilityShuffle) == 1 && SaveFile.GetInt(HolyCrossUnlocked) == 0) { + ItemPatches.ToggleHolyCrossObjects(false); + } + + if (SaveFile.GetInt(HexagonQuestEnabled) == 1) { + TunicArchipelago.Tracker.ImportantItems["Pages"] = 28; + SaveFile.SetInt("last page viewed", 0); + } + + try { + FairyTargets.CreateFairyTargets(); + FairyTargets.CreateEntranceTargets(); + FairyTargets.FindFairyTargets(); + } catch (Exception ex) { + Logger.LogError("An error occurred creating new fairy seeker spell targets:"); + Logger.LogError(ex.Message + " " + ex.StackTrace); + } + + if (!SceneLoaderPatches.SpawnedGhosts) { + GhostHints.SpawnHintGhosts(SceneLoaderPatches.SceneName); + } + + ItemStatsHUD.UpdateAbilitySection(); + + OptionsGUIPatches.SaveSettings(); + + if (!ModelSwaps.SwappedThisSceneAlready) { + ModelSwaps.SwapItemsInScene(); + } + // this is here for the first time you're loading in, assumes you're in Overworld + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { + TunicPortals.AltModifyPortals(); + } + + if (PaletteEditor.ToonFox.GetComponent() == null) { + PaletteEditor.ToonFox.AddComponent().material = __instance.transform.GetChild(25).GetComponent().material; + } + + PaletteEditor.GatherHyperdashRenderers(); + PaletteEditor.SetupPartyHat(__instance); + PaletteEditor.SetupFoxCape(__instance); + + if (TunicArchipelago.Settings.RandomFoxColorsEnabled) { + PaletteEditor.RandomizeFoxColors(); + } + + if (TunicArchipelago.Settings.UseCustomTexture) { + LoadCustomTexture = true; + } + + if (TunicArchipelago.Settings.RealestAlwaysOn) { + GameObject.FindObjectOfType().SpellEffect(); + } + + if (PaletteEditor.CelShadingEnabled) { + PaletteEditor.ApplyCelShading(); + } + + if (PaletteEditor.PartyHatEnabled) { + WearHat = true; + } + } + + private static void PlayerCharacter_Start_SinglePlayerSetup() { + int seed = SaveFile.GetInt("seed"); + + if (seed == 0) { + seed = QuickSettings.CustomSeed == "" ? new System.Random().Next() : int.Parse(QuickSettings.CustomSeed); + Logger.LogInfo($"Starting new single player file with seed: " + seed); + SaveFile.SetInt("seed", seed); + SaveFile.SetInt("randomizer", 1); + + if (TunicArchipelago.Settings.MysterySeed) { + SaveFile.SetInt("randomizer mystery seed", 1); + GenerateMysterySettings(); + } else { + SaveFile.SetString("randomizer game mode", Enum.GetName(typeof(RandomizerSettings.GameModes), TunicArchipelago.Settings.GameMode)); + if (TunicArchipelago.Settings.GameMode == RandomizerSettings.GameModes.HEXAGONQUEST) { + SaveFile.SetInt(HexagonQuestEnabled, 1); + SaveFile.SetInt("randomizer hexagon quest goal", TunicArchipelago.Settings.HexagonQuestGoal); + SaveFile.SetInt("randomizer hexagon quest extras", TunicArchipelago.Settings.HexagonQuestExtraPercentage); + + for (int i = 0; i < 28; i++) { + SaveFile.SetInt($"randomizer obtained page {i}", 1); + } + + StateVariable.GetStateVariableByName("Placed Hexagon 1 Red").BoolValue = true; + StateVariable.GetStateVariableByName("Placed Hexagon 2 Green").BoolValue = true; + StateVariable.GetStateVariableByName("Placed Hexagon 3 Blue").BoolValue = true; + StateVariable.GetStateVariableByName("Placed Hexagons ALL").BoolValue = true; + StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue = true; + StateVariable.GetStateVariableByName("Has Died To God").BoolValue = true; + } + if (TunicArchipelago.Settings.SwordProgressionEnabled) { + SaveFile.SetInt("randomizer sword progression enabled", 1); + SaveFile.SetInt("randomizer sword progression level", 0); + } + if (TunicArchipelago.Settings.KeysBehindBosses) { + SaveFile.SetInt("randomizer keys behind bosses", 1); + } + if (TunicArchipelago.Settings.StartWithSwordEnabled) { + Inventory.GetItemByName("Sword").Quantity = 1; + SaveFile.SetInt("randomizer started with sword", 1); + } + + if (TunicArchipelago.Settings.Maskless) { + SaveFile.SetInt(MasklessLogic, 1); + } + if (TunicArchipelago.Settings.Lanternless) { + SaveFile.SetInt(LanternlessLogic, 1); + } + + SaveFile.SetInt("randomizer laurels location", (int)TunicArchipelago.Settings.FixedLaurelsOption); + + if (TunicArchipelago.Settings.EntranceRandoEnabled) { + Inventory.GetItemByName("Torch").Quantity = 1; + SaveFile.SetInt("randomizer entrance rando enabled", 1); + } + if (TunicArchipelago.Settings.ERFixedShop) { + SaveFile.SetInt("randomizer ER fixed shop", 1); + } + if (TunicArchipelago.Settings.ShuffleAbilities) { + SaveFile.SetInt("randomizer shuffled abilities", 1); + } + } + + foreach (string Scene in Locations.AllScenes) { + SaveFile.SetFloat($"randomizer play time {Scene}", 0.0f); + } + + EnemyRandomizer.CreateAreaSeeds(); + + SaveFile.SaveToDisk(); + } + TunicArchipelago.Tracker = new ItemTracker(); + TunicArchipelago.Tracker.Seed = seed; + Logger.LogInfo("Loading single player seed: " + seed); + ItemRandomizer.PopulateSphereZero(); + ItemRandomizer.RandomizeAndPlaceItems(); + } + + private static void PlayerCharacter_Start_ArchipelagoSetup() { + if (!Archipelago.instance.integration.connected) { + Archipelago.instance.Connect(); + } else { + if (TunicArchipelago.Settings.DeathLinkEnabled) { + Archipelago.instance.integration.EnableDeathLink(); + } else { + Archipelago.instance.integration.DisableDeathLink(); + } + } + if (Archipelago.instance.integration.connected) { Archipelago.instance.integration.sentCompletion = false; Archipelago.instance.integration.sentRelease = false; Archipelago.instance.integration.sentCollect = false; Dictionary slotData = Archipelago.instance.GetPlayerSlotData(); - SaveFile.SetInt("archipelago", 1); if (SaveFile.GetString("archipelago player name") == "") { SaveFile.SetString("archipelago player name", TunicArchipelago.Settings.ConnectionSettings.Player); } - if (Locations.VanillaLocations.Count == 0) { - Locations.CreateLocationLookups(); - } + if (slotData.TryGetValue("hexagon_quest", out var hexagonQuest)) { if (SaveFile.GetInt(HexagonQuestEnabled) == 0 && hexagonQuest.ToString() == "1") { SaveFile.SetInt(HexagonQuestEnabled, 1); @@ -225,8 +454,8 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance StateVariable.GetStateVariableByName("Placed Hexagons ALL").BoolValue = true; StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue = true; StateVariable.GetStateVariableByName("Has Died To God").BoolValue = true; - - if(slotData.TryGetValue("Hexagon Quest Goal", out var hexagonGoal)) { + + if (slotData.TryGetValue("Hexagon Quest Goal", out var hexagonGoal)) { SaveFile.SetInt(HexagonQuestGoal, int.Parse(hexagonGoal.ToString())); } } @@ -243,13 +472,13 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance if (SaveFile.GetInt(HexagonQuestEnabled) == 1) { SaveFile.SetInt(HexagonQuestPrayer, int.Parse(slotData["Hexagon Quest Prayer"].ToString(), CultureInfo.InvariantCulture)); SaveFile.SetInt(HexagonQuestHolyCross, int.Parse(slotData["Hexagon Quest Holy Cross"].ToString(), CultureInfo.InvariantCulture)); - SaveFile.SetInt(HexagonQuestIceRod, int.Parse(slotData["Hexagon Quest Ice Rod"].ToString(), CultureInfo.InvariantCulture)); + SaveFile.SetInt(HexagonQuestIcebolt, int.Parse(slotData["Hexagon Quest Icebolt"].ToString(), CultureInfo.InvariantCulture)); } } - if(abilityShuffling.ToString() == "0") { + if (abilityShuffling.ToString() == "0") { SaveFile.SetInt(PrayerUnlocked, 1); SaveFile.SetInt(HolyCrossUnlocked, 1); - SaveFile.SetInt(IceRodUnlocked, 1); + SaveFile.SetInt(IceBoltUnlocked, 1); } } if (slotData.TryGetValue("sword_progression", out var swordProgression)) { @@ -278,7 +507,7 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance if (SaveFile.GetInt("seed") == 0) { SaveFile.SetInt("seed", int.Parse(Seed.ToString(), CultureInfo.InvariantCulture)); EnemyRandomizer.CreateAreaSeeds(); - Logger.LogInfo("Imported seed from archipelago: " + Seed); + Logger.LogInfo("Starting new archipelago file with seed: " + Seed); } else { Logger.LogInfo("Loading seed: " + SaveFile.GetInt("seed")); } @@ -287,6 +516,7 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance Locations.PopulateMajorItemLocations(slotData); + Locations.RandomizedLocations.Clear(); Locations.CheckedLocations.Clear(); ItemLookup.ItemList.Clear(); List LocationIDs = new List(); @@ -305,72 +535,68 @@ public static void PlayerCharacter_Start_PostfixPatch(PlayerCharacter __instance string ItemName = Archipelago.instance.integration.session.Items.GetItemName(Location.Item) == null ? "UNKNOWN ITEM" : Archipelago.instance.integration.session.Items.GetItemName(Location.Item); ItemLookup.ItemList.Add(LocationId, new ArchipelagoItem(ItemName, Location.Player, Location.Flags)); } - }).Wait(); + }).Wait(); Logger.LogInfo("Successfully scouted locations for item placements"); - ItemTracker.PopulateSpoilerLog(); - GhostHints.GenerateHints(); - Hints.PopulateHints(); - if (Hints.HeroGraveHints.Count != 0) { - Hints.SetupHeroGraveToggle(); - } - - if (SaveFile.GetInt(AbilityShuffle) == 1 && SaveFile.GetInt(HolyCrossUnlocked) == 0) { - ItemPatches.ToggleHolyCrossObjects(false); - } - - if (SaveFile.GetInt(HexagonQuestEnabled) == 1) { - TunicArchipelago.Tracker.ImportantItems["Pages"] = 28; - SaveFile.SetInt("last page viewed", 0); - } - - FairyTargets.CreateFairyTargets(); - - if (!SceneLoaderPatches.SpawnedGhosts) { - GhostHints.SpawnHintGhosts(SceneLoaderPatches.SceneName); - } - - if (!ModelSwaps.SwappedThisSceneAlready) { - ModelSwaps.SwapItemsInScene(); - } Archipelago.instance.integration.UpdateDataStorageOnLoad(); } } + } - ItemStatsHUD.UpdateAbilitySection(); - - OptionsGUIPatches.SaveSettings(); + public static void GenerateMysterySettings() { + System.Random random = new System.Random(SaveFile.GetInt("seed")); - ItemPresentationPatches.SwitchDathStonePresentation(); + SaveFile.SetString("randomizer game mode", ((RandomizerSettings.GameModes)random.Next(2)).ToString()); + if (SaveFile.GetString("randomizer game mode") == "HEXAGONQUEST") { + SaveFile.SetInt(HexagonQuestEnabled, 1); + SaveFile.SetInt("randomizer hexagon quest goal", random.Next(15, 51)); + SaveFile.SetInt("randomizer hexagon quest extras", random.Next(101)); - PaletteEditor.GatherHyperdashRenderers(); - PaletteEditor.SetupPartyHat(__instance); - PaletteEditor.SetupFoxCape(__instance); + for (int i = 0; i < 28; i++) { + SaveFile.SetInt($"randomizer obtained page {i}", 1); + } - if (TunicArchipelago.Settings.RandomFoxColorsEnabled) { - PaletteEditor.RandomizeFoxColors(); + StateVariable.GetStateVariableByName("Placed Hexagon 1 Red").BoolValue = true; + StateVariable.GetStateVariableByName("Placed Hexagon 2 Green").BoolValue = true; + StateVariable.GetStateVariableByName("Placed Hexagon 3 Blue").BoolValue = true; + StateVariable.GetStateVariableByName("Placed Hexagons ALL").BoolValue = true; + StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue = true; + StateVariable.GetStateVariableByName("Has Died To God").BoolValue = true; } - if (TunicArchipelago.Settings.UseCustomTexture) { - LoadCustomTexture = true; - } + SaveFile.SetInt("randomizer sword progression enabled", 1); + SaveFile.SetInt("randomizer sword progression level", 0); - if (TunicArchipelago.Settings.RealestAlwaysOn) { - GameObject.FindObjectOfType().SpellEffect(); + if (random.Next(2) == 1) { + SaveFile.SetInt("randomizer keys behind bosses", 1); } - - if (PaletteEditor.CelShadingEnabled) { - PaletteEditor.ApplyCelShading(); + if (random.Next(2) == 1) { + Inventory.GetItemByName("Sword").Quantity = 1; + SaveFile.SetInt("randomizer started with sword", 1); } - if (PaletteEditor.PartyHatEnabled) { - WearHat = true; + if (random.NextDouble() < 0.25) { + SaveFile.SetInt(MasklessLogic, 1); + } + if (random.NextDouble() < 0.25) { + SaveFile.SetInt(LanternlessLogic, 1); } - } - public static void PlayerCharacter_creature_Awake_PostfixPatch(PlayerCharacter __instance) { - __instance.gameObject.AddComponent(); + TunicArchipelago.Settings.FoolTrapIntensity = (RandomizerSettings.FoolTrapOption)random.Next(4); + + SaveFile.SetInt("randomizer laurels location", random.NextDouble() < 0.75 ? 0 : random.Next(1, 4)); + + if (random.Next(2) == 1) { + SaveFile.SetInt("randomizer entrance rando enabled", 1); + Inventory.GetItemByName("Torch").Quantity = 1; + } + if (random.Next(2) == 1) { + SaveFile.SetInt("randomizer ER fixed shop", 1); + } + if (random.Next(2) == 1) { + SaveFile.SetInt("randomizer shuffled abilities", 1); + } } public static void PlayerCharacter_Die_MoveNext_PostfixPatch(PlayerCharacter._Die_d__481 __instance, ref bool __result) { @@ -424,5 +650,10 @@ public static bool Monster_IDamageable_ReceiveDamage_PrefixPatch(Monster __insta return true; } + public static bool Ladder_ClimbOn_PrefixPatch(Ladder __instance, LadderEnd ladderEnd) { + LastLadder = ladderEnd; + return true; + } + } } diff --git a/src/Patches/QuickSettings.cs b/src/Patches/QuickSettings.cs index eca893d..426756b 100644 --- a/src/Patches/QuickSettings.cs +++ b/src/Patches/QuickSettings.cs @@ -1,6 +1,9 @@ using BepInEx.Logging; using System; using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; @@ -9,11 +12,11 @@ public class QuickSettings : MonoBehaviour { private static ManualLogSource Logger = TunicArchipelago.Logger; - public static int CustomSeed = 0; + public static string CustomSeed = ""; public static Font OdinRounded; public static List FoolChoices = new List() { "Off", "Normal", "Double", "Onslaught" }; public static List FoolColors = new List() { "white", "#4FF5D4", "#E3D457", "#FF3333" }; - private static float height = 450f; + private static bool ShowAdvancedSinglePlayerOptions = false; private static bool ShowAPSettingsWindow = false; private static string stringToEdit = ""; private static bool editingPlayer = false; @@ -22,59 +25,385 @@ public class QuickSettings : MonoBehaviour { private static bool editingPassword = false; private static bool showPort = false; private static bool showPassword = false; - + private void OnGUI() { if (SceneManager.GetActiveScene().name == "TitleScreen" && GameObject.FindObjectOfType() != null) { GUI.skin.font = PaletteEditor.OdinRounded == null ? GUI.skin.font : PaletteEditor.OdinRounded; Cursor.visible = true; - GUI.Window(101, new Rect(20f, 150f, 430f, 490f), new Action(QuickSettingsWindow), "Quick Settings"); - - if (ShowAPSettingsWindow) { - GUI.Window(103, new Rect(460f, 150f, 350f, 490f), new Action(QuickAPSettings), "Archipelago Config"); + switch (TunicArchipelago.Settings.Mode) { + case RandomizerSettings.RandomizerType.SINGLEPLAYER: + GUI.Window(101, new Rect(20f, 150f, 430f, TunicArchipelago.Settings.MysterySeed ? 430f : 510f), new Action(SinglePlayerQuickSettingsWindow), "Single Player Settings"); + ShowAPSettingsWindow = false; + editingPlayer = false; + editingHostname = false; + editingPort = false; + editingPassword = false; + break; + case RandomizerSettings.RandomizerType.ARCHIPELAGO: + GUI.Window(101, new Rect(20f, 150f, 430f, 540f), new Action(ArchipelagoQuickSettingsWindow), "Archipelago Settings"); + break; + } + + if (ShowAPSettingsWindow && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO) { + GUI.Window(103, new Rect(460f, 150f, 350f, 490f), new Action(ArchipelagoConfigEditorWindow), "Archipelago Config"); + } + if (ShowAdvancedSinglePlayerOptions && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER && !TunicArchipelago.Settings.MysterySeed) { + GUI.Window(105, new Rect(460f, 150f, 405f, 485f), new Action(AdvancedLogicOptionsWindow), "Advanced Logic Options"); } + GameObject.Find("elderfox_sword graphic").GetComponent().enabled = !ShowAdvancedSinglePlayerOptions && !ShowAPSettingsWindow; } } private void Update() { - - if (Input.anyKeyDown && !Input.GetKeyDown(KeyCode.Return) && !Input.GetKeyDown(KeyCode.Tab) && !Input.GetKeyDown(KeyCode.Backspace)) { + if (TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO && ShowAPSettingsWindow && SceneManager.GetActiveScene().name == "TitleScreen") { + if (Input.anyKeyDown && !Input.GetKeyDown(KeyCode.Return) && !Input.GetKeyDown(KeyCode.Tab) && !Input.GetKeyDown(KeyCode.Backspace)) { + + if (editingPort && Input.inputString != "" && int.TryParse(Input.inputString, out int num)) { + stringToEdit += Input.inputString; + } else if (!editingPort && Input.inputString != "") { + stringToEdit += Input.inputString; + } + } + if (Input.GetKeyDown(KeyCode.Backspace)) { + if (stringToEdit.Length >= 2) { + stringToEdit = stringToEdit.Substring(0, stringToEdit.Length - 1); + } else { + stringToEdit = ""; + } + } + if (editingPlayer) { + TunicArchipelago.Settings.ConnectionSettings.Player = stringToEdit; + } + if (editingHostname) { + TunicArchipelago.Settings.ConnectionSettings.Hostname = stringToEdit; + } + if (editingPort) { + TunicArchipelago.Settings.ConnectionSettings.Port = stringToEdit; + } + if (editingPassword) { + TunicArchipelago.Settings.ConnectionSettings.Password = stringToEdit; + } + } + } + + private static void ArchipelagoQuickSettingsWindow(int windowID) { + GUI.skin.label.fontSize = 25; + GUI.skin.button.fontSize = 20; + GUI.skin.toggle.fontSize = 20; + GUI.skin.label.alignment = TextAnchor.UpperLeft; + GUI.skin.label.clipping = TextClipping.Overflow; + GUI.color = Color.white; + GUI.DragWindow(new Rect(500f, 50f, 500f, 30f)); + + float y = 20f; + + GUI.skin.toggle.fontSize = 15; + GUI.skin.button.fontSize = 15; + if (TunicArchipelago.Settings.RaceMode) { + TunicArchipelago.Settings.RaceMode = GUI.Toggle(new Rect(330f, y, 90f, 30f), TunicArchipelago.Settings.RaceMode, "Race Mode"); + } else { + bool ToggleSpoilerLog = GUI.Toggle(new Rect(TunicArchipelago.Settings.CreateSpoilerLog ? 280f : 330f, y, 90f, 30f), TunicArchipelago.Settings.CreateSpoilerLog, "Spoiler Log"); + TunicArchipelago.Settings.CreateSpoilerLog = ToggleSpoilerLog; + if (ToggleSpoilerLog) { + GUI.skin.button.fontSize = 15; + bool OpenSpoilerLog = GUI.Button(new Rect(370f, y, 50f, 25f), "Open"); + if (OpenSpoilerLog) { + if (File.Exists(TunicArchipelago.SpoilerLogPath)) { + System.Diagnostics.Process.Start(TunicArchipelago.SpoilerLogPath); + } + } + } + } + + GUI.skin.toggle.fontSize = 20; + GUI.skin.button.fontSize = 20; - if (editingPort && Input.inputString != "" && int.TryParse(Input.inputString, out int num)) { - stringToEdit += Input.inputString; - } else if (!editingPort && Input.inputString != "") { - stringToEdit += Input.inputString; + GUI.Label(new Rect(10f, 20f, 300f, 30f), "Randomizer Mode"); + y += 40f; + bool ToggleSinglePlayer = GUI.Toggle(new Rect(10f, y, 130f, 30f), TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER, "Single Player"); + if (ToggleSinglePlayer && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO) { + TunicArchipelago.Settings.Mode = RandomizerSettings.RandomizerType.SINGLEPLAYER; + OptionsGUIPatches.SaveSettings(); + } + bool ToggleArchipelago = GUI.Toggle(new Rect(150f, y, 150f, 30f), TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO, "Archipelago"); + if (ToggleArchipelago && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER) { + TunicArchipelago.Settings.Mode = RandomizerSettings.RandomizerType.ARCHIPELAGO; + OptionsGUIPatches.SaveSettings(); + } + y += 40f; + GUI.Label(new Rect(10f, y, 500f, 30f), $"Player: {(TunicArchipelago.Settings.ConnectionSettings.Player)}"); + y += 40f; + GUI.Label(new Rect(10f, y, 80f, 30f), $"Status:"); + if (Archipelago.instance.integration != null && Archipelago.instance.integration.connected) { + GUI.color = Color.green; + GUI.Label(new Rect(95f, y, 150f, 30f), $"Connected!"); + GUI.color = Color.white; + GUI.Label(new Rect(250f, y, 300f, 30f), $"(world {Archipelago.instance.integration.session.ConnectionInfo.Slot} of {Archipelago.instance.integration.session.Players.Players[0].Count-1})"); + } else { + GUI.color = Color.red; + GUI.Label(new Rect(95f, y, 300f, 30f), $"Disconnected"); + } + GUI.color = Color.white; + y += 40f; + bool Connect = GUI.Button(new Rect(10f, y, 200f, 30f), "Connect"); + if (Connect) { + Archipelago.instance.Connect(); + } + + bool Disconnect = GUI.Button(new Rect(220f, y, 200f, 30f), "Disconnect"); + if (Disconnect) { + Archipelago.instance.Disconnect(); + } + y += 40f; + bool OpenSettings = GUI.Button(new Rect(10f, y, 200f, 30f), "Open Settings File"); + if (OpenSettings) { + try { + System.Diagnostics.Process.Start(TunicArchipelago.SettingsPath); + } catch (Exception e) { + Logger.LogError(e); } } - if (Input.GetKeyDown(KeyCode.Backspace)) { - if (stringToEdit.Length >= 2) { - stringToEdit = stringToEdit.Substring(0, stringToEdit.Length - 1); + bool OpenAPSettings = GUI.Button(new Rect(220f, y, 200f, 30f), ShowAPSettingsWindow ? "Close AP Config" : "Edit AP Config"); + if (OpenAPSettings) { + if (ShowAPSettingsWindow) { + CloseAPSettingsWindow(); + Archipelago.instance.Connect(); } else { - stringToEdit = ""; + ShowAPSettingsWindow = true; } } - if (editingPlayer) { - TunicArchipelago.Settings.ConnectionSettings.Player = stringToEdit; + y += 40f; + GUI.Label(new Rect(10f, y, 200f, 30f), $"World Settings"); + if (Archipelago.instance.integration != null && Archipelago.instance.integration.connected) { + Dictionary slotData = Archipelago.instance.GetPlayerSlotData(); + y += 40f; + GUI.Toggle(new Rect(10f, y, 180f, 30f), slotData["keys_behind_bosses"].ToString() == "1", "Keys Behind Bosses"); + GUI.Toggle(new Rect(220f, y, 210f, 30f), slotData["sword_progression"].ToString() == "1", "Sword Progression"); + y += 40f; + GUI.Toggle(new Rect(10f, y, 175f, 30f), slotData["start_with_sword"].ToString() == "1", "Start With Sword"); + GUI.Toggle(new Rect(220f, y, 175f, 30f), slotData["ability_shuffling"].ToString() == "1", "Shuffled Abilities"); + y += 40f; + GUI.Toggle(new Rect(10f, y, 185f, 30f), slotData["hexagon_quest"].ToString() == "1", slotData["hexagon_quest"].ToString() == "1" ? + $"Hexagon Quest ({slotData["Hexagon Quest Goal"].ToString()})" : $"Hexagon Quest"); + int FoolIndex = int.Parse(slotData["fool_traps"].ToString()); + GUI.Toggle(new Rect(220f, y, 195f, 60f), FoolIndex != 0, $"Fool Traps: {(FoolIndex == 0 ? "Off" : $"{FoolChoices[FoolIndex]}")}"); + + if (slotData.ContainsKey("entrance_rando")) { + y += 40f; + GUI.Toggle(new Rect(10f, y, 195f, 30f), slotData["entrance_rando"].ToString() == "1", $"Entrance Randomizer"); + } else { + y += 40f; + GUI.Toggle(new Rect(10f, y, 195f, 30f), false, $"Entrance Randomizer"); + } + } else { + y += 40f; + GUI.Toggle(new Rect(10f, y, 180f, 30f), false, "Keys Behind Bosses"); + GUI.Toggle(new Rect(220f, y, 210f, 30f), false, "Sword Progression"); + y += 40f; + GUI.Toggle(new Rect(10f, y, 175f, 30f), false, "Start With Sword"); + GUI.Toggle(new Rect(220f, y, 175f, 30f), false, "Shuffled Abilities"); + y += 40f; + GUI.Toggle(new Rect(10f, y, 175f, 30f), false, "Hexagon Quest"); + GUI.Toggle(new Rect(220f, y, 175f, 30f), false, "Fool Traps: Off"); + y += 40f; + GUI.Toggle(new Rect(10f, y, 195f, 30f), false, $"Entrance Randomizer"); } - if (editingHostname) { - TunicArchipelago.Settings.ConnectionSettings.Hostname = stringToEdit; + y += 40f; + GUI.Label(new Rect(10f, y, 400f, 30f), "Other Settings (more in options menu!)"); + y += 40f; + bool DeathLink = GUI.Toggle(new Rect(10f, y, 115f, 30f), TunicArchipelago.Settings.DeathLinkEnabled, "Death Link"); + TunicArchipelago.Settings.DeathLinkEnabled = DeathLink; + bool EnemyRandomizer = GUI.Toggle(new Rect(150f, y, 180f, 30f), TunicArchipelago.Settings.EnemyRandomizerEnabled, "Enemy Randomizer"); + TunicArchipelago.Settings.EnemyRandomizerEnabled = EnemyRandomizer; + GUI.skin.label.fontSize = 20; + } + + private static void SinglePlayerQuickSettingsWindow(int windowID) { + GUI.skin.label.fontSize = 25; + //GUI.skin.toggle.fontSize = 25; + //GUI.skin.toggle.alignment = TextAnchor.UpperLeft; + GUI.skin.toggle.fontSize = 20; + GUI.skin.label.alignment = TextAnchor.UpperLeft; + GUI.skin.label.clipping = TextClipping.Overflow; + GUI.color = Color.white; + GUI.DragWindow(new Rect(500f, 50f, 500f, 30f)); + float y = 20f; + + GUI.skin.toggle.fontSize = 15; + if (TunicArchipelago.Settings.RaceMode) { + TunicArchipelago.Settings.RaceMode = GUI.Toggle(new Rect(330f, y, 90f, 30f), TunicArchipelago.Settings.RaceMode, "Race Mode"); + } else { + bool ToggleSpoilerLog = GUI.Toggle(new Rect(TunicArchipelago.Settings.CreateSpoilerLog ? 280f : 330f, y, 90f, 30f), TunicArchipelago.Settings.CreateSpoilerLog, "Spoiler Log"); + TunicArchipelago.Settings.CreateSpoilerLog = ToggleSpoilerLog; + if (ToggleSpoilerLog) { + GUI.skin.button.fontSize = 15; + bool OpenSpoilerLog = GUI.Button(new Rect(370f, y, 50f, 25f), "Open"); + if (OpenSpoilerLog) { + if (File.Exists(TunicArchipelago.SpoilerLogPath)) { + System.Diagnostics.Process.Start(TunicArchipelago.SpoilerLogPath); + } + } + } } - if (editingPort) { - if (int.TryParse(stringToEdit, out int num)) { - TunicArchipelago.Settings.ConnectionSettings.Port = num; - } else if (stringToEdit == "") { - TunicArchipelago.Settings.ConnectionSettings.Port = 0; + GUI.skin.toggle.fontSize = 20; + + GUI.Label(new Rect(10f, 20f, 300f, 30f), "Randomizer Mode"); + y += 40f; + bool ToggleSinglePlayer = GUI.Toggle(new Rect(10f, y, 130f, 30f), TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER, "Single Player"); + if (ToggleSinglePlayer && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO) { + TunicArchipelago.Settings.Mode = RandomizerSettings.RandomizerType.SINGLEPLAYER; + OptionsGUIPatches.SaveSettings(); + } + bool ToggleArchipelago = GUI.Toggle(new Rect(150f, y, 150f, 30f), TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO, "Archipelago"); + if (ToggleArchipelago && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER) { + TunicArchipelago.Settings.Mode = RandomizerSettings.RandomizerType.ARCHIPELAGO; + OptionsGUIPatches.SaveSettings(); + } + + GUI.skin.toggle.fontSize = 20; + y += 40f; + GUI.Label(new Rect(10f, y, 200f, 30f), "Logic Settings"); + TunicArchipelago.Settings.MysterySeed = GUI.Toggle(new Rect(240f, y+5, 200f, 30f), TunicArchipelago.Settings.MysterySeed, "Mystery Seed"); + y += 45f; + if (TunicArchipelago.Settings.MysterySeed) { + GUI.Label(new Rect(10f, y, 400f, 30f), "Mystery Seed Enabled!"); + GUI.skin.label.fontSize = 20; + y += 40f; + GUI.Label(new Rect(10f, y, 400f, 30f), "Settings will be chosen randomly on New Game."); + y += 40f; + } else { + bool ToggleHexagonQuest = GUI.Toggle(new Rect(10f, y, 175f, 30f), TunicArchipelago.Settings.GameMode == RandomizerSettings.GameModes.HEXAGONQUEST, "Hexagon Quest"); + if (ToggleHexagonQuest) { + TunicArchipelago.Settings.GameMode = RandomizerSettings.GameModes.HEXAGONQUEST; + } else if (!ToggleHexagonQuest && TunicArchipelago.Settings.GameMode == RandomizerSettings.GameModes.HEXAGONQUEST) { + TunicArchipelago.Settings.GameMode = RandomizerSettings.GameModes.RANDOMIZER; + } + TunicArchipelago.Settings.SwordProgressionEnabled = GUI.Toggle(new Rect(240f, y, 180f, 30f), TunicArchipelago.Settings.SwordProgressionEnabled, "Sword Progression"); + y += 40f; + TunicArchipelago.Settings.KeysBehindBosses = GUI.Toggle(new Rect(10f, y, 200f, 30f), TunicArchipelago.Settings.KeysBehindBosses, "Keys Behind Bosses"); + TunicArchipelago.Settings.ShuffleAbilities = GUI.Toggle(new Rect(240f, y, 175f, 30f), TunicArchipelago.Settings.ShuffleAbilities, "Shuffle Abilities"); + y += 40f; + TunicArchipelago.Settings.EntranceRandoEnabled = GUI.Toggle(new Rect(10f, y, 200f, 30f), TunicArchipelago.Settings.EntranceRandoEnabled, "Entrance Randomizer"); + TunicArchipelago.Settings.StartWithSwordEnabled = GUI.Toggle(new Rect(240f, y, 175f, 30f), TunicArchipelago.Settings.StartWithSwordEnabled, "Start With Sword"); + + y += 40f; + GUI.skin.button.fontSize = 20; + bool ShowAdvancedOptions = GUI.Button(new Rect(10f, y, 410f, 30f), $"{(ShowAdvancedSinglePlayerOptions ? "Hide" : "Show")} Advanced Options"); + if (ShowAdvancedOptions) { + ShowAdvancedSinglePlayerOptions = !ShowAdvancedSinglePlayerOptions; + } + y += 40f; + + } + GUI.Label(new Rect(10f, y, 400f, 30f), "Other Settings (more in options menu!)"); + y += 40f; + TunicArchipelago.Settings.EnemyRandomizerEnabled = GUI.Toggle(new Rect(10f, y, 200f, 30f), TunicArchipelago.Settings.EnemyRandomizerEnabled, "Enemy Randomizer"); + GUI.skin.button.fontSize = 20; + y += 40f; + GUI.Label(new Rect(10f, y, 300f, 30f), $"Custom Seed: {(CustomSeed == "" ? "Not Set" : CustomSeed.ToString())}"); + if (CustomSeed != "") { + bool ClearSeed = GUI.Button(new Rect(300f, y, 110f, 30f), "Clear"); + if (ClearSeed) { + CustomSeed = ""; + } + } + y += 40f; + bool GenerateSeed = GUI.Button(new Rect(10f, y, 200f, 30f), "Generate Seed"); + if (GenerateSeed) { + CustomSeed = new System.Random().Next().ToString(); + } + + bool PasteSeed = GUI.Button(new Rect(220f, y, 200f, 30f), "Paste Seed"); + if (PasteSeed) { + try { + CustomSeed = int.Parse(GUIUtility.systemCopyBuffer, CultureInfo.InvariantCulture).ToString(); + } catch (System.Exception e) { + Logger.LogError("Invalid custom seed pasted!"); } } - if (editingPassword) { - TunicArchipelago.Settings.ConnectionSettings.Password = stringToEdit; + y += 40f; + bool CopySettings = GUI.Button(new Rect(10f, y, 200f, 30f), "Copy Seed + Settings"); + if (CopySettings) { + CopyQuickSettings(); + } + bool PasteSettings = GUI.Button(new Rect(220f, y, 200f, 30f), "Paste Seed + Settings"); + if (PasteSettings) { + PasteQuickSettings(); + } + } + + private static void AdvancedLogicOptionsWindow(int windowID) { + GUI.skin.label.fontSize = 25; + float y = 20f; + GUI.Label(new Rect(10f, y, 300f, 30f), $"Hexagon Quest"); + y += 30; + GUI.Label(new Rect(10f, y, 220f, 20f), $"Hexagons Required:"); + GUI.Label(new Rect(190f, y, 30f, 30f), $"{(TunicArchipelago.Settings.HexagonQuestGoal)}"); + TunicArchipelago.Settings.HexagonQuestGoal = (int)GUI.HorizontalSlider(new Rect(220f, y + 15, 175f, 20f), TunicArchipelago.Settings.HexagonQuestGoal, 15, 50); + y += 30f; + GUI.Label(new Rect(10f, y, 220f, 30f), $"Hexagons in Item Pool:"); + GUI.Label(new Rect(190f, y, 30f, 30f), $"{((int)Math.Round((100f + TunicArchipelago.Settings.HexagonQuestExtraPercentage) / 100f * TunicArchipelago.Settings.HexagonQuestGoal))}"); + TunicArchipelago.Settings.HexagonQuestExtraPercentage = (int)GUI.HorizontalSlider(new Rect(220f, y + 15, 175f, 30f), TunicArchipelago.Settings.HexagonQuestExtraPercentage, 0, 100); + y += 40f; + GUI.Label(new Rect(10f, y, 300f, 30f), $"Entrance Randomizer"); + y += 40f; + TunicArchipelago.Settings.ERFixedShop = GUI.Toggle(new Rect(10f, y, 200f, 30f), TunicArchipelago.Settings.ERFixedShop, "Fewer Shop Entrances"); + y += 40f; + GUI.Label(new Rect(10f, y, 300f, 30f), $"Fool Traps"); + y += 40f; + bool NoFools = GUI.Toggle(new Rect(10f, y, 90f, 30f), TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.NONE, "None"); + if (NoFools) { + TunicArchipelago.Settings.FoolTrapIntensity = RandomizerSettings.FoolTrapOption.NONE; + } + bool NormalFools = GUI.Toggle(new Rect(110f, y, 90f, 30f), TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.NORMAL, "Normal"); + if (NormalFools) { + TunicArchipelago.Settings.FoolTrapIntensity = RandomizerSettings.FoolTrapOption.NORMAL; + } + bool DoubleFools = GUI.Toggle(new Rect(200f, y, 90f, 30f), TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.DOUBLE, "Double"); + if (DoubleFools) { + TunicArchipelago.Settings.FoolTrapIntensity = RandomizerSettings.FoolTrapOption.DOUBLE; + } + bool OnslaughtFools = GUI.Toggle(new Rect(290f, y, 100f, 30f), TunicArchipelago.Settings.FoolTrapIntensity == RandomizerSettings.FoolTrapOption.ONSLAUGHT, "Onslaught"); + if (OnslaughtFools) { + TunicArchipelago.Settings.FoolTrapIntensity = RandomizerSettings.FoolTrapOption.ONSLAUGHT; + } + y += 40f; + GUI.Label(new Rect(10f, y, 300f, 30f), $"Hero's Laurels Location"); + y += 40f; + bool RandomLaurels = GUI.Toggle(new Rect(10f, y, 90f, 30f), TunicArchipelago.Settings.FixedLaurelsOption == RandomizerSettings.FixedLaurelsType.RANDOM, "Random"); + if (RandomLaurels) { + TunicArchipelago.Settings.FixedLaurelsOption = RandomizerSettings.FixedLaurelsType.RANDOM; + } + bool SixCoinsLaurels = GUI.Toggle(new Rect(110f, y, 90f, 30f), TunicArchipelago.Settings.FixedLaurelsOption == RandomizerSettings.FixedLaurelsType.SIXCOINS, "6 Coins"); + if (SixCoinsLaurels) { + TunicArchipelago.Settings.FixedLaurelsOption = RandomizerSettings.FixedLaurelsType.SIXCOINS; + } + bool TenCoinsLaurels = GUI.Toggle(new Rect(200f, y, 90f, 30f), TunicArchipelago.Settings.FixedLaurelsOption == RandomizerSettings.FixedLaurelsType.TENCOINS, "10 Coins"); + if (TenCoinsLaurels) { + TunicArchipelago.Settings.FixedLaurelsOption = RandomizerSettings.FixedLaurelsType.TENCOINS; + } + bool TenFairiesLaurels = GUI.Toggle(new Rect(290f, y, 100f, 30f), TunicArchipelago.Settings.FixedLaurelsOption == RandomizerSettings.FixedLaurelsType.TENFAIRIES, "10 Fairies"); + if (TenFairiesLaurels) { + TunicArchipelago.Settings.FixedLaurelsOption = RandomizerSettings.FixedLaurelsType.TENFAIRIES; + } + y += 40f; + GUI.Label(new Rect(10f, y, 300f, 30f), $"Difficulty Options"); + y += 40f; + TunicArchipelago.Settings.Lanternless = GUI.Toggle(new Rect(10f, y, 175f, 30f), TunicArchipelago.Settings.Lanternless, "Lanternless Logic"); + TunicArchipelago.Settings.Maskless = GUI.Toggle(new Rect(195f, y, 175f, 30f), TunicArchipelago.Settings.Maskless, "Maskless Logic"); + y += 40f; + bool Close = GUI.Button(new Rect(10f, y, 200f, 30f), "Close"); + if (Close) { + ShowAdvancedSinglePlayerOptions = false; + OptionsGUIPatches.SaveSettings(); } } - private static void QuickAPSettings(int windowID) { + private static void ArchipelagoConfigEditorWindow(int windowID) { GUI.skin.label.fontSize = 25; GUI.skin.button.fontSize = 17; - GUI.Label(new Rect(10f, 20f, 300f, 30f), $"Player: {TunicArchipelago.Settings.ConnectionSettings.Player}"); + GUI.Label(new Rect(10f, 20f, 300f, 30f), $"Player: {(TunicArchipelago.Settings.ConnectionSettings.Player)}"); bool EditPlayer = GUI.Button(new Rect(10f, 70f, 75f, 30f), editingPlayer ? "Save" : "Edit"); if (EditPlayer) { if (editingPlayer) { @@ -95,8 +424,16 @@ private static void QuickAPSettings(int windowID) { editingPlayer = false; OptionsGUIPatches.SaveSettings(); } + bool ClearPlayer = GUI.Button(new Rect(190f, 70f, 75f, 30f), "Clear"); + if (ClearPlayer) { + if (editingPlayer) { + stringToEdit = ""; + } + TunicArchipelago.Settings.ConnectionSettings.Player = ""; + OptionsGUIPatches.SaveSettings(); + } - GUI.Label(new Rect(10f, 120f, 300f, 30f), $"Host: {TunicArchipelago.Settings.ConnectionSettings.Hostname}"); + GUI.Label(new Rect(10f, 120f, 300f, 30f), $"Host: {(TunicArchipelago.Settings.ConnectionSettings.Hostname)}"); bool setLocalhost = GUI.Toggle(new Rect(160f, 160f, 90f, 30f), TunicArchipelago.Settings.ConnectionSettings.Hostname == "localhost", "localhost"); if (setLocalhost && TunicArchipelago.Settings.ConnectionSettings.Hostname != "localhost") { TunicArchipelago.Settings.ConnectionSettings.Hostname = "localhost"; @@ -127,7 +464,15 @@ private static void QuickAPSettings(int windowID) { editingHostname = false; OptionsGUIPatches.SaveSettings(); } - + bool ClearHost = GUI.Button(new Rect(190f, 200f, 75f, 30f), "Clear"); + if (ClearHost) { + if (editingHostname) { + stringToEdit = ""; + } + TunicArchipelago.Settings.ConnectionSettings.Hostname = ""; + OptionsGUIPatches.SaveSettings(); + } + GUI.Label(new Rect(10f, 250f, 300f, 30f), $"Port: {(editingPort ? (showPort ? stringToEdit : new string('*', stringToEdit.Length)) : (showPort ? TunicArchipelago.Settings.ConnectionSettings.Port.ToString() : new string('*', TunicArchipelago.Settings.ConnectionSettings.Port.ToString().Length)))}"); showPort = GUI.Toggle(new Rect(270f, 260f, 75f, 30f), showPort, "Show"); bool EditPort = GUI.Button(new Rect(10f, 300f, 75f, 30f), editingPort ? "Save" : "Edit"); @@ -147,13 +492,23 @@ private static void QuickAPSettings(int windowID) { bool PastePort = GUI.Button(new Rect(100f, 300f, 75f, 30f), "Paste"); if (PastePort) { try { - TunicArchipelago.Settings.ConnectionSettings.Port = int.Parse(GUIUtility.systemCopyBuffer); + if (int.TryParse(GUIUtility.systemCopyBuffer, out int num)) { + TunicArchipelago.Settings.ConnectionSettings.Port = GUIUtility.systemCopyBuffer; + } editingPort = false; OptionsGUIPatches.SaveSettings(); } catch (Exception e) { Logger.LogError("invalid input pasted for port number!"); } } + bool ClearPort = GUI.Button(new Rect(190f, 300f, 75f, 30f), "Clear"); + if (ClearPort) { + if (editingPort) { + stringToEdit = ""; + } + TunicArchipelago.Settings.ConnectionSettings.Port = ""; + OptionsGUIPatches.SaveSettings(); + } GUI.Label(new Rect(10f, 350f, 300f, 30f), $"Password: {(showPassword ? TunicArchipelago.Settings.ConnectionSettings.Password : new string('*', TunicArchipelago.Settings.ConnectionSettings.Password.Length))}"); showPassword = GUI.Toggle(new Rect(270f, 360f, 75f, 30f), showPassword, "Show"); @@ -180,6 +535,9 @@ private static void QuickAPSettings(int windowID) { } bool ClearPassword = GUI.Button(new Rect(190f, 400f, 75f, 30f), "Clear"); if (ClearPassword) { + if (editingPassword) { + stringToEdit = ""; + } TunicArchipelago.Settings.ConnectionSettings.Password = ""; OptionsGUIPatches.SaveSettings(); } @@ -201,101 +559,229 @@ private static void CloseAPSettingsWindow() { OptionsGUIPatches.SaveSettings(); } - private static void QuickSettingsWindow(int windowID) { - GUI.skin.label.fontSize = 25; - GUI.skin.button.fontSize = 20; - GUI.skin.toggle.fontSize = 20; - GUI.skin.label.alignment = TextAnchor.UpperLeft; - GUI.skin.label.clipping = TextClipping.Overflow; - GUI.color = Color.white; - GUI.DragWindow(new Rect(500f, 50f, 500f, 30f)); - GUI.Label(new Rect(10f, 20f, 500f, 30f), $"Player: {TunicArchipelago.Settings.ConnectionSettings.Player}"); - GUI.Label(new Rect(10f, 60f, 80f, 30f), $"Status:"); - if (Archipelago.instance.integration != null && Archipelago.instance.integration.connected) { - GUI.color = Color.green; - GUI.Label(new Rect(95f, 60f, 150f, 30f), $"Connected!"); - GUI.color = Color.white; - GUI.Label(new Rect(250f, 60f, 300f, 30f), $"(world {Archipelago.instance.integration.session.ConnectionInfo.Slot} of {Archipelago.instance.integration.session.Players.Players[0].Count-1})"); - } else { - GUI.color = Color.red; - GUI.Label(new Rect(95f, 60f, 300f, 30f), $"Disconnected"); + public static void CopyQuickSettings() { + if (TunicArchipelago.Settings.MysterySeed) { + GUIUtility.systemCopyBuffer = $"{CustomSeed},mystery_seed"; + return; + } + List Settings = new List() { CustomSeed.ToString(), Enum.GetName(typeof(RandomizerSettings.GameModes), TunicArchipelago.Settings.GameMode).ToLower() }; + if (TunicArchipelago.Settings.GameMode == RandomizerSettings.GameModes.HEXAGONQUEST) { + Settings.Add($"hexagon_quest_goal={(TunicArchipelago.Settings.HexagonQuestGoal)}="); + Settings.Add($"hexagon_quest_extras~{(TunicArchipelago.Settings.HexagonQuestExtraPercentage)}~"); + } + if (TunicArchipelago.Settings.KeysBehindBosses) { + Settings.Add("keys_behind_bosses"); + } + if (TunicArchipelago.Settings.SwordProgressionEnabled) { + Settings.Add("sword_progression"); + } + if (TunicArchipelago.Settings.StartWithSwordEnabled) { + Settings.Add("start_with_sword"); + } + if (TunicArchipelago.Settings.ShuffleAbilities) { + Settings.Add("shuffle_abilities"); + } + if (TunicArchipelago.Settings.EntranceRandoEnabled) { + Settings.Add("entrance_randomizer"); + } + if (TunicArchipelago.Settings.ERFixedShop) { + Settings.Add("er_fixed_shop"); + } + Settings.Add($"fool_traps${(int)TunicArchipelago.Settings.FoolTrapIntensity}$"); + Settings.Add($"laurels_location#{(int)TunicArchipelago.Settings.FixedLaurelsOption}#"); + if (TunicArchipelago.Settings.Lanternless) { + Settings.Add("lanternless"); + } + if (TunicArchipelago.Settings.Maskless) { + Settings.Add("maskless"); } - GUI.color = Color.white; - bool Connect = GUI.Button(new Rect(10f, 100f, 200f, 30f), "Connect"); - if (Connect) { - Archipelago.instance.Connect(); + // Enemy Rando + if (TunicArchipelago.Settings.EnemyRandomizerEnabled) { + Settings.Add("enemy_randomizer"); + if (TunicArchipelago.Settings.ExtraEnemiesEnabled) { + Settings.Add("extra_enemies"); + } + Settings.Add($"enemy_generation!{(int)TunicArchipelago.Settings.EnemyDifficulty}!{(int)TunicArchipelago.Settings.EnemyGeneration}!"); } - bool Disconnect = GUI.Button(new Rect(220f, 100f, 200f, 30f), "Disconnect"); - if (Disconnect) { - Archipelago.instance.Disconnect(); + // Race Settings + if (TunicArchipelago.Settings.RaceMode) { + Settings.Add("race_mode"); + if (TunicArchipelago.Settings.DisableIceboltInHeirFight) { + Settings.Add("no_heir_icebolt"); + } + if (TunicArchipelago.Settings.DisableDistantBellShots) { + Settings.Add("no_distant_bell_shot"); + } + if (TunicArchipelago.Settings.DisableIceGrappling) { + Settings.Add("no_ice_grapple"); + } + if (TunicArchipelago.Settings.DisableLadderStorage) { + Settings.Add("no_ladder_storage"); + } + if (TunicArchipelago.Settings.DisableUpgradeStealing) { + Settings.Add("no_upgrade_stealing"); + } } - bool OpenSettings = GUI.Button(new Rect(10f, 140f, 200f, 30f), "Open Settings File"); - if (OpenSettings) { - try { - System.Diagnostics.Process.Start(TunicArchipelago.SettingsPath); - } catch (Exception e) { - Logger.LogError(e); + GUIUtility.systemCopyBuffer = string.Join(",", Settings.ToArray()); + } + + public static void CopyQuickSettingsInGame() { + if (SaveFile.GetInt("randomizer mystery seed") == 1) { + GUIUtility.systemCopyBuffer = $"{SaveFile.GetInt("seed")},mystery_seed"; + return; + } + List Settings = new List() { SaveFile.GetInt("seed").ToString(), SaveFile.GetString("randomizer game mode").ToLower() }; + if (SaveFile.GetInt("randomizer hexagon quest enabled") == 1) { + Settings.Add($"hexagon_quest_goal={SaveFile.GetInt("randomizer hexagon quest goal")}="); + Settings.Add($"hexagon_quest_extras~{SaveFile.GetInt("randomizer hexagon quest extras")}~"); + } + if (SaveFile.GetInt("randomizer keys behind bosses") == 1) { + Settings.Add("keys_behind_bosses"); + } + if (SaveFile.GetInt("randomizer sword progression enabled") == 1) { + Settings.Add("sword_progression"); + } + if (SaveFile.GetInt("randomizer started with sword") == 1) { + Settings.Add("start_with_sword"); + } + if (SaveFile.GetInt("randomizer shuffled abilities") == 1) { + Settings.Add("shuffle_abilities"); + } + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { + Settings.Add("entrance_randomizer"); + } + if (SaveFile.GetInt("randomizer ER fixed shop") == 1) { + Settings.Add("er_fixed_shop"); + } + Settings.Add($"fool_traps${(int)TunicArchipelago.Settings.FoolTrapIntensity}$"); + Settings.Add($"laurels_location#{SaveFile.GetInt("randomizer laurels location")}#"); + if (SaveFile.GetInt(SaveFlags.LanternlessLogic) == 1) { + Settings.Add("lanternless"); + } + if (SaveFile.GetInt(SaveFlags.MasklessLogic) == 1) { + Settings.Add("maskless"); + } + + // Enemy Rando + if (TunicArchipelago.Settings.EnemyRandomizerEnabled) { + Settings.Add("enemy_randomizer"); + if (TunicArchipelago.Settings.ExtraEnemiesEnabled) { + Settings.Add("extra_enemies"); } + Settings.Add($"enemy_generation!{(int)TunicArchipelago.Settings.EnemyDifficulty}!{(int)TunicArchipelago.Settings.EnemyGeneration}!"); } - bool OpenAPSettings = GUI.Button(new Rect(220f, 140f, 200f, 30f), ShowAPSettingsWindow ? "Close AP Config" : "Edit AP Config"); - if (OpenAPSettings) { - if (ShowAPSettingsWindow) { - CloseAPSettingsWindow(); - Archipelago.instance.Connect(); - } else { - Archipelago.instance.Disconnect(); - ShowAPSettingsWindow = true; + + // Race Settings + if (TunicArchipelago.Settings.RaceMode) { + Settings.Add("race_mode"); + if (TunicArchipelago.Settings.DisableIceboltInHeirFight) { + Settings.Add("no_heir_icebolt"); + } + if (TunicArchipelago.Settings.DisableDistantBellShots) { + Settings.Add("no_distant_bell_shot"); + } + if (TunicArchipelago.Settings.DisableIceGrappling) { + Settings.Add("no_ice_grapple"); + } + if (TunicArchipelago.Settings.DisableLadderStorage) { + Settings.Add("no_ladder_storage"); + } + if (TunicArchipelago.Settings.DisableUpgradeStealing) { + Settings.Add("no_upgrade_stealing"); } } - GUI.Label(new Rect(10f, 180f, 200f, 30f), $"World Settings"); - float y = 340f; - if (Archipelago.instance.integration != null && Archipelago.instance.integration.connected) { - Dictionary slotData = Archipelago.instance.GetPlayerSlotData(); - GUI.Toggle(new Rect(10f, 220f, 180f, 30f), slotData["keys_behind_bosses"].ToString() == "1", "Keys Behind Bosses"); - GUI.Toggle(new Rect(220f, 220f, 210f, 30f), slotData["sword_progression"].ToString() == "1", "Sword Progression"); - GUI.Toggle(new Rect(10f, 260f, 175f, 30f), slotData["start_with_sword"].ToString() == "1", "Start With Sword"); - GUI.Toggle(new Rect(220f, 260f, 175f, 30f), slotData["ability_shuffling"].ToString() == "1", "Shuffled Abilities"); - GUI.Toggle(new Rect(10f, 300f, 185f, 30f), slotData["hexagon_quest"].ToString() == "1", slotData["hexagon_quest"].ToString() == "1" ? - $"Hexagon Quest ({slotData["Hexagon Quest Goal"].ToString()})" : $"Hexagon Quest"); - int FoolIndex = int.Parse(slotData["fool_traps"].ToString()); - GUI.Toggle(new Rect(220f, 300f, 195f, 60f), FoolIndex != 0, $"Fool Traps: {(FoolIndex == 0 ? "Off" : $"{FoolChoices[FoolIndex]}")}"); + GUIUtility.systemCopyBuffer = string.Join(",", Settings.ToArray()); + } - if (slotData.ContainsKey("entrance_rando")) { - height = 490f; - y += 40f; - GUI.Toggle(new Rect(10f, 340f, 195f, 30f), slotData["entrance_rando"].ToString() == "1", $"Entrance Randomizer"); + + public static void PasteQuickSettings() { + try { + string SettingsString = GUIUtility.systemCopyBuffer; + string[] SplitSettings = SettingsString.Split(','); + CustomSeed = int.Parse(SplitSettings[0], CultureInfo.InvariantCulture).ToString(); + RandomizerSettings.GameModes NewGameMode; + if (SettingsString.Contains("mystery_seed")) { + TunicArchipelago.Settings.MysterySeed = true; + return; + } + if (Enum.TryParse(SplitSettings[1].ToUpper(), true, out NewGameMode)) { + TunicArchipelago.Settings.GameMode = NewGameMode; } else { - height = 490f; - y += 40f; - GUI.Toggle(new Rect(10f, 340f, 195f, 30f), false, $"Entrance Randomizer"); + TunicArchipelago.Settings.GameMode = RandomizerSettings.GameModes.RANDOMIZER; } - } else { - height = 490f; - y += 40f; - GUI.Toggle(new Rect(10f, 220f, 180f, 30f), false, "Keys Behind Bosses"); - GUI.Toggle(new Rect(220f, 220f, 210f, 30f), false, "Sword Progression"); - GUI.Toggle(new Rect(10f, 260f, 175f, 30f), false, "Start With Sword"); - GUI.Toggle(new Rect(220f, 260f, 175f, 30f), false, "Shuffled Abilities"); - GUI.Toggle(new Rect(10f, 300f, 175f, 30f), false, "Hexagon Quest"); - GUI.Toggle(new Rect(220f, 300f, 175f, 30f), false, "Fool Traps: Off"); - GUI.Toggle(new Rect(10f, 340f, 195f, 30f), false, $"Entrance Randomizer"); + if (SettingsString.Split('=').Count() > 1) { + try { + TunicArchipelago.Settings.HexagonQuestGoal = int.Parse(SettingsString.Split('=')[1]); + } catch (Exception e) { + TunicArchipelago.Settings.HexagonQuestGoal = 20; + } + } + if (SettingsString.Split('~').Count() > 1) { + try { + TunicArchipelago.Settings.HexagonQuestExtraPercentage = int.Parse(SettingsString.Split('~')[1]); + } catch (Exception e) { + TunicArchipelago.Settings.HexagonQuestExtraPercentage = 50; + } + } + if (SettingsString.Split('$').Count() > 1) { + try { + TunicArchipelago.Settings.FoolTrapIntensity = (RandomizerSettings.FoolTrapOption)int.Parse(SettingsString.Split('$')[1]); + } catch (Exception e) { + TunicArchipelago.Settings.FoolTrapIntensity = RandomizerSettings.FoolTrapOption.NORMAL; + } + } + if (SettingsString.Split('#').Count() > 1) { + try { + TunicArchipelago.Settings.FixedLaurelsOption = (RandomizerSettings.FixedLaurelsType)int.Parse(SettingsString.Split('#')[1]); + } catch(Exception e) { + TunicArchipelago.Settings.FixedLaurelsOption = RandomizerSettings.FixedLaurelsType.RANDOM; + } + } + TunicArchipelago.Settings.KeysBehindBosses = SettingsString.Contains("keys_behind_bosses"); + TunicArchipelago.Settings.SwordProgressionEnabled = SettingsString.Contains("sword_progression"); + TunicArchipelago.Settings.StartWithSwordEnabled = SettingsString.Contains("start_with_sword"); + TunicArchipelago.Settings.ShuffleAbilities = SettingsString.Contains("shuffle_abilities"); + TunicArchipelago.Settings.EntranceRandoEnabled = SettingsString.Contains("entrance_randomizer"); + TunicArchipelago.Settings.ERFixedShop = SettingsString.Contains("er_fixed_shop"); + TunicArchipelago.Settings.Lanternless = SettingsString.Contains("lanternless"); + TunicArchipelago.Settings.Maskless = SettingsString.Contains("maskless"); + + TunicArchipelago.Settings.EnemyRandomizerEnabled = SettingsString.Contains("enemy_randomizer"); + if (TunicArchipelago.Settings.EnemyRandomizerEnabled) { + TunicArchipelago.Settings.EnemyRandomizerEnabled = true; + TunicArchipelago.Settings.ExtraEnemiesEnabled = SettingsString.Contains("extra_enemies"); + if (SettingsString.Split('!').Count() > 1) { + try { + TunicArchipelago.Settings.EnemyDifficulty = (RandomizerSettings.EnemyRandomizationType)int.Parse(SettingsString.Split('!')[2]); + TunicArchipelago.Settings.EnemyGeneration = (RandomizerSettings.EnemyGenerationType)int.Parse(SettingsString.Split('!')[1]); + + } catch (Exception e) { + TunicArchipelago.Settings.EnemyDifficulty = RandomizerSettings.EnemyRandomizationType.BALANCED; + TunicArchipelago.Settings.EnemyGeneration = RandomizerSettings.EnemyGenerationType.SEEDED; + } + } + } + TunicArchipelago.Settings.RaceMode = SettingsString.Contains("race_mode"); + if (TunicArchipelago.Settings.RaceMode) { + TunicArchipelago.Settings.DisableIceboltInHeirFight = SettingsString.Contains("no_heir_icebolt"); + TunicArchipelago.Settings.DisableDistantBellShots = SettingsString.Contains("no_distant_bell_shot"); + TunicArchipelago.Settings.DisableIceGrappling = SettingsString.Contains("no_ice_grapple"); + TunicArchipelago.Settings.DisableLadderStorage = SettingsString.Contains("no_ladder_storage"); + TunicArchipelago.Settings.DisableUpgradeStealing = SettingsString.Contains("no_upgrade_stealing"); + } + + OptionsGUIPatches.SaveSettings(); + } catch (Exception e) { + Logger.LogError("Error parsing quick settings string!"); } - GUI.Label(new Rect(10f, y, 200f, 30f), $"Other Settings"); - y += 40f; - bool DeathLink = GUI.Toggle(new Rect(10f, y, 115f, 30f), TunicArchipelago.Settings.DeathLinkEnabled, "Death Link"); - TunicArchipelago.Settings.DeathLinkEnabled = DeathLink; - bool EnemyRandomizer = GUI.Toggle(new Rect(150f, y, 180f, 30f), TunicArchipelago.Settings.EnemyRandomizerEnabled, "Enemy Randomizer"); - TunicArchipelago.Settings.EnemyRandomizerEnabled = EnemyRandomizer; - GUI.skin.label.fontSize = 20; - y += 30f; - GUI.Label(new Rect(10f, y, 500f, 30f), $"More settings in options menu!"); } + public static bool TitleScreen___NewGame_PrefixPatch(TitleScreen __instance) { CloseAPSettingsWindow(); return true; @@ -304,9 +790,9 @@ public static bool TitleScreen___NewGame_PrefixPatch(TitleScreen __instance) { public static bool FileManagement_LoadFileAndStart_PrefixPatch(FileManagementGUI __instance, string filename) { CloseAPSettingsWindow(); SaveFile.LoadFromFile(filename); - if (SaveFile.GetInt("archipelago") == 0) { - Logger.LogInfo("Non-Archipelago file selected!"); - GenericMessage.ShowMessage("<#FF0000>[death] \"<#FF0000>warning!\" <#FF0000>[death]\n\"Non-Archipelago file selected.\"\n\"Returning to menu.\""); + if (SaveFile.GetInt("archipelago") == 0 && SaveFile.GetInt("randomizer") == 0) { + Logger.LogInfo("Non-Randomizer file selected!"); + GenericMessage.ShowMessage("<#FF0000>[death] \"<#FF0000>warning!\" <#FF0000>[death]\n\"Non-Randomizer file selected.\"\n\"Returning to menu.\""); return false; } if (SaveFile.GetString("archipelago player name") != "") { diff --git a/src/Patches/SceneLoaderPatches.cs b/src/Patches/SceneLoaderPatches.cs index cc60326..933b16d 100644 --- a/src/Patches/SceneLoaderPatches.cs +++ b/src/Patches/SceneLoaderPatches.cs @@ -16,6 +16,8 @@ public class SceneLoaderPatches { public static float TimeOfLastSceneTransition = 0.0f; public static bool SpawnedGhosts = false; + public static GameObject SpiritArenaTeleporterPrefab; + public static bool SceneLoader_OnSceneLoaded_PrefixPatch(Scene loadingScene, LoadSceneMode mode, SceneLoader __instance) { // ladder storage fix if (PlayerCharacter.instance != null) @@ -31,7 +33,7 @@ public static bool SceneLoader_OnSceneLoaded_PrefixPatch(Scene loadingScene, Loa SaveFile.SetInt("chest open 19", SaveFile.GetInt("randomizer picked up 19 [Sword Cave]")); } - if (Archipelago.instance != null && Archipelago.instance.integration != null && Archipelago.instance.integration.connected) { + if (IsArchipelago()) { foreach (long location in Archipelago.instance.integration.session.Locations.AllLocationsChecked) { string LocationId = Archipelago.instance.integration.session.Locations.GetLocationNameFromId(location); string GameObjectId = Locations.LocationDescriptionToId[LocationId]; @@ -142,12 +144,17 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo if (loadingScene.name == "Spirit Arena" && ModelSwaps.ThirdSword == null) { ModelSwaps.InitializeThirdSword(); ItemPresentationPatches.SetupCustomSwordItemPresentations(); + SpiritArenaTeleporterPrefab = GameObject.Instantiate(GameObject.Find("Teleporter")); + GameObject.DontDestroyOnLoad(SpiritArenaTeleporterPrefab); + SpiritArenaTeleporterPrefab.transform.position = new Vector3(-30000f, -30000f, -30000f); + SpiritArenaTeleporterPrefab.SetActive(false); ModelSwaps.SetupStarburstEffect(); SceneLoader.LoadScene("Transit"); return; } if (loadingScene.name == "Library Arena" && ModelSwaps.SecondSword == null) { ModelSwaps.InitializeSecondSword(); + EnemyRandomizer.InitializeEnemies("Library Arena"); SceneLoader.LoadScene("Spirit Arena"); return; } @@ -192,7 +199,8 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo if (SceneName == "Overworld Redux" && (StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue && StateVariable.GetStateVariableByName("Has Died To God").BoolValue) && SaveFile.GetInt(DiedToHeir) != 1 && SaveFile.GetInt(HexagonQuestEnabled) == 0) { PlayerCharacterPatches.ResetDayNightTimer = 0.0f; - Logger.LogInfo("Resetting time of day to daytime!"); + Logger.LogInfo("Resetting time of day to daytime!"); + SpawnHeirFastTravel("Spirit Arena", new Vector3(2.0801f, 43.5833f, -54.0065f)); } PlayerCharacterPatches.StungByBee = false; @@ -246,10 +254,12 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo Resources.FindObjectsOfTypeAll().ToList()[0].gameObject.transform.GetChild(0).GetComponent().originalMaterials = ModelSwaps.Items["GoldenTrophy_2"].GetComponent().materials; Resources.FindObjectsOfTypeAll().ToList()[0].gameObject.transform.GetChild(1).GetComponent().originalMaterials = ModelSwaps.Items["GoldenTrophy_2"].GetComponent().materials; } + + SpawnHeirFastTravel("Overworld Redux", new Vector3(-30000f, -30000f, -30000f)); } else if (SceneName == "Overworld Interiors") { GameObject.Find("Trophy Stuff").transform.GetChild(4).gameObject.SetActive(true); - GameObject.Destroy(GameObject.Find("_Special/Bed Toggle Trigger/")); + GameObject.FindObjectOfType().canBeUsed = StateVariable.GetStateVariableByName("false"); if ((StateVariable.GetStateVariableByName("Has Been Betrayed").BoolValue || StateVariable.GetStateVariableByName("Has Died To God").BoolValue) && SaveFile.GetInt(HexagonQuestEnabled) == 0) { InteractionPatches.SetupDayNightHourglass(); } @@ -262,7 +272,7 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo SaveFile.SetInt("chest open 19", 0); } else if (SceneName == "TitleScreen") { TitleVersion.Initialize(); - if (!Archipelago.instance.integration.connected) { + if (!Archipelago.instance.integration.connected && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO) { Archipelago.instance.Connect(); } } else if (SceneName == "Temple") { @@ -283,7 +293,10 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo GameObject.Find("_Environment Special/Door (1)/door/key twist").GetComponent().materials = ModelSwaps.Items["Key (House)"].GetComponent().materials; GameObject.Find("_Environment/_Decorations/Mailbox (1)/mailbox flag").AddComponent(); - if (SaveFile.GetInt("randomizer entrance rando enabled") == 1 || (Archipelago.instance.integration.slotData.ContainsKey("entrance_rando") && Archipelago.instance.integration.slotData["entrance_rando"].ToString() == "1" && SaveFile.GetInt("seed") == 0)) { + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1 || (SaveFile.GetInt("seed") == 0 && + ((TunicArchipelago.Settings.EntranceRandoEnabled && TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.SINGLEPLAYER) || + (TunicArchipelago.Settings.Mode == RandomizerSettings.RandomizerType.ARCHIPELAGO && Archipelago.instance.integration.connected + && Archipelago.instance.integration.slotData.ContainsKey("entrance_rando") && Archipelago.instance.integration.slotData["entrance_rando"].ToString() == "1")))) { GhostHints.SpawnTorchHintGhost(); } @@ -295,6 +308,9 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo } } } + if (SaveFile.GetInt(DiedToHeir) == 1) { + SpawnHeirFastTravel("Spirit Arena", new Vector3(2.0801f, 43.5833f, -54.0065f)); + } } else if (SceneName == "Swamp Redux 2") { GhostHints.SpawnCathedralDoorGhost(); @@ -306,6 +322,17 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo // Activate night bridge to allow access to shortcut ladder GameObject.Find("_Setpieces Etc/NightBridge/").GetComponent().dayOrNight = StateVariable.GetStateVariableByName("Is Night").BoolValue ? DayNightBridge.DayNight.NIGHT : DayNightBridge.DayNight.DAY; + if (SaveFile.GetInt("fuseClosed 1096") == 1) { + if (GameObject.Find("_Setpieces Etc/plank_4u (planks on gate)/") != null) { + GameObject.Find("_Setpieces Etc/plank_4u (planks on gate)/").SetActive(false); + } + if (GameObject.Find("_Setpieces Etc/Gated Wooden Double Door/") != null) { + GameObject.Find("_Setpieces Etc/Gated Wooden Double Door/").GetComponent().stateVar = StateVariable.GetStateVariableByName("true"); + GameObject.Find("_Setpieces Etc/Gated Wooden Double Door/").GetComponent().isNightstateVar = StateVariable.GetStateVariableByName("true"); + } + + } + if (TunicArchipelago.Settings.MoreSkulls) { InteractionPatches.SpawnMoreSkulls(); } @@ -314,13 +341,13 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo } else if (SceneName == "Posterity") { GhostHints.SpawnRescuedGhostFox(); } else if (SceneName == "Shop") { - if (new System.Random().Next(100) < 3) { + if (new System.Random().Next(100) < 2) { GameObject.Find("merchant").SetActive(false); GameObject.Find("Environment").transform.GetChild(3).gameObject.SetActive(true); } ModelSwaps.AddNewShopItems(); } else if (SceneName == "ShopSpecial") { - if (new System.Random().Next(100) < 3) { + if (new System.Random().Next(100) < 2) { GameObject.Find("merchant (1)").SetActive(false); GameObject.Find("Environment").transform.GetChild(3).gameObject.SetActive(true); } @@ -335,87 +362,109 @@ public static void SceneLoader_OnSceneLoaded_PostfixPatch(Scene loadingScene, Lo } else if (SceneName == "Maze Room") { foreach (Chest chest in Resources.FindObjectsOfTypeAll().Where(chest => chest.name == "Chest: Fairy")) { chest.transform.GetChild(4).gameObject.SetActive(false); - chest.transform.GetChild(7).gameObject.SetActive(false); - chest.transform.parent.GetChild(2).gameObject.SetActive(false); } } else if(SceneName == "frog cave main") { SetupFrogDomainSecret(); } else if(SceneName == "Sword Access") { - GameObject Bush = GameObject.Find("_Grass/bush (97)"); + GameObject Bush = GameObject.Find("_Grass/bush (70)"); if (Bush != null) { Bush.SetActive(false); } } - if (Archipelago.instance != null && Archipelago.instance.integration != null && Archipelago.instance.integration.connected) { - if (TunicArchipelago.Settings.EnemyRandomizerEnabled && EnemyRandomizer.Enemies.Count > 0 && !EnemyRandomizer.ExcludedScenes.Contains(SceneName)) { - EnemyRandomizer.SpawnNewEnemies(); + if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { + if (IsArchipelago()) { + TunicPortals.CreatePortalPairs(((JObject)Archipelago.instance.GetPlayerSlotData()["Entrance Rando"]).ToObject>()); + } else if (IsSinglePlayer()) { + TunicPortals.RandomizePortals(SaveFile.GetInt("seed")); } + TunicPortals.ModifyPortals(loadingScene); + TunicPortals.MarkPortals(); + } - if (TunicArchipelago.Settings.ArachnophobiaMode) { - EnemyRandomizer.ToggleArachnophobiaMode(); - } + if (TunicArchipelago.Settings.EnemyRandomizerEnabled && EnemyRandomizer.Enemies.Count > 0 && !EnemyRandomizer.ExcludedScenes.Contains(SceneName)) { + EnemyRandomizer.SpawnNewEnemies(); + } - if (Hints.HeroGraveHints.Count != 0) { - Hints.SetupHeroGraveToggle(); - } - try { - if (TunicArchipelago.Settings.UseCustomTexture) { - PaletteEditor.LoadCustomTexture(); - } - } catch (Exception ex) { - Logger.LogError("An error occurred applying custom texture:"); - Logger.LogError(ex.Message + " " + ex.StackTrace); - } - try { - if (!ModelSwaps.SwappedThisSceneAlready && (ItemLookup.ItemList.Count > 0 && SaveFile.GetInt("seed") != 0)) { - ModelSwaps.SwapItemsInScene(); - } - } catch (Exception ex) { - Logger.LogError("An error occurred swapping item models in this scene:"); - Logger.LogError(ex.Message + " " + ex.StackTrace); - } + if (TunicArchipelago.Settings.ArachnophobiaMode) { + EnemyRandomizer.ToggleArachnophobiaMode(); + } - if (SaveFile.GetInt("randomizer entrance rando enabled") == 1) { - TunicPortals.CreatePortalPairs(((JObject)Archipelago.instance.GetPlayerSlotData()["Entrance Rando"]).ToObject>()); - TunicPortals.ModifyPortals(loadingScene); + try { + if (!ModelSwaps.SwappedThisSceneAlready && (ItemLookup.ItemList.Count > 0 || Locations.RandomizedLocations.Count > 0) && SaveFile.GetInt("seed") != 0) { + ModelSwaps.SwapItemsInScene(); } + } catch (Exception ex) { + Logger.LogError("An error occurred swapping item models in this scene:"); + Logger.LogError(ex.Message + " " + ex.StackTrace); + } - - if (SaveFile.GetInt(AbilityShuffle) == 1 && SaveFile.GetInt(HolyCrossUnlocked) == 0) { - ItemPatches.ToggleHolyCrossObjects(false); + if (SaveFile.GetInt(AbilityShuffle) == 1 && SaveFile.GetInt(HolyCrossUnlocked) == 0) { + ItemPatches.ToggleHolyCrossObjects(false); + } + try { + if (PlayerCharacter.instance != null) { + FairyTargets.CreateFairyTargets(); + FairyTargets.CreateEntranceTargets(); + FairyTargets.FindFairyTargets(); } + } catch (Exception ex) { + Logger.LogError("An error occurred creating new fairy seeker spell targets:"); + Logger.LogError(ex.Message + " " + ex.StackTrace); + } - try { - if (TunicArchipelago.Settings.GhostFoxHintsEnabled && GhostHints.HintGhosts.Count > 0 && SaveFile.GetInt("seed") != 0) { - GhostHints.SpawnHintGhosts(SceneName); - SpawnedGhosts = true; - } - } catch (Exception ex) { - Logger.LogError("An error occurred spawning hint ghost foxes:"); - Logger.LogError(ex.Message + " " + ex.StackTrace); + try { + if (TunicArchipelago.Settings.UseCustomTexture) { + PaletteEditor.LoadCustomTexture(); } + } catch (Exception ex) { + Logger.LogError("An error occurred applying custom texture:"); + Logger.LogError(ex.Message + " " + ex.StackTrace); + } + if (TunicArchipelago.Settings.RealestAlwaysOn) { try { - FairyTargets.CreateFairyTargets(); - } catch (Exception ex) { - Logger.LogError("An error occurred creating new fairy seeker spell targets:"); - Logger.LogError(ex.Message + " " + ex.StackTrace); + GameObject.FindObjectOfType().SpellEffect(); + } catch (Exception e) { } + } - if (TunicArchipelago.Settings.RealestAlwaysOn) { - try { - GameObject.FindObjectOfType().SpellEffect(); - } catch (Exception e) { - } + if (Hints.HeroGraveHints.Count != 0) { + Hints.SetupHeroGraveToggle(); + } + + try { + if (TunicArchipelago.Settings.GhostFoxHintsEnabled && GhostHints.HintGhosts.Count > 0 && SaveFile.GetInt("seed") != 0) { + GhostHints.SpawnHintGhosts(SceneName); + SpawnedGhosts = true; } + } catch (Exception ex) { + Logger.LogError("An error occurred spawning hint ghost foxes:"); + Logger.LogError(ex.Message + " " + ex.StackTrace); + } + if (IsArchipelago()) { Archipelago.instance.integration.UpdateDataStorageOnLoad(); } ItemTracker.SaveTrackerFile(); } + private static void SpawnHeirFastTravel(string SceneName, Vector3 position) { + GameObject gameObject = GameObject.Instantiate(SpiritArenaTeleporterPrefab, position, SpiritArenaTeleporterPrefab.transform.rotation); + ScenePortal scenePortal = gameObject.transform.GetComponentInChildren(); + scenePortal.id = "heirfasttravel_spawnid"; + scenePortal.destinationSceneName = SceneName; + if (SceneManager.GetActiveScene().name == "Spirit Arena") { + scenePortal.spawnTransform = GameObject.Find("Teleporter").transform.GetChild(0).GetChild(0).GetChild(0); + gameObject.transform.GetChild(0).GetChild(1).gameObject.SetActive(false); + } else { + scenePortal.spawnTransform = gameObject.transform.GetChild(0).GetChild(0).GetChild(0); + } + scenePortal.optionalIDToSpawnAt = ""; + gameObject.SetActive(true); + } + public static void SetupOldHouseRelicToggles() { if (SceneManager.GetActiveScene().name == "Overworld Interiors" && GameObject.Find("_Offerings") != null && GameObject.Find("_Offerings").transform.childCount >= 5) { GameObject Offerings = GameObject.Find("_Offerings"); diff --git a/src/Patches/SpeedrunFinishlineDisplayPatches.cs b/src/Patches/SpeedrunFinishlineDisplayPatches.cs index 98d3483..70e8eb7 100644 --- a/src/Patches/SpeedrunFinishlineDisplayPatches.cs +++ b/src/Patches/SpeedrunFinishlineDisplayPatches.cs @@ -74,9 +74,10 @@ public class SpeedrunFinishlineDisplayPatches { public static GameObject CompletionRate; public static GameObject CompletionCanvas; - public static bool WasSpeedrunModeOn = false; public static bool ShowCompletionStatsAfterDelay = false; + public static bool GameCompleted = false; + public static bool SpeedrunFinishlineDisplay_showFinishline_PrefixPatch(SpeedrunFinishlineDisplay __instance) { SpeedrunReportItem DathStone = ScriptableObject.CreateInstance(); @@ -198,18 +199,25 @@ public static void SetupCompletionStatsDisplay() { CompletionCanvas.transform.position = new Vector3(0f, 0f, 300f); CompletionCanvas.SetActive(false); - int CheckCount = Locations.VanillaLocations.Keys.Where(Check => Locations.CheckedLocations[Check] || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Check} was collected") == 1)).Count(); - int ChecksCollectedByOthers = Locations.VanillaLocations.Keys.Where(Check => !Locations.CheckedLocations[Check] && SaveFile.GetInt($"randomizer {Check} was collected") == 1).Count(); - float CheckPercentage = ((float)CheckCount / ItemLookup.ItemList.Count) * 100.0f; + int CheckCount = 0; + int ChecksCollectedByOthers = 0; + float CheckPercentage = 0; + string Color = "<#FFFFFF>"; + + + CheckCount = Locations.VanillaLocations.Keys.Where(Check => Locations.CheckedLocations[Check] || (IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Check} was collected") == 1)).Count(); + ChecksCollectedByOthers = IsArchipelago() ? Locations.VanillaLocations.Keys.Where(Check => !Locations.CheckedLocations[Check] && SaveFile.GetInt($"randomizer {Check} was collected") == 1).Count() : 0; + CheckPercentage = ((float)CheckCount / Locations.VanillaLocations.Count) * 100.0f; + Color = CheckCount == Locations.VanillaLocations.Count ? $"<#eaa614>" : "<#FFFFFF>"; + GameObject TotalCompletion = GameObject.Instantiate(CompletionRate.gameObject, GameObject.Find("_FinishlineDisplay(Clone)/").transform.GetChild(2)); TotalCompletion.transform.position = new Vector3(-60f, -30f, 55f); TotalCompletion.transform.localScale = new Vector3(2.5f, 2.5f, 2.5f); - string Color = CheckCount == ItemLookup.ItemList.Count ? $"<#eaa614>" : "<#FFFFFF>"; - TotalCompletion.GetComponent().text = $"Overall Completion: {Color}{CheckCount}/{ItemLookup.ItemList.Count}" + - $"{(TunicArchipelago.Settings.CollectReflectsInWorld ? "*" : "")} " + + TotalCompletion.GetComponent().text = $"Overall Completion: {Color}{CheckCount}/{Locations.VanillaLocations.Count}" + + $"{(SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld ? "*" : "")} " + $"({Math.Round(CheckPercentage, 2)}%) {((int)CheckPercentage == 69 ? "nice" : "")}" + - $"{(TunicArchipelago.Settings.CollectReflectsInWorld ? $"\n\t*includes {ChecksCollectedByOthers} locations collected by others" : "")}"; + $"{(SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld ? $"\n\t*includes {ChecksCollectedByOthers} locations collected by others" : "")}"; TotalCompletion.GetComponent().fontSize = 100f; TotalCompletion.SetActive(true); @@ -270,7 +278,7 @@ public static void SetupCompletionCount(string Area, int index, int x) { float Percentage = 0; foreach (string SubArea in Locations.MainAreasToSubAreas[Area]) { TotalAreaChecks += Locations.VanillaLocations.Keys.Where(Check => Locations.VanillaLocations[Check].Location.SceneName == SubArea).Count(); - AreaChecksFound += Locations.VanillaLocations.Keys.Where(Check => Locations.VanillaLocations[Check].Location.SceneName == SubArea && (Locations.CheckedLocations[Check] || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Check} was collected") == 1))).Count(); + AreaChecksFound += Locations.VanillaLocations.Keys.Where(Check => Locations.VanillaLocations[Check].Location.SceneName == SubArea && (Locations.CheckedLocations[Check] || (SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Check} was collected") == 1))).Count(); TotalAreaTime += SaveFile.GetFloat($"randomizer play time {SubArea}"); } if (TotalAreaChecks > 0) { @@ -281,7 +289,10 @@ public static void SetupCompletionCount(string Area, int index, int x) { AreaCount.GetComponent().text = $"{(AreaChecksFound == TotalAreaChecks ? StatsScreenSecret[Area] : Area)}\n{TimeString}\n{Color}{AreaChecksFound} / {TotalAreaChecks} ({(int)Percentage}%)"; if (Area == "Swamp") { - AreaCount.GetComponent().text += "\n<#FFFFFF>Press 1 to hide stats.\nPress R to release items.\nPress C to collect items."; + AreaCount.GetComponent().text += "\n<#FFFFFF>Press 1 to hide stats."; + if (IsArchipelago()) { + AreaCount.GetComponent().text += "\nPress R to release items.\nPress C to collect items."; + } } if (Area == "Far Shore/Hero's Grave") { AreaCount.GetComponent().text = $"{AreaCount.GetComponent().text}"; @@ -296,7 +307,7 @@ public static void SetupCompletionCount(string Area, int index, int x) { foreach (Dictionary requirements in Locations.VanillaLocations[Key].Location.RequiredItems) { if (requirements.ContainsKey("21")) { TotalChecks++; - if (Locations.CheckedLocations[Key] || (TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Key} was collected") == 1)) { + if (Locations.CheckedLocations[Key] || (SaveFlags.IsArchipelago() && TunicArchipelago.Settings.CollectReflectsInWorld && SaveFile.GetInt($"randomizer {Key} was collected") == 1)) { ChecksFound++; } continue; @@ -352,7 +363,7 @@ public static void SetupCompletionCount(string Area, int index, int x) { float GrappleTime = SaveFile.GetFloat("randomizer Magic Orb 1 time"); float HyperdashTime = SaveFile.GetFloat("randomizer Hero's Laurels 1 time"); Text += $"Sword:\t{FormatTime(SwordTime, true)}\n" + - $"Grapple:\t{FormatTime(GrappleTime, true)}\t" + + $"Magic Orb:\t{FormatTime(GrappleTime, true)}\t" + $"Laurels:\t{FormatTime(HyperdashTime, true)}\n"; int Total = 6; if (SaveFile.GetInt(AbilityShuffle) == 1) { @@ -395,11 +406,16 @@ public static bool GameOverDecision___retry_PrefixPatch(GameOverDecision __insta if (CompletionCanvas != null) { GameObject.Destroy(CompletionCanvas); } - if (Archipelago.instance != null && Archipelago.instance.integration != null) { + + + if (IsArchipelago()) { Archipelago.instance.integration.sentCompletion = false; Archipelago.instance.integration.sentCollect = false; Archipelago.instance.integration.sentRelease = false; } + + GameCompleted = false; + return true; } @@ -407,11 +423,14 @@ public static bool GameOverDecision___newgame_PrefixPatch(GameOverDecision __ins if (CompletionCanvas != null) { GameObject.Destroy(CompletionCanvas); } - if (Archipelago.instance != null && Archipelago.instance.integration != null) { + if (IsArchipelago()) { Archipelago.instance.integration.sentCompletion = false; Archipelago.instance.integration.sentCollect = false; Archipelago.instance.integration.sentRelease = false; } + + GameCompleted = false; + return true; } diff --git a/src/Patches/SwordProgression.cs b/src/Patches/SwordProgression.cs index 1305350..d05822b 100644 --- a/src/Patches/SwordProgression.cs +++ b/src/Patches/SwordProgression.cs @@ -16,13 +16,13 @@ public static void UpgradeSword(int SwordLevel) { //fownd ahn Itehm! Inventory.GetItemByName("Stick").Quantity = 1; Inventory.GetItemByName("Stick").collectionMessage = ScriptableObject.CreateInstance(); - Inventory.GetItemByName("Stick").collectionMessage.text = $"fownd ahn Itehm! \"(<#8ddc6e>Lv. 1<#FFFFFF>)\""; + Inventory.GetItemByName("Stick").collectionMessage.text = TunicArchipelago.Settings.UseTrunicTranslations ? $"fownd ahn Itehm! (<#8ddc6e>lehvuhl 1<#FFFFFF>)" : $"fownd ahn Itehm! \"(<#8ddc6e>Lv. 1<#FFFFFF>)\""; ItemPresentation.PresentItem(Inventory.GetItemByName("Stick")); } else if (SwordLevel == 2) { Inventory.GetItemByName("Sword").Quantity = 1; Inventory.GetItemByName("Sword").collectionMessage = ScriptableObject.CreateInstance(); - Inventory.GetItemByName("Sword").collectionMessage.text = $"fownd ahn Itehm! \"(<#e99d4c>Lv. 2<#FFFFFF>)\""; + Inventory.GetItemByName("Sword").collectionMessage.text = TunicArchipelago.Settings.UseTrunicTranslations ? $"fownd ahn Itehm! (<#e99d4c>lehvuhl 2<#FFFFFF>)" : $"fownd ahn Itehm! \"(<#e99d4c>Lv. 2<#FFFFFF>)\""; Inventory.GetItemByName("Sword").useAlreadyHaveOneMessage = false; ItemPresentation.PresentItem(Inventory.GetItemByName("Sword")); List items = Inventory.buttonAssignedItems.ToList(); @@ -35,6 +35,7 @@ public static void UpgradeSword(int SwordLevel) { Inventory.buttonAssignedItems = items.ToArray(); } else if (SwordLevel == 3) { Inventory.GetItemByName("Librarian Sword").Quantity = 1; + Inventory.GetItemByName("Librarian Sword").collectionMessage.text = TunicArchipelago.Settings.UseTrunicTranslations ? $" ? ? ? (<#ca7be4>lehvuhl 3<#FFFFFF>)" : $"\" ? ? ? (<#ca7be4>Lv. 3<#FFFFFF>)\""; ItemPresentation.PresentItem(Inventory.GetItemByName("Librarian Sword")); Inventory.GetItemByName("Level Up - Attack").Quantity += 1; TunicArchipelago.Tracker.ImportantItems["Level Up - Attack"] = Inventory.GetItemByName("Level Up - Attack").Quantity; @@ -48,6 +49,7 @@ public static void UpgradeSword(int SwordLevel) { Inventory.buttonAssignedItems = items.ToArray(); } else if (SwordLevel >= 4) { Inventory.GetItemByName("Heir Sword").Quantity = 1; + Inventory.GetItemByName("Heir Sword").collectionMessage.text = TunicArchipelago.Settings.UseTrunicTranslations ? $" ! ! ! (<#5de7cf>lehvuhl 4<#FFFFFF>)" : $"\" ! ! ! (<#5de7cf>Lv. 4<#FFFFFF>)\""; ItemPresentation.PresentItem(Inventory.GetItemByName("Heir Sword")); Inventory.GetItemByName("Level Up - Attack").Quantity += 1; TunicArchipelago.Tracker.ImportantItems["Level Up - Attack"] = Inventory.GetItemByName("Level Up - Attack").Quantity; @@ -171,8 +173,18 @@ public static void CreateSwordItemBehaviours(PlayerCharacter instance) { public static bool HitReceiver_ReceiveHit_PrefixPatch(HitReceiver __instance, ref HitType hitType, ref bool unblockable, ref bool isPlayerCharacterMelee) { + // Disables hitting the west bell from long range for race purposes + if (__instance.GetComponent() != null && __instance.name == "tuning fork" && SceneManager.GetActiveScene().name == "Overworld Redux" + && hitType == HitType.TECHBOW && TunicArchipelago.Settings.RaceMode && TunicArchipelago.Settings.DisableDistantBellShots) { + if (PlayerCharacter.instance.transform.position.x > __instance.transform.position.x + 5 + || PlayerCharacter.instance.transform.position.z > __instance.transform.position.z + 5) { + return false; + } + } + + // Allows lvl 4 sword to hit bells/switches, also tells AP data storage if bells were rung if ((__instance.GetComponent() != null || __instance.GetComponent() != null) && isPlayerCharacterMelee) { - if (__instance.name == "tuning fork") { + if (__instance.name == "tuning fork" && IsArchipelago()) { if (SceneManager.GetActiveScene().name == "Forest Belltower") { Archipelago.instance.UpdateDataStorage("Rang East Bell", true); } diff --git a/src/Patches/TextBuilderPatches.cs b/src/Patches/TextBuilderPatches.cs index 2abda41..551e415 100644 --- a/src/Patches/TextBuilderPatches.cs +++ b/src/Patches/TextBuilderPatches.cs @@ -237,7 +237,7 @@ public class TextBuilderPatches { { "Pages 46-47", "[book]" }, { "Pages 48-49", "[book]" }, { "Pages 50-51", "[book]" }, - { "Pages 52-53 (Ice Rod)", "[book]" }, + { "Pages 52-53 (Icebolt)", "[book]" }, { "Pages 54-55", "[book]" }, // Non-Tunic Item { "Archipelago Item", "[archipelago]"} diff --git a/src/Patches/TitleVersion.cs b/src/Patches/TitleVersion.cs index 2cd2f90..59a2927 100644 --- a/src/Patches/TitleVersion.cs +++ b/src/Patches/TitleVersion.cs @@ -15,54 +15,51 @@ namespace TunicArchipelago { public class TitleVersion { public static bool Loaded = false; - public static GameObject TitleLogo; + public static GameObject VersionString; public static bool DevBuild = true; + public static bool UpdateAvailable = false; + public static string UpdateVersion = ""; public static void Initialize() { - if (!Loaded) { - bool UpdateAvailable = false; - string UpdateVersion = PluginInfo.VERSION; - try { - HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://api.github.com/repos/silent-destroyer/tunic-randomizer-archipelago/releases"); - Request.UserAgent = "request"; - HttpWebResponse response = (HttpWebResponse)Request.GetResponse(); - StreamReader Reader = new StreamReader(response.GetResponseStream()); - string JsonResponse = Reader.ReadToEnd(); - dynamic Releases = JsonConvert.DeserializeObject(JsonResponse); - UpdateAvailable = Releases[0]["tag_name"].ToString() != PluginInfo.VERSION && !DevBuild; - UpdateVersion = Releases[0]["tag_name"].ToString(); - } catch (Exception e) { - TunicArchipelago.Logger.LogInfo(e.Message); - } - TMP_FontAsset FontAsset = Resources.FindObjectsOfTypeAll().Where(Font => Font.name == "Latin Rounded").ToList()[0]; - Material FontMaterial = Resources.FindObjectsOfTypeAll().Where(Material => Material.name == "Latin Rounded - Quantity Outline").ToList()[0]; - GameObject TitleVersion = new GameObject("randomizer version"); - TitleVersion.AddComponent().text = $"Randomizer + Archipelago Mod Ver. {PluginInfo.VERSION}{(DevBuild ? "-dev" : "")}"; - if (UpdateAvailable) { - TitleVersion.GetComponent().text += $" (Update Available: v{UpdateVersion}!)"; - } - TitleVersion.GetComponent().color = new Color(1.0f, 0.64f, 0.0f); - TitleVersion.GetComponent().fontMaterial = FontMaterial; - TitleVersion.GetComponent().font = FontAsset; - TitleVersion.GetComponent().fontSize = 16; - TitleVersion.layer = 5; - TitleVersion.transform.parent = GameObject.Find("_GameGUI(Clone)/Title Canvas/Title Screen Root/").transform; - TitleVersion.GetComponent().sizeDelta = new Vector2(700f, 50f); - if (Screen.width <= 1280 && Screen.height <= 800) { - TitleVersion.transform.localPosition = new Vector3(-122f, 240f, 0f); - } else { - TitleVersion.transform.localPosition = new Vector3(-176f, 240f, 0f); - } - TitleVersion.transform.localScale = Vector3.one; - GameObject.DontDestroyOnLoad(TitleVersion); - System.Random Random = new System.Random(); - - if (Random.Next(100) < 10) { - GameObject Title = GameObject.Find("_GameGUI(Clone)/Title Canvas/Title Screen Root/Image"); - Title.GetComponent().sprite = ModelSwaps.TuncTitleImage.GetComponent().sprite; - } + UpdateVersion = PluginInfo.VERSION; + try { + HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://api.github.com/repos/silent-destroyer/tunic-randomizer/releases"); + Request.UserAgent = "request"; + HttpWebResponse response = (HttpWebResponse)Request.GetResponse(); + StreamReader Reader = new StreamReader(response.GetResponseStream()); + string JsonResponse = Reader.ReadToEnd(); + dynamic Releases = JsonConvert.DeserializeObject(JsonResponse); + UpdateAvailable = Releases[0]["tag_name"].ToString() != PluginInfo.VERSION && !DevBuild; + UpdateVersion = Releases[0]["tag_name"].ToString(); + } catch (Exception e) { + TunicArchipelago.Logger.LogInfo(e.Message); + } + TMP_FontAsset FontAsset = Resources.FindObjectsOfTypeAll().Where(Font => Font.name == "Latin Rounded").ToList()[0]; + Material FontMaterial = Resources.FindObjectsOfTypeAll().Where(Material => Material.name == "Latin Rounded - Quantity Outline").ToList()[0]; + VersionString = new GameObject("randomizer version"); + VersionString.AddComponent().text = $"Randomizer Mod Ver. {PluginInfo.VERSION}{(DevBuild ? "-dev" : "")}"; + if (UpdateAvailable) { + VersionString.GetComponent().text += $" (Update Available: v{UpdateVersion}!)"; } - Loaded = true; + VersionString.GetComponent().color = new Color(1.0f, 0.64f, 0.0f); + VersionString.GetComponent().fontMaterial = FontMaterial; + VersionString.GetComponent().font = FontAsset; + VersionString.GetComponent().fontSize = 24; + VersionString.layer = 5; + VersionString.transform.parent = GameObject.Find("_GameGUI(Clone)/Title Canvas/Title Screen Root/").transform; + VersionString.GetComponent().sizeDelta = new Vector2(700f, 50f); + if (Screen.width <= 1280 && Screen.height <= 800) { + VersionString.transform.localPosition = new Vector3(-122f, 240f, 0f); + } else { + VersionString.transform.localPosition = new Vector3(-176f, 240f, 0f); + } + VersionString.transform.localScale = Vector3.one; + GameObject.DontDestroyOnLoad(VersionString); + System.Random Random = new System.Random(); + if (Random.Next(100) < 10) { + GameObject Title = GameObject.Find("_GameGUI(Clone)/Title Canvas/Title Screen Root/Image"); + Title.GetComponent().sprite = ModelSwaps.TuncTitleImage.GetComponent().sprite; + } } } diff --git a/src/Patches/TunicPortals.cs b/src/Patches/TunicPortals.cs index 21ded01..ce2f1e1 100644 --- a/src/Patches/TunicPortals.cs +++ b/src/Patches/TunicPortals.cs @@ -1,686 +1,3991 @@ -using System.Collections.Generic; -using UnityEngine.SceneManagement; +using BepInEx.Logging; +using System.Collections.Generic; +using System.Linq; using UnityEngine; -using BepInEx.Logging; +using UnityEngine.SceneManagement; -namespace TunicArchipelago -{ +namespace TunicArchipelago { public class TunicPortals { private static ManualLogSource Logger = TunicArchipelago.Logger; public static Dictionary RandomizedPortals = new Dictionary(); public class TunicPortal { - public string SceneName; // the scene the portal is in - public string Destination; // the vanilla destination scene - public string DestinationTag; // the vanilla destination tag, aka ID - public string PortalName; // a human-readable name for the portal - public string GranularRegion; // a sub-region name, if there is one for that scene. For use in making sure everything can be accessed - public Dictionary RequiredItems; // required items if there is only one item or one set of items required. A string like "scene, destination_tag" counts as an item. - public List> RequiredItemsOr; // required items if there are multiple different possible requirements. A string like "scene, destination_tag" counts as an item. - public List GivesAccess; // portals that you are given access to by this portal. ex: the dance fox portal to the lower east forest portal in guardhouse 1. - public Dictionary EntryItems; // portals that require items to enter, but not exit from. ex: hero's graves, the yellow prayer portal pads, and the fountain holy cross door in overworld. - public bool DeadEnd; // portals that are dead ends, like stick house or the gauntlet lower entry. - public bool PrayerPortal; // portals that require prayer to enter. This is a more convenient version of GivesAccess for prayer portals. - public bool OneWay; // portals that are one-way, such as the back entrance to monastery and the forest belltower top portal - public bool IgnoreScene; // portals that cannot reach the center of the region, and as such do not give region access, like the rail between beneath the well and furnace - - public TunicPortal() { } - - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, bool prayerPortal = false, bool deadEnd = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - PrayerPortal = prayerPortal; - DeadEnd = deadEnd; - OneWay = oneWay; - IgnoreScene = ignoreScene; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, Dictionary entryItems, bool prayerPortal = false, bool deadEnd = false, bool oneWay = false, bool ignoreScene = false) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - EntryItems = entryItems; - PrayerPortal = prayerPortal; - DeadEnd = deadEnd; - OneWay = oneWay; - IgnoreScene = ignoreScene; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, Dictionary requiredItems, bool prayerPortal = false, bool deadEnd = false, bool ignoreScene = false) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - RequiredItems = requiredItems; - PrayerPortal = prayerPortal; - DeadEnd = deadEnd; - IgnoreScene = ignoreScene; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, List givesAccess, bool ignoreScene = false, bool oneWay = false) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - GivesAccess = givesAccess; - IgnoreScene = ignoreScene; - OneWay = oneWay; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, Dictionary requiredItems) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - RequiredItems = requiredItems; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, Dictionary requiredItems, List givesAccess, bool ignoreScene = false) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - RequiredItems = requiredItems; - IgnoreScene = ignoreScene; - GivesAccess = givesAccess; - } - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, List> requiredItemsOr, bool prayerPortal = false, bool ignoreScene = false) { + public string Name; + public string Destination; + + public TunicPortal(string name, string destination) { + Name = name; Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - RequiredItemsOr = requiredItemsOr; - PrayerPortal = prayerPortal; - IgnoreScene = ignoreScene; } + } + + public class RegionInfo { + public string Scene; + public bool DeadEnd; - public TunicPortal(string destination, string destinationTag, string portalName, string granularRegion, Dictionary requiredItems, List givesAccess) { - Destination = destination; - DestinationTag = destinationTag; - PortalName = portalName; - GranularRegion = granularRegion; - RequiredItems = requiredItems; - GivesAccess = givesAccess; + public RegionInfo(string scene, bool deadEnd) { + Scene = scene; + DeadEnd = deadEnd; } } - // this is a big list of every portal in the game, along with their access requirements - // a portal without access requirements just means you can get to the center of the region from that portal and vice versa - public static Dictionary> PortalList = new Dictionary> - { + public static Dictionary>> RegionPortalsList = new Dictionary>> { + { + "Overworld Redux", + new Dictionary> { + { + "Overworld", + new List { + new TunicPortal("Stick House Entrance", "Sword Cave_"), + new TunicPortal("Windmill Entrance", "Windmill_"), + new TunicPortal("Well Ladder Entrance", "Sewer_entrance"), + new TunicPortal("Old House Waterfall Entrance", "Overworld Interiors_under_checkpoint"), + new TunicPortal("Entrance to Furnace under Windmill", "Furnace_gyro_upper_east"), + new TunicPortal("Entrance to Furnace from Beach", "Furnace_gyro_lower"), + new TunicPortal("Caustic Light Cave Entrance", "Overworld Cave_"), + new TunicPortal("Swamp Lower Entrance", "Swamp Redux 2_conduit"), + new TunicPortal("Ruined Passage Not-Door Entrance", "Ruins Passage_east"), + new TunicPortal("Atoll Upper Entrance", "Atoll Redux_upper"), + new TunicPortal("Atoll Lower Entrance", "Atoll Redux_lower"), + new TunicPortal("Maze Cave Entrance", "Maze Room_"), + new TunicPortal("Temple Rafters Entrance", "Temple_rafters"), + new TunicPortal("Ruined Shop Entrance", "Ruined Shop_"), + new TunicPortal("Patrol Cave Entrance", "PatrolCave_"), + new TunicPortal("Hourglass Cave Entrance", "Town Basement_beach"), + new TunicPortal("Changing Room Entrance", "Changing Room_"), + new TunicPortal("Cube Cave Entrance", "CubeRoom_"), + new TunicPortal("Stairs from Overworld to Mountain", "Mountain_"), + new TunicPortal("Overworld to Fortress", "Fortress Courtyard_"), + new TunicPortal("Overworld to Quarry Connector", "Darkwoods Tunnel_"), + new TunicPortal("Dark Tomb Main Entrance", "Crypt Redux_"), + new TunicPortal("Overworld to Forest Belltower", "Forest Belltower_"), + new TunicPortal("Secret Gathering Place Entrance", "Waterfall_"), + } + }, + { + "Overworld Well to Furnace Rail", + new List { + new TunicPortal("Entrance to Well from Well Rail", "Sewer_west_aqueduct"), + new TunicPortal("Entrance to Furnace from Well Rail", "Furnace_gyro_upper_north"), + } + }, + { + "Overworld Old House Door", + new List { + new TunicPortal("Old House Door Entrance", "Overworld Interiors_house"), + } + }, + { + "Overworld to West Garden from Furnace", + new List { + new TunicPortal("Entrance to Furnace near West Garden", "Furnace_gyro_west"), + new TunicPortal("West Garden Entrance from Furnace", "Archipelagos Redux_lower"), + } + }, + { + "Overworld Swamp Upper Entry", + new List { + new TunicPortal("Swamp Upper Entrance", "Swamp Redux 2_wall"), + } + }, + { + "Overworld Ruined Passage Door", + new List { + new TunicPortal("Ruined Passage Door Entrance", "Ruins Passage_west"), + } + }, + { + "Overworld Special Shop Entry", + new List { + new TunicPortal("Special Shop Entrance", "ShopSpecial_"), + } + }, + { + "Overworld Belltower", + new List { + new TunicPortal("West Garden Entrance near Belltower", "Archipelagos Redux_upper"), + } + }, + { + "Overworld West Garden Laurels Entry", + new List { + new TunicPortal("West Garden Laurels Entrance", "Archipelagos Redux_lowest"), + } + }, + { + "Overworld Temple Door", + new List { + new TunicPortal("Temple Door Entrance", "Temple_main"), + } + }, + { + "Overworld Fountain Cross Door", + new List { + new TunicPortal("Fountain HC Door Entrance", "Town_FiligreeRoom_"), + } + }, + { + "Overworld Southeast Cross Door", + new List { + new TunicPortal("Southeast HC Door Entrance", "EastFiligreeCache_"), + } + }, + { + "Overworld Town Portal", + new List { + new TunicPortal("Town to Far Shore", "Transit_teleporter_town"), + } + }, + { + "Overworld Spawn Portal", + new List { + new TunicPortal("Spawn to Far Shore", "Transit_teleporter_starting island"), + } + }, + } + }, + { + "Waterfall", + new Dictionary> { + { + "Secret Gathering Place", + new List { + new TunicPortal("Secret Gathering Place Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Windmill", + new Dictionary> { + { + "Windmill", + new List { + new TunicPortal("Windmill Exit", "Overworld Redux_"), + new TunicPortal("Windmill Shop", "Shop_"), + } + }, + } + }, + { + "Overworld Interiors", + new Dictionary> { + { + "Old House Front", + new List { + new TunicPortal("Old House Door Exit", "Overworld Redux_house"), + new TunicPortal("Old House to Glyph Tower", "g_elements_"), + } + }, + { + "Old House Back", + new List { + new TunicPortal("Old House Waterfall Exit", "Overworld Redux_under_checkpoint"), + } + }, + } + }, + { + "g_elements", + new Dictionary> { + { + "Relic Tower", + new List { + new TunicPortal("Glyph Tower Exit", "Overworld Interiors_"), + } + }, + } + }, + { + "Changing Room", + new Dictionary> { + { + "Changing Room", + new List { + new TunicPortal("Changing Room Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Town_FiligreeRoom", + new Dictionary> { + { + "Fountain Cross Room", + new List { + new TunicPortal("Fountain HC Room Exit", "Overworld Redux_"), + } + }, + } + }, + { + "CubeRoom", + new Dictionary> { + { + "Cube Cave", + new List { + new TunicPortal("Cube Cave Exit", "Overworld Redux_"), + } + }, + } + }, + { + "PatrolCave", + new Dictionary> { + { + "Patrol Cave", + new List { + new TunicPortal("Guard Patrol Cave Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Ruined Shop", + new Dictionary> { + { + "Ruined Shop", + new List { + new TunicPortal("Ruined Shop Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Furnace", + new Dictionary> { + { + "Furnace Fuse", + new List { + new TunicPortal("Furnace Exit towards Well", "Overworld Redux_gyro_upper_north"), + } + }, + { + "Furnace Walking Path", + new List { + new TunicPortal("Furnace Exit to Dark Tomb", "Crypt Redux_"), + new TunicPortal("Furnace Exit towards West Garden", "Overworld Redux_gyro_west"), + } + }, + { + "Furnace Ladder Area", + new List { + new TunicPortal("Furnace Exit to Beach", "Overworld Redux_gyro_lower"), + new TunicPortal("Furnace Exit under Windmill", "Overworld Redux_gyro_upper_east"), + } + }, + } + }, + { + "Sword Cave", + new Dictionary> { + { + "Stick House", + new List { + new TunicPortal("Stick House Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Ruins Passage", + new Dictionary> { + { + "Ruined Passage", + new List { + new TunicPortal("Ruined Passage Not-Door Exit", "Overworld Redux_east"), + new TunicPortal("Ruined Passage Door Exit", "Overworld Redux_west"), + } + }, + } + }, + { + "EastFiligreeCache", + new Dictionary> { + { + "Southeast Cross Room", + new List { + new TunicPortal("Southeast HC Room Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Overworld Cave", + new Dictionary> { + { + "Caustic Light Cave", + new List { + new TunicPortal("Caustic Light Cave Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Maze Room", + new Dictionary> { + { + "Maze Cave", + new List { + new TunicPortal("Maze Cave Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Town Basement", + new Dictionary> { + { + "Hourglass Cave", + new List { + new TunicPortal("Hourglass Cave Exit", "Overworld Redux_beach"), + } + }, + } + }, + { + "ShopSpecial", + new Dictionary> { + { + "Special Shop", + new List { + new TunicPortal("Special Shop Exit", "Overworld Redux_"), + } + }, + } + }, + { + "Temple", + new Dictionary> { + { + "Sealed Temple Rafters", + new List { + new TunicPortal("Temple Rafters Exit", "Overworld Redux_rafters"), + } + }, + { + "Sealed Temple", + new List { + new TunicPortal("Temple Door Exit", "Overworld Redux_main"), + } + }, + } + }, + { + "Sewer", + new Dictionary> { + { + "Beneath the Well Front", + new List { + new TunicPortal("Well Ladder Exit", "Overworld Redux_entrance"), + } + }, + { + "Beneath the Well Back", + new List { + new TunicPortal("Well to Well Boss", "Sewer_Boss_"), + new TunicPortal("Well Exit towards Furnace", "Overworld Redux_west_aqueduct"), + } + }, + } + }, + { + "Sewer_Boss", + new Dictionary> { + { + "Well Boss", + new List { + new TunicPortal("Well Boss to Well", "Sewer_"), + } + }, + { + "Dark Tomb Checkpoint", + new List { + new TunicPortal("Checkpoint to Dark Tomb", "Crypt Redux_"), + } + }, + } + }, + { + "Crypt Redux", + new Dictionary> { + { + "Dark Tomb Entry Point", + new List { + new TunicPortal("Dark Tomb to Overworld", "Overworld Redux_"), + new TunicPortal("Dark Tomb to Checkpoint", "Sewer_Boss_"), + } + }, + { + "Dark Tomb Dark Exit", + new List { + new TunicPortal("Dark Tomb to Furnace", "Furnace_"), + } + }, + } + }, + { + "Archipelagos Redux", + new Dictionary> { + { + "West Garden", + new List { + new TunicPortal("West Garden Exit near Hero's Grave", "Overworld Redux_lower"), + new TunicPortal("West Garden to Magic Dagger House", "archipelagos_house_"), + new TunicPortal("West Garden Shop", "Shop_"), + } + }, + { + "West Garden after Boss", + new List { + new TunicPortal("West Garden Exit after Boss", "Overworld Redux_upper"), + } + }, + { + "West Garden Laurels Exit", + new List { + new TunicPortal("West Garden Laurels Exit", "Overworld Redux_lowest"), + } + }, + { + "West Garden Hero's Grave", + new List { + new TunicPortal("West Garden Hero's Grave", "RelicVoid_teleporter_relic plinth"), + } + }, + { + "West Garden Portal", + new List { + new TunicPortal("West Garden to Far Shore", "Transit_teleporter_archipelagos_teleporter"), + } + }, + } + }, + { + "archipelagos_house", + new Dictionary> { + { + "Magic Dagger House", + new List { + new TunicPortal("Magic Dagger House Exit", "Archipelagos Redux_"), + } + }, + } + }, + { + "Atoll Redux", + new Dictionary> { + { + "Ruined Atoll", + new List { + new TunicPortal("Atoll Upper Exit", "Overworld Redux_upper"), + new TunicPortal("Atoll Shop", "Shop_"), + new TunicPortal("Frog Stairs Eye Entrance", "Frog Stairs_eye"), + } + }, + { + "Ruined Atoll Lower Entry Area", + new List { + new TunicPortal("Atoll Lower Exit", "Overworld Redux_lower"), + } + }, + { + "Ruined Atoll Portal", + new List { + new TunicPortal("Atoll to Far Shore", "Transit_teleporter_atoll"), + new TunicPortal("Atoll Statue Teleporter", "Library Exterior_"), + } + }, + { + "Ruined Atoll Frog Mouth", + new List { + new TunicPortal("Frog Stairs Mouth Entrance", "Frog Stairs_mouth"), + } + }, + } + }, + { + "Frog Stairs", + new Dictionary> { + { + "Frog's Domain Entry", + new List { + new TunicPortal("Frog Stairs Eye Exit", "Atoll Redux_eye"), + new TunicPortal("Frog Stairs Mouth Exit", "Atoll Redux_mouth"), + new TunicPortal("Frog Stairs to Frog's Domain's Entrance", "frog cave main_Entrance"), + new TunicPortal("Frog Stairs to Frog's Domain's Exit", "frog cave main_Exit"), + } + }, + } + }, + { + "frog cave main", + new Dictionary> { + { + "Frog's Domain", + new List { + new TunicPortal("Frog's Domain Ladder Exit", "Frog Stairs_Entrance"), + } + }, + { + "Frog's Domain Back", + new List { + new TunicPortal("Frog's Domain Orb Exit", "Frog Stairs_Exit"), + } + }, + } + }, + { + "Library Exterior", + new Dictionary> { + { + "Library Exterior Tree", + new List { + new TunicPortal("Library Exterior Tree", "Atoll Redux_"), + } + }, + { + "Library Exterior Ladder", + new List { + new TunicPortal("Library Exterior Ladder", "Library Hall_"), + } + }, + } + }, + { + "Library Hall", + new Dictionary> { + { + "Library Hall", + new List { + new TunicPortal("Library Hall Bookshelf Exit", "Library Exterior_"), + new TunicPortal("Library Hall to Rotunda", "Library Rotunda_"), + } + }, + { + "Library Hero's Grave", + new List { + new TunicPortal("Library Hero's Grave", "RelicVoid_teleporter_relic plinth"), + } + }, + } + }, + { + "Library Rotunda", + new Dictionary> { + { + "Library Rotunda", + new List { + new TunicPortal("Library Rotunda Lower Exit", "Library Hall_"), + new TunicPortal("Library Rotunda Upper Exit", "Library Lab_"), + } + }, + } + }, + { + "Library Lab", + new Dictionary> { + { + "Library Lab Lower", + new List { + new TunicPortal("Library Lab to Rotunda", "Library Rotunda_"), + } + }, + { + "Library Portal", + new List { + new TunicPortal("Library to Far Shore", "Transit_teleporter_library teleporter"), + } + }, + { + "Library Lab", + new List { + new TunicPortal("Library Lab to Librarian Arena", "Library Arena_"), + } + }, + } + }, + { + "Library Arena", + new Dictionary> { + { + "Library Arena", + new List { + new TunicPortal("Librarian Arena Exit", "Library Lab_"), + } + }, + } + }, + { + "East Forest Redux", + new Dictionary> { + { + "East Forest", + new List { + new TunicPortal("Forest to Belltower", "Forest Belltower_"), + new TunicPortal("Forest Guard House 1 Lower Entrance", "East Forest Redux Laddercave_lower"), + new TunicPortal("Forest Guard House 1 Gate Entrance", "East Forest Redux Laddercave_gate"), + new TunicPortal("Forest Guard House 2 Lower Entrance", "East Forest Redux Interior_lower"), + new TunicPortal("Forest Guard House 2 Upper Entrance", "East Forest Redux Interior_upper"), + new TunicPortal("Forest Grave Path Lower Entrance", "Sword Access_lower"), + new TunicPortal("Forest Grave Path Upper Entrance", "Sword Access_upper"), + } + }, + { + "East Forest Dance Fox Spot", + new List { + new TunicPortal("Forest Dance Fox Outside Doorway", "East Forest Redux Laddercave_upper"), + } + }, + { + "East Forest Portal", + new List { + new TunicPortal("Forest to Far Shore", "Transit_teleporter_forest teleporter"), + } + }, + } + }, + { + "East Forest Redux Laddercave", + new Dictionary> { + { + "Guard House 1 West", + new List { + new TunicPortal("Guard House 1 Dance Fox Exit", "East Forest Redux_upper"), + new TunicPortal("Guard House 1 Lower Exit", "East Forest Redux_lower"), + } + }, + { + "Guard House 1 East", + new List { + new TunicPortal("Guard House 1 Upper Forest Exit", "East Forest Redux_gate"), + new TunicPortal("Guard House 1 to Guard Captain Room", "Forest Boss Room_"), + } + }, + } + }, + { + "Sword Access", + new Dictionary> { + { + "Forest Grave Path Upper", + new List { + new TunicPortal("Forest Grave Path Upper Exit", "East Forest Redux_upper"), + } + }, + { + "Forest Grave Path Main", + new List { + new TunicPortal("Forest Grave Path Lower Exit", "East Forest Redux_lower"), + } + }, + { + "Forest Hero's Grave", + new List { + new TunicPortal("East Forest Hero's Grave", "RelicVoid_teleporter_relic plinth"), + } + }, + } + }, + { + "East Forest Redux Interior", + new Dictionary> { + { + "Guard House 2", + new List { + new TunicPortal("Guard House 2 Lower Exit", "East Forest Redux_lower"), + new TunicPortal("Guard House 2 Upper Exit", "East Forest Redux_upper"), + } + }, + } + }, + { + "Forest Boss Room", + new Dictionary> { + { + "Forest Boss Room", + new List { + new TunicPortal("Guard Captain Room Non-Gate Exit", "East Forest Redux Laddercave_"), + new TunicPortal("Guard Captain Room Gate Exit", "Forest Belltower_"), + } + }, + } + }, + { + "Forest Belltower", + new Dictionary> { + { + "Forest Belltower Main", + new List { + new TunicPortal("Forest Belltower to Fortress", "Fortress Courtyard_"), + new TunicPortal("Forest Belltower to Overworld", "Overworld Redux_"), + } + }, + { + "Forest Belltower Lower", + new List { + new TunicPortal("Forest Belltower to Forest", "East Forest Redux_"), + } + }, + { + "Forest Belltower Upper", + new List { + new TunicPortal("Forest Belltower to Guard Captain Room", "Forest Boss Room_"), + } + }, + } + }, + { + "Fortress Courtyard", + new Dictionary> { + { + "Fortress Courtyard", + new List { + new TunicPortal("Fortress Courtyard to Fortress Grave Path Lower", "Fortress Reliquary_Lower"), + new TunicPortal("Fortress Courtyard to Fortress Interior", "Fortress Main_Big Door"), + } + }, + { + "Fortress Courtyard Upper", + new List { + new TunicPortal("Fortress Courtyard to Fortress Grave Path Upper", "Fortress Reliquary_Upper"), + new TunicPortal("Fortress Courtyard to East Fortress", "Fortress East_"), + } + }, + { + "Fortress Exterior near cave", + new List { + new TunicPortal("Fortress Courtyard to Beneath the Earth", "Fortress Basement_"), + new TunicPortal("Fortress Courtyard Shop", "Shop_"), + } + }, + { + "Fortress Exterior from East Forest", + new List { + new TunicPortal("Fortress Courtyard to Forest Belltower", "Forest Belltower_"), + } + }, + { + "Fortress Exterior from Overworld", + new List { + new TunicPortal("Fortress Courtyard to Overworld", "Overworld Redux_"), + } + }, + } + }, + { + "Fortress Basement", + new Dictionary> { + { + "Beneath the Vault Back", + new List { + new TunicPortal("Beneath the Earth to Fortress Interior", "Fortress Main_"), + } + }, + { + "Beneath the Vault Front", + new List { + new TunicPortal("Beneath the Earth to Fortress Courtyard", "Fortress Courtyard_"), + } + }, + } + }, + { + "Fortress Main", + new Dictionary> { + { + "Eastern Vault Fortress", + new List { + new TunicPortal("Fortress Interior Main Exit", "Fortress Courtyard_Big Door"), + new TunicPortal("Fortress Interior to Beneath the Earth", "Fortress Basement_"), + new TunicPortal("Fortress Interior Shop", "Shop_"), + new TunicPortal("Fortress Interior to East Fortress Upper", "Fortress East_upper"), + new TunicPortal("Fortress Interior to East Fortress Lower", "Fortress East_lower"), + } + }, + { + "Eastern Vault Fortress Gold Door", + new List { + new TunicPortal("Fortress Interior to Siege Engine Arena", "Fortress Arena_"), + } + }, + } + }, + { + "Fortress East", + new Dictionary> { + { + "Fortress East Shortcut Lower", + new List { + new TunicPortal("East Fortress to Interior Lower", "Fortress Main_lower"), + } + }, + { + "Fortress East Shortcut Upper", + new List { + new TunicPortal("East Fortress to Courtyard", "Fortress Courtyard_"), + new TunicPortal("East Fortress to Interior Upper", "Fortress Main_upper"), + } + }, + } + }, + { + "Fortress Reliquary", + new Dictionary> { + { + "Fortress Grave Path", + new List { + new TunicPortal("Fortress Grave Path Lower Exit", "Fortress Courtyard_Lower"), + new TunicPortal("Fortress Hero's Grave", "RelicVoid_teleporter_relic plinth"), + } + }, + { + "Fortress Grave Path Upper", + new List { + new TunicPortal("Fortress Grave Path Upper Exit", "Fortress Courtyard_Upper"), + } + }, + { + "Fortress Grave Path Dusty Entrance", + new List { + new TunicPortal("Fortress Grave Path Dusty Entrance", "Dusty_"), + } + }, + } + }, + { + "Dusty", + new Dictionary> { + { + "Fortress Leaf Piles", + new List { + new TunicPortal("Dusty Exit", "Fortress Reliquary_"), + } + }, + } + }, + { + "Fortress Arena", + new Dictionary> { + { + "Fortress Arena", + new List { + new TunicPortal("Siege Engine Arena to Fortress", "Fortress Main_"), + } + }, + { + "Fortress Arena Portal", + new List { + new TunicPortal("Fortress to Far Shore", "Transit_teleporter_spidertank"), + } + }, + } + }, + { + "Mountain", + new Dictionary> { + { + "Lower Mountain Stairs", + new List { + new TunicPortal("Stairs to Top of the Mountain", "Mountaintop_"), + } + }, + { + "Lower Mountain", + new List { + new TunicPortal("Mountain to Quarry", "Quarry Redux_"), + new TunicPortal("Mountain to Overworld", "Overworld Redux_"), + } + }, + } + }, + { + "Mountaintop", + new Dictionary> { + { + "Top of the Mountain", + new List { + new TunicPortal("Top of the Mountain Exit", "Mountain_"), + } + }, + } + }, + { + "Darkwoods Tunnel", + new Dictionary> { + { + "Quarry Connector", + new List { + new TunicPortal("Quarry Connector to Overworld", "Overworld Redux_"), + new TunicPortal("Quarry Connector to Quarry", "Quarry Redux_"), + } + }, + } + }, + { + "Quarry Redux", + new Dictionary> { + { + "Quarry Entry", + new List { + new TunicPortal("Quarry to Overworld Exit", "Darkwoods Tunnel_"), + new TunicPortal("Quarry Shop", "Shop_"), + } + }, + { + "Quarry Monastery Entry", + new List { + new TunicPortal("Quarry to Monastery Front", "Monastery_front"), + } + }, + { + "Monastery Rope", + new List { + new TunicPortal("Quarry to Monastery Back", "Monastery_back"), + } + }, + { + "Quarry Back", + new List { + new TunicPortal("Quarry to Mountain", "Mountain_"), + } + }, + { + "Lower Quarry Zig Door", + new List { + new TunicPortal("Quarry to Ziggurat", "ziggurat2020_0_"), + } + }, + { + "Quarry Portal", + new List { + new TunicPortal("Quarry to Far Shore", "Transit_teleporter_quarry teleporter"), + } + }, + } + }, + { + "Monastery", + new Dictionary> { + { + "Monastery Back", + new List { + new TunicPortal("Monastery Rear Exit", "Quarry Redux_back"), + } + }, + { + "Monastery Front", + new List { + new TunicPortal("Monastery Front Exit", "Quarry Redux_front"), + } + }, + { + "Monastery Hero's Grave", + new List { + new TunicPortal("Monastery Hero's Grave", "RelicVoid_teleporter_relic plinth"), + } + }, + } + }, + { + "ziggurat2020_0", + new Dictionary> { + { + "Rooted Ziggurat Entry", + new List { + new TunicPortal("Ziggurat Entry Hallway to Ziggurat Upper", "ziggurat2020_1_"), + new TunicPortal("Ziggurat Entry Hallway to Quarry", "Quarry Redux_"), + } + }, + } + }, + { + "ziggurat2020_1", + new Dictionary> { + { + "Rooted Ziggurat Upper Entry", + new List { + new TunicPortal("Ziggurat Upper to Ziggurat Entry Hallway", "ziggurat2020_0_"), + } + }, + { + "Rooted Ziggurat Upper Back", + new List { + new TunicPortal("Ziggurat Upper to Ziggurat Tower", "ziggurat2020_2_"), + } + }, + } + }, + { + "ziggurat2020_2", + new Dictionary> { + { + "Rooted Ziggurat Middle Top", + new List { + new TunicPortal("Ziggurat Tower to Ziggurat Upper", "ziggurat2020_1_"), + } + }, + { + "Rooted Ziggurat Middle Bottom", + new List { + new TunicPortal("Ziggurat Tower to Ziggurat Lower", "ziggurat2020_3_"), + } + }, + } + }, + { + "ziggurat2020_3", + new Dictionary> { + { + "Rooted Ziggurat Lower Front", + new List { + new TunicPortal("Ziggurat Lower to Ziggurat Tower", "ziggurat2020_2_"), + } + }, + { + "Rooted Ziggurat Portal Room Entrance", + new List { + new TunicPortal("Ziggurat Portal Room Entrance", "ziggurat2020_FTRoom_"), + } + }, + } + }, + { + "ziggurat2020_FTRoom", + new Dictionary> { + { + "Rooted Ziggurat Portal Room Exit", + new List { + new TunicPortal("Ziggurat Portal Room Exit", "ziggurat2020_3_"), + } + }, + { + "Rooted Ziggurat Portal", + new List { + new TunicPortal("Ziggurat to Far Shore", "Transit_teleporter_ziggurat teleporter"), + } + }, + } + }, + { + "Swamp Redux 2", + new Dictionary> { + { + "Swamp", + new List { + new TunicPortal("Swamp Lower Exit", "Overworld Redux_conduit"), + new TunicPortal("Swamp Shop", "Shop_"), + } + }, + { + "Swamp to Cathedral Main Entrance", + new List { + new TunicPortal("Swamp to Cathedral Main Entrance", "Cathedral Redux_main"), + } + }, + { + "Swamp to Cathedral Treasure Room", + new List { + new TunicPortal("Swamp to Cathedral Secret Legend Room Entrance", "Cathedral Redux_secret"), + } + }, + { + "Back of Swamp", + new List { + new TunicPortal("Swamp to Gauntlet", "Cathedral Arena_"), + } + }, + { + "Back of Swamp Laurels Area", + new List { + new TunicPortal("Swamp Upper Exit", "Overworld Redux_wall"), + } + }, + { + "Swamp Hero's Grave", + new List { + new TunicPortal("Swamp Hero's Grave", "RelicVoid_teleporter_relic plinth"), + } + }, + } + }, + { + "Cathedral Redux", + new Dictionary> { + { + "Cathedral", + new List { + new TunicPortal("Cathedral Main Exit", "Swamp Redux 2_main"), + new TunicPortal("Cathedral Elevator", "Cathedral Arena_"), + } + }, + { + "Cathedral Secret Legend Room", + new List { + new TunicPortal("Cathedral Secret Legend Room Exit", "Swamp Redux 2_secret"), + } + }, + } + }, + { + "Cathedral Arena", + new Dictionary> { + { + "Cathedral Gauntlet Exit", + new List { + new TunicPortal("Gauntlet to Swamp", "Swamp Redux 2_"), + } + }, + { + "Cathedral Gauntlet Checkpoint", + new List { + new TunicPortal("Gauntlet Elevator", "Cathedral Redux_"), + new TunicPortal("Gauntlet Shop", "Shop_"), + } + }, + } + }, + { + "RelicVoid", + new Dictionary> { + { + "Hero Relic - Fortress", + new List { + new TunicPortal("Hero's Grave to Fortress", "Fortress Reliquary_teleporter_relic plinth"), + } + }, + { + "Hero Relic - Quarry", + new List { + new TunicPortal("Hero's Grave to Monastery", "Monastery_teleporter_relic plinth"), + } + }, + { + "Hero Relic - West Garden", + new List { + new TunicPortal("Hero's Grave to West Garden", "Archipelagos Redux_teleporter_relic plinth"), + } + }, + { + "Hero Relic - East Forest", + new List { + new TunicPortal("Hero's Grave to East Forest", "Sword Access_teleporter_relic plinth"), + } + }, + { + "Hero Relic - Library", + new List { + new TunicPortal("Hero's Grave to Library", "Library Hall_teleporter_relic plinth"), + } + }, + { + "Hero Relic - Swamp", + new List { + new TunicPortal("Hero's Grave to Swamp", "Swamp Redux 2_teleporter_relic plinth"), + } + }, + } + }, + { + "Transit", + new Dictionary> { + { + "Far Shore to West Garden", + new List { + new TunicPortal("Far Shore to West Garden", "Archipelagos Redux_teleporter_archipelagos_teleporter"), + } + }, + { + "Far Shore to Library", + new List { + new TunicPortal("Far Shore to Library", "Library Lab_teleporter_library teleporter"), + } + }, + { + "Far Shore to Quarry", + new List { + new TunicPortal("Far Shore to Quarry", "Quarry Redux_teleporter_quarry teleporter"), + } + }, + { + "Far Shore to East Forest", + new List { + new TunicPortal("Far Shore to East Forest", "East Forest Redux_teleporter_forest teleporter"), + } + }, + { + "Far Shore to Fortress", + new List { + new TunicPortal("Far Shore to Fortress", "Fortress Arena_teleporter_spidertank"), + } + }, + { + "Far Shore", + new List { + new TunicPortal("Far Shore to Atoll", "Atoll Redux_teleporter_atoll"), + new TunicPortal("Far Shore to Ziggurat", "ziggurat2020_FTRoom_teleporter_ziggurat teleporter"), + new TunicPortal("Far Shore to Heir", "Spirit Arena_teleporter_spirit arena"), + new TunicPortal("Far Shore to Town", "Overworld Redux_teleporter_town"), + } + }, + { + "Far Shore to Spawn", + new List { + new TunicPortal("Far Shore to Spawn", "Overworld Redux_teleporter_starting island"), + } + }, + } + }, + { + "Spirit Arena", + new Dictionary> { + { + "Spirit Arena", + new List { + new TunicPortal("Heir Arena Exit", "Transit_teleporter_spirit arena"), + } + }, + } + }, + { + "Purgatory", + new Dictionary> { + { + "Purgatory", + new List { + new TunicPortal("Purgatory Bottom Exit", "Purgatory_bottom"), + new TunicPortal("Purgatory Top Exit", "Purgatory_top"), + } + }, + } + }, + { + "Shop", + new Dictionary> { + { + "Shop", + new List { + new TunicPortal("Shop", "Previous Region_"), + } + }, + } + }, + }; + + public static Dictionary RegionDict = new Dictionary { + { + "Overworld", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Belltower", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Swamp Upper Entry", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Special Shop Entry", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld West Garden Laurels Entry", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld to West Garden from Furnace", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Well to Furnace Rail", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Ruined Passage Door", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Old House Door", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Southeast Cross Door", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Fountain Cross Door", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Temple Door", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Town Portal", + new RegionInfo("Overworld Redux", false) + }, + { + "Overworld Spawn Portal", + new RegionInfo("Overworld Redux", false) + }, + { + "Stick House", + new RegionInfo("Sword Cave", true) + }, + { + "Windmill", + new RegionInfo("Windmill", false) + }, + { + "Old House Back", + new RegionInfo("Overworld Interiors", false) + }, + { + "Old House Front", + new RegionInfo("Overworld Interiors", false) + }, + { + "Relic Tower", + new RegionInfo("g_elements", true) + }, + { + "Furnace Fuse", + new RegionInfo("Furnace", false) + }, + { + "Furnace Ladder Area", + new RegionInfo("Furnace", false) + }, + { + "Furnace Walking Path", + new RegionInfo("Furnace", false) + }, + { + "Secret Gathering Place", + new RegionInfo("Waterfall", true) + }, + { + "Changing Room", + new RegionInfo("Changing Room", true) + }, + { + "Patrol Cave", + new RegionInfo("PatrolCave", true) + }, + { + "Ruined Shop", + new RegionInfo("Ruined Shop", true) + }, + { + "Ruined Passage", + new RegionInfo("Ruins Passage", false) + }, + { + "Special Shop", + new RegionInfo("ShopSpecial", true) + }, + { + "Caustic Light Cave", + new RegionInfo("Overworld Cave", true) + }, + { + "Maze Cave", + new RegionInfo("Maze Room", true) + }, + { + "Cube Cave", + new RegionInfo("CubeRoom", true) + }, + { + "Southeast Cross Room", + new RegionInfo("EastFiligreeCache", true) + }, + { + "Fountain Cross Room", + new RegionInfo("Town_FiligreeRoom", true) + }, + { + "Hourglass Cave", + new RegionInfo("Town Basement", true) + }, + { + "Sealed Temple", + new RegionInfo("Temple", false) + }, + { + "Sealed Temple Rafters", + new RegionInfo("Temple", false) + }, + { + "Forest Belltower Upper", + new RegionInfo("Forest Belltower", false) + }, + { + "Forest Belltower Main", + new RegionInfo("Forest Belltower", false) + }, + { + "Forest Belltower Lower", + new RegionInfo("Forest Belltower", false) + }, + { + "East Forest", + new RegionInfo("East Forest Redux", false) + }, + { + "East Forest Dance Fox Spot", + new RegionInfo("East Forest Redux", false) + }, + { + "East Forest Portal", + new RegionInfo("East Forest Redux", false) + }, + { + "Guard House 1 East", + new RegionInfo("East Forest Redux Laddercave", false) + }, + { + "Guard House 1 West", + new RegionInfo("East Forest Redux Laddercave", false) + }, + { + "Guard House 2", + new RegionInfo("East Forest Redux Interior", false) + }, + { + "Forest Boss Room", + new RegionInfo("Forest Boss Room", false) + }, + { + "Forest Grave Path Main", + new RegionInfo("Sword Access", false) + }, + { + "Forest Grave Path Upper", + new RegionInfo("Sword Access", false) + }, + { + "Forest Grave Path by Grave", + new RegionInfo("Sword Access", false) + }, + { + "Forest Hero's Grave", + new RegionInfo("Sword Access", false) + }, + { + "Dark Tomb Entry Point", + new RegionInfo("Crypt Redux", false) + }, + { + "Dark Tomb Main", + new RegionInfo("Crypt Redux", false) + }, + { + "Dark Tomb Dark Exit", + new RegionInfo("Crypt Redux", false) + }, + { + "Dark Tomb Checkpoint", + new RegionInfo("Sewer_Boss", false) + }, + { + "Well Boss", + new RegionInfo("Sewer_Boss", false) + }, + { + "Beneath the Well Front", + new RegionInfo("Sewer", false) + }, + { + "Beneath the Well Main", + new RegionInfo("Sewer", false) + }, + { + "Beneath the Well Back", + new RegionInfo("Sewer", false) + }, + { + "West Garden", + new RegionInfo("Archipelagos Redux", false) + }, + { + "West Garden Portal", + new RegionInfo("Archipelagos Redux", true) + }, + { + "West Garden Portal Item", + new RegionInfo("Archipelagos Redux", true) + }, + { + "West Garden Laurels Exit", + new RegionInfo("Archipelagos Redux", false) + }, + { + "West Garden after Boss", + new RegionInfo("Archipelagos Redux", false) + }, + { + "West Garden Hero's Grave", + new RegionInfo("Archipelagos Redux", false) + }, + { + "Magic Dagger House", + new RegionInfo("archipelagos_house", true) + }, + { + "Ruined Atoll", + new RegionInfo("Atoll Redux", false) + }, + { + "Ruined Atoll Lower Entry Area", + new RegionInfo("Atoll Redux", false) + }, + { + "Ruined Atoll Frog Mouth", + new RegionInfo("Atoll Redux", false) + }, + { + "Ruined Atoll Portal", + new RegionInfo("Atoll Redux", false) + }, + { + "Frog's Domain Entry", + new RegionInfo("Frog Stairs", false) + }, + { + "Frog's Domain", + new RegionInfo("frog cave main", false) + }, + { + "Frog's Domain Back", + new RegionInfo("frog cave main", false) + }, + { + "Library Exterior Tree", + new RegionInfo("Library Exterior", false) + }, + { + "Library Exterior Ladder", + new RegionInfo("Library Exterior", false) + }, + { + "Library Hall", + new RegionInfo("Library Hall", false) + }, + { + "Library Hero's Grave", + new RegionInfo("Library Hall", false) + }, + { + "Library Rotunda", + new RegionInfo("Library Rotunda", false) + }, + { + "Library Lab", + new RegionInfo("Library Lab", false) + }, + { + "Library Lab Lower", + new RegionInfo("Library Lab", false) + }, + { + "Library Portal", + new RegionInfo("Library Lab", false) + }, + { + "Library Arena", + new RegionInfo("Library Arena", true) + }, + { + "Fortress Exterior from East Forest", + new RegionInfo("Fortress Courtyard", false) + }, + { + "Fortress Exterior from Overworld", + new RegionInfo("Fortress Courtyard", false) + }, + { + "Fortress Exterior near cave", + new RegionInfo("Fortress Courtyard", false) + }, + { + "Fortress Courtyard", + new RegionInfo("Fortress Courtyard", false) + }, + { + "Fortress Courtyard Upper", + new RegionInfo("Fortress Courtyard", false) + }, + { + "Beneath the Vault Front", + new RegionInfo("Fortress Basement", false) + }, + { + "Beneath the Vault Back", + new RegionInfo("Fortress Basement", false) + }, + { + "Eastern Vault Fortress", + new RegionInfo("Fortress Main", false) + }, + { + "Eastern Vault Fortress Gold Door", + new RegionInfo("Fortress Main", false) + }, + { + "Fortress East Shortcut Upper", + new RegionInfo("Fortress East", false) + }, + { + "Fortress East Shortcut Lower", + new RegionInfo("Fortress East", false) + }, + { + "Fortress Grave Path", + new RegionInfo("Fortress Reliquary", false) + }, + { + "Fortress Grave Path Upper", + new RegionInfo("Fortress Reliquary", true) + }, + { + "Fortress Grave Path Dusty Entrance", + new RegionInfo("Fortress Reliquary", false) + }, + { + "Fortress Hero's Grave", + new RegionInfo("Fortress Reliquary", false) + }, + { + "Fortress Leaf Piles", + new RegionInfo("Dusty", true) + }, + { + "Fortress Arena", + new RegionInfo("Fortress Arena", false) + }, + { + "Fortress Arena Portal", + new RegionInfo("Fortress Arena", false) + }, + { + "Lower Mountain", + new RegionInfo("Mountain", false) + }, + { + "Lower Mountain Stairs", + new RegionInfo("Mountain", false) + }, + { + "Top of the Mountain", + new RegionInfo("Mountaintop", true) + }, + { + "Quarry Connector", + new RegionInfo("Darkwoods Tunnel", false) + }, + { + "Quarry Entry", + new RegionInfo("Quarry Redux", false) + }, + { + "Quarry", + new RegionInfo("Quarry Redux", false) + }, + { + "Quarry Portal", + new RegionInfo("Quarry Redux", false) + }, + { + "Quarry Back", + new RegionInfo("Quarry Redux", false) + }, + { + "Quarry Monastery Entry", + new RegionInfo("Quarry Redux", false) + }, + { + "Monastery Front", + new RegionInfo("Monastery", false) + }, + { + "Monastery Back", + new RegionInfo("Monastery", false) + }, + { + "Monastery Hero's Grave", + new RegionInfo("Monastery", false) + }, + { + "Monastery Rope", + new RegionInfo("Quarry Redux", false) + }, + { + "Lower Quarry", + new RegionInfo("Quarry Redux", false) + }, + { + "Lower Quarry Zig Door", + new RegionInfo("Quarry Redux", false) + }, + { + "Rooted Ziggurat Entry", + new RegionInfo("ziggurat2020_0", false) + }, + { + "Rooted Ziggurat Upper Entry", + new RegionInfo("ziggurat2020_1", false) + }, + { + "Rooted Ziggurat Upper Front", + new RegionInfo("ziggurat2020_1", false) + }, + { + "Rooted Ziggurat Upper Back", + new RegionInfo("ziggurat2020_1", false) + }, + { + "Rooted Ziggurat Middle Top", + new RegionInfo("ziggurat2020_2", false) + }, + { + "Rooted Ziggurat Middle Bottom", + new RegionInfo("ziggurat2020_2", false) + }, + { + "Rooted Ziggurat Lower Front", + new RegionInfo("ziggurat2020_3", false) + }, + { + "Rooted Ziggurat Lower Back", + new RegionInfo("ziggurat2020_3", false) + }, + { + "Rooted Ziggurat Portal Room Entrance", + new RegionInfo("ziggurat2020_3", false) + }, + { + "Rooted Ziggurat Portal", + new RegionInfo("ziggurat2020_FTRoom", false) + }, + { + "Rooted Ziggurat Portal Room Exit", + new RegionInfo("ziggurat2020_FTRoom", false) + }, + { + "Swamp", + new RegionInfo("Swamp Redux 2", false) + }, + { + "Swamp to Cathedral Treasure Room", + new RegionInfo("Swamp Redux 2", false) + }, + { + "Swamp to Cathedral Main Entrance", + new RegionInfo("Swamp Redux 2", false) + }, + { + "Back of Swamp", + new RegionInfo("Swamp Redux 2", false) + }, + { + "Swamp Hero's Grave", + new RegionInfo("Swamp Redux 2", false) + }, + { + "Back of Swamp Laurels Area", + new RegionInfo("Swamp Redux 2", false) + }, + { + "Cathedral", + new RegionInfo("Cathedral Redux", false) + }, + { + "Cathedral Secret Legend Room", + new RegionInfo("Cathedral Redux", true) + }, + { + "Cathedral Gauntlet Checkpoint", + new RegionInfo("Cathedral Arena", false) + }, + { + "Cathedral Gauntlet", + new RegionInfo("Cathedral Arena", false) + }, + { + "Cathedral Gauntlet Exit", + new RegionInfo("Cathedral Arena", false) + }, + { + "Far Shore", + new RegionInfo("Transit", false) + }, + { + "Far Shore to Spawn", + new RegionInfo("Transit", false) + }, + { + "Far Shore to East Forest", + new RegionInfo("Transit", false) + }, + { + "Far Shore to Quarry", + new RegionInfo("Transit", false) + }, + { + "Far Shore to Fortress", + new RegionInfo("Transit", false) + }, + { + "Far Shore to Library", + new RegionInfo("Transit", false) + }, + { + "Far Shore to West Garden", + new RegionInfo("Transit", false) + }, + { + "Hero Relic - Fortress", + new RegionInfo("RelicVoid", true) + }, + { + "Hero Relic - Quarry", + new RegionInfo("RelicVoid", true) + }, + { + "Hero Relic - West Garden", + new RegionInfo("RelicVoid", true) + }, + { + "Hero Relic - East Forest", + new RegionInfo("RelicVoid", true) + }, + { + "Hero Relic - Library", + new RegionInfo("RelicVoid", true) + }, + { + "Hero Relic - Swamp", + new RegionInfo("RelicVoid", true) + }, + { + "Purgatory", + new RegionInfo("Purgatory", false) + }, + { + "Shop Entrance 1", + new RegionInfo("Shop", true) + }, + { + "Shop Entrance 2", + new RegionInfo("Shop", true) + }, + { + "Shop Entrance 3", + new RegionInfo("Shop", true) + }, + { + "Shop Entrance 4", + new RegionInfo("Shop", true) + }, + { + "Shop Entrance 5", + new RegionInfo("Shop", true) + }, + { + "Shop Entrance 6", + new RegionInfo("Shop", true) + }, + { + "Shop", + new RegionInfo("Shop", true) + }, + { + "Spirit Arena", + new RegionInfo("Spirit Arena", true) + }, + }; + + public static Dictionary>>> TraversalReqs = new Dictionary>>> { + { + "Overworld", + new Dictionary>> { + { + "Overworld Belltower", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Overworld Swamp Upper Entry", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Overworld Special Shop Entry", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Overworld West Garden Laurels Entry", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Overworld Southeast Cross Door", + new List> { + new List { + "21", + }, + } + }, + { + "Overworld Ruined Passage Door", + new List> { + new List { + "Key", + }, + new List { + "Hyperdash", "nmg", + }, + } + }, + { + "Overworld Temple Door", + new List> { + new List { + "26", "Techbow", "Wand", "Stundagger", "nmg", + }, + new List { + "Techbow", "Forest Belltower Upper", "nmg", + }, + new List { + "Stick", "Forest Belltower Upper", "Overworld Belltower", + }, + new List { + "Techbow", "Forest Belltower Upper", "Overworld Belltower", + }, + } + }, + { + "Overworld Fountain Cross Door", + new List> { + new List { + "21", + }, + new List { + "26", "Techbow", "Wand", "Stundagger", "nmg", + }, + } + }, + { + "Overworld Town Portal", + new List> { + new List { + "12", + }, + } + }, + { + "Overworld Spawn Portal", + new List> { + new List { + "12", + }, + } + }, + { + "Overworld Well to Furnace Rail", + new List> { + new List { + "Ladder Storage", + }, + } + }, + { + "Overworld Old House Door", + new List> { + new List { + "Key (House)", + }, + new List { + "Stundagger", "Wand", "nmg", + }, + } + }, + } + }, + { + "Old House Front", + new Dictionary>> { + { + "Old House Back", + new List> { + } + }, + } + }, + { + "Old House Back", + new Dictionary>> { + { + "Old House Front", + new List> { + new List { + "Hyperdash", "nmg", + }, + } + }, + } + }, + { + "Furnace Fuse", + new Dictionary>> { + { + "Furnace Ladder Area", + new List> { + new List { + "Hyperdash", + }, + } + }, + } + }, + { + "Furnace Ladder Area", + new Dictionary>> { + { + "Furnace Fuse", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Furnace Walking Path", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + } + }, + { + "Furnace Walking Path", + new Dictionary>> { + { + "Furnace Ladder Area", + new List> { + new List { + "Hyperdash", + }, + } + }, + } + }, + { + "Sealed Temple", + new Dictionary>> { + { + "Sealed Temple Rafters", + new List> { + } + }, + } + }, + { + "Sealed Temple Rafters", + new Dictionary>> { + { + "Sealed Temple", + new List> { + new List { + "Hyperdash", + }, + } + }, + } + }, + { + "East Forest", + new Dictionary>> { + { + "East Forest Dance Fox Spot", + new List> { + new List { + "Hyperdash", + }, + new List { + "26", "nmg", + }, + } + }, + { + "East Forest Portal", + new List> { + new List { + "12", + }, + } + }, + } + }, + { + "Forest Belltower Upper", + new Dictionary>> { + { + "Forest Belltower Main", + new List> { + } + }, + } + }, + { + "Forest Belltower Main", + new Dictionary>> { + { + "Forest Belltower Lower", + new List> { + } + }, + } + }, + { + "East Forest Dance Fox Spot", + new Dictionary>> { + { + "East Forest", + new List> { + new List { + "Hyperdash", + }, + new List { + "26", "nmg", + }, + } + }, + } + }, + { + "East Forest Portal", + new Dictionary>> { + { + "East Forest", + new List> { + } + }, + } + }, + { + "Guard House 1 East", + new Dictionary>> { + { + "Guard House 1 West", + new List> { + } + }, + } + }, + { + "Forest Grave Path Main", + new Dictionary>> { + { + "Forest Grave Path Upper", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Forest Grave Path by Grave", + new List> { + } + }, + } + }, + { + "Forest Grave Path Upper", + new Dictionary>> { + { + "Forest Grave Path Main", + new List> { + new List { + "Hyperdash", + }, + new List { + "26", "nmg", + }, + } + }, + } + }, + { + "Forest Grave Path by Grave", + new Dictionary>> { + { + "Forest Hero's Grave", + new List> { + new List { + "12", + }, + } + }, + { + "Forest Grave Path Main", + new List> { + new List { + "Hyperdash", "nmg", + }, + } + }, + } + }, + { + "Forest Hero's Grave", + new Dictionary>> { + { + "Forest Grave Path by Grave", + new List> { + } + }, + } + }, + { + "Beneath the Well Front", + new Dictionary>> { + { + "Beneath the Well Main", + new List> { + new List { + "Stick", "Lantern" + }, + new List { + "Techbow", "Lantern" + }, + } + }, + } + }, + { + "Beneath the Well Back", + new Dictionary>> { + { + "Beneath the Well Main", + new List> { + new List { + "Stick" + }, + new List { + "Techbow" + }, + } + }, + } + }, + { + "Beneath the Well Main", + new Dictionary>> { + { + "Beneath the Well Front", + new List> { + } + }, + { + "Beneath the Well Back", + new List> { + } + }, + } + }, + { + "Well Boss", + new Dictionary>> { + { + "Dark Tomb Checkpoint", + new List> { + } + }, + } + }, + { + "Dark Tomb Checkpoint", + new Dictionary>> { + { + "Well Boss", + new List> { + new List { + "Hyperdash", "nmg", + }, + } + }, + } + }, + { + "Dark Tomb Entry Point", + new Dictionary>> { + { + "Dark Tomb Main", + new List> { + new List { + "Lantern" + }, + } + }, + } + }, + { + "Dark Tomb Main", + new Dictionary>> { + { + "Dark Tomb Dark Exit", + new List> { + } + }, + { + "Dark Tomb Entry Point", + new List> { + } + }, + } + }, + { + "Dark Tomb Dark Exit", + new Dictionary>> { + { + "Dark Tomb Main", + new List> { + new List { + "Lantern" + }, + } + }, + } + }, + { + "West Garden", + new Dictionary>> { + { + "West Garden Laurels Exit", + new List> { + new List { + "Hyperdash" + }, + new List { + "Ladder Storage" + }, + } + }, + { + "West Garden after Boss", + new List> { + new List { + "Sword" + }, + new List { + "Ladder Storage" + }, + } + }, + { + "West Garden Hero's Grave", + new List> { + new List { + "12" + }, + } + }, + { + "West Garden Portal Item", + new List> { + new List { + "26", "Wand", "Stundagger", "Techbow", "nmg" + }, + } + }, + } + }, + { + "West Garden Laurels Exit", + new Dictionary>> { + { + "West Garden", + new List> { + new List { + "Hyperdash" + }, + } + }, + } + }, + { + "West Garden after Boss", + new Dictionary>> { + { + "West Garden", + new List> { + new List { + "Hyperdash" + }, + } + }, + } + }, + { + "West Garden Portal Item", + new Dictionary>> { + { + "West Garden", + new List> { + new List { + "26", "Wand", "Stundagger", "Techbow", "nmg" + }, + } + }, + { + "West Garden Portal", + new List> { + new List { + "Hyperdash", "12", "West Garden" + }, + } + }, + } + }, { - "Overworld Redux", - new List { - new TunicPortal("Sword Cave", "", "Stick House Entrance", granularRegion: "Overworld"), - new TunicPortal("Windmill", "", "Windmill Entrance", granularRegion: "Overworld"), - new TunicPortal("Sewer", "entrance", "Well Ladder Entrance", granularRegion: "Overworld"), - new TunicPortal("Sewer", "west_aqueduct", "Entrance to Well from Well Rail", granularRegion: "Overworld Well to Furnace Rail", ignoreScene: true, givesAccess: new List { "Overworld Redux, Furnace_gyro_upper_north" }, requiredItems: new Dictionary { { "Overworld Redux, Furnace_gyro_upper_north", 1 } }), - new TunicPortal("Overworld Interiors", "house", "Old House Entry Door", granularRegion: "Overworld Not First Steps", requiredItems: new Dictionary { {"Key (House)", 1} }), // make this match actual item name - new TunicPortal("Overworld Interiors", "under_checkpoint", "Old House Waterfall Entrance", granularRegion: "Overworld"), - new TunicPortal("Furnace", "gyro_upper_north", "Entrance to Furnace from Well Rail", granularRegion: "Overworld Well to Furnace Rail", ignoreScene: true, givesAccess: new List { "Overworld Redux, Sewer_west_aqueduct" }, requiredItems: new Dictionary { { "Overworld Redux, Sewer_west_aqueduct", 1 } }), - new TunicPortal("Furnace", "gyro_upper_east", "Entrance to Furnace under Windmill", granularRegion: "Overworld"), - new TunicPortal("Furnace", "gyro_west", "Entrance to Furnace near West Garden", granularRegion: "Overworld Not First Steps", ignoreScene: true, givesAccess: new List {"Overworld Redux, Archipelagos Redux_lower"}, requiredItems: new Dictionary { { "Overworld Redux, Archipelagos Redux_lower", 1 } }), - new TunicPortal("Furnace", "gyro_lower", "Entrance to Furnace from Beach", granularRegion: "Overworld"), - new TunicPortal("Overworld Cave", "", "Caustic Light Cave Entrance", granularRegion: "Overworld"), - new TunicPortal("Swamp Redux 2", "wall", "Swamp Upper Entrance", granularRegion: "Overworld Not First Steps", requiredItems: new Dictionary { { "Hyperdash", 1} }), - new TunicPortal("Swamp Redux 2", "conduit", "Swamp Lower Entrance", granularRegion: "Overworld"), - new TunicPortal("Ruins Passage", "east", "Ruined Passage Not-Door Entrance", granularRegion: "Overworld"), - new TunicPortal("Ruins Passage", "west", "Ruined Passage Door Entrance", granularRegion: "Overworld Not First Steps", requiredItems: new Dictionary { { "Key", 2 } }), // and access to any overworld portal, but we start in overworld so no need to put it here - new TunicPortal("Atoll Redux", "upper", "Atoll Upper Entrance", granularRegion: "Overworld"), - new TunicPortal("Atoll Redux", "lower", "Atoll Lower Entrance", granularRegion: "Overworld"), - new TunicPortal("ShopSpecial", "", "Special Shop Entrance", granularRegion: "Overworld Not First Steps", requiredItems: new Dictionary { { "Hyperdash", 1} }), - new TunicPortal("Maze Room", "", "Maze Cave Entrance", granularRegion: "Overworld"), - new TunicPortal("Archipelagos Redux", "upper", "West Garden Entrance near Belltower", granularRegion: "Overworld Not First Steps", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - new TunicPortal("Archipelagos Redux", "lower", "West Garden Entrance from Furnace", granularRegion: "Overworld Not First Steps", ignoreScene: true, givesAccess: new List {"Overworld Redux, Furnace_gyro_west"}, requiredItems: new Dictionary {{"Overworld Redux, Furnace_gyro_west", 1}}), - new TunicPortal("Archipelagos Redux", "lowest", "West Garden Laurels Entrance", granularRegion: "Overworld Not First Steps", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - new TunicPortal("Temple", "main", "Temple Door Entrance", granularRegion: "Overworld Not First Steps", requiredItemsOr: new List> { new Dictionary { { "Forest Belltower, Forest Boss Room_", 1 }, { "Overworld Redux, Archipelagos Redux_upper", 1 }, { "Stick", 1 } }, new Dictionary { { "Forest Belltower, Forest Boss Room_", 1 }, { "Overworld Redux, Archipelagos Redux_upper", 1 }, { "Techbow", 1 } }, new Dictionary { { "Forest Belltower, Forest Boss Room_", 1 }, { "Hyperdash", 1 }, { "Stick", 1 } }, new Dictionary { { "Forest Belltower, Forest Boss Room_", 1 }, { "Hyperdash", 1 }, { "Techbow", 1 } } }), - new TunicPortal("Temple", "rafters", "Temple Rafters Entrance", granularRegion: "Overworld"), - new TunicPortal("Ruined Shop", "", "Ruined Shop Entrance", granularRegion: "Overworld"), - new TunicPortal("PatrolCave", "", "Patrol Cave Entrance", granularRegion: "Overworld"), - new TunicPortal("Town Basement", "beach", "Hourglass Cave Entrance", granularRegion: "Overworld"), - new TunicPortal("Changing Room", "", "Changing Room Entrance", granularRegion: "Overworld"), - new TunicPortal("CubeRoom", "", "Cube Cave Entrance", granularRegion: "Overworld"), - new TunicPortal("Mountain", "", "Stairs from Overworld to Mountain", granularRegion: "Overworld"), - new TunicPortal("Fortress Courtyard", "", "Overworld to Fortress", granularRegion: "Overworld"), - new TunicPortal("Town_FiligreeRoom", "", "Fountain HC Door Entrance", granularRegion: "Overworld Ability", entryItems: new Dictionary { { "21", 1 } }), // this is entry items because when you exit from this portal, you end up in front of the door - new TunicPortal("EastFiligreeCache", "", "Southeast HC Door Entrance", granularRegion: "Overworld Ability", requiredItems: new Dictionary { { "21", 1 } }), // this is required items because when you exit from this portal, you end up behind the door - new TunicPortal("Darkwoods Tunnel", "", "Overworld to Quarry Connector", granularRegion: "Overworld"), - new TunicPortal("Crypt Redux", "", "Dark Tomb Main Entrance", granularRegion: "Overworld"), - new TunicPortal("Forest Belltower", "", "Overworld to Forest Belltower", granularRegion: "Overworld"), - new TunicPortal("Transit", "teleporter_town", "Town to Far Shore", granularRegion: "Overworld Ability", prayerPortal: true), - new TunicPortal("Transit", "teleporter_starting island", "Spawn to Far Shore", granularRegion: "Overworld Ability", prayerPortal: true), - new TunicPortal("Waterfall", "", "Secret Gathering Place Entrance", granularRegion: "Overworld"), - - // new TunicPortal("_", "", "Portal"), // ? - // new TunicPortal("Forest Belltower_", "showfloordemo2022", "Portal (12)"), // ? - // new TunicPortal("DEMO_altEnd_", "", "_Portal (Secret Demo End)"), // ? - } - }, - { - "Waterfall", // fairy cave - new List { - new TunicPortal("Overworld Redux", "", "Secret Gathering Place Exit", granularRegion: "Waterfall", deadEnd: true), + "West Garden Portal", + new Dictionary>> { + { + "West Garden Portal Item", + new List> { + new List { + "Hyperdash" + }, + } + }, } }, { - "Windmill", - new List { - new TunicPortal("Overworld Redux", "", "Windmill Exit", granularRegion: "Windmill"), - new TunicPortal("Shop", "", "Windmill Shop", granularRegion: "Windmill"), + "West Garden Hero's Grave", + new Dictionary>> { + { + "West Garden", + new List> { + } + }, } }, { - "Overworld Interiors", // House in town - new List { - new TunicPortal("Overworld Redux", "house", "Old House Door Exit", granularRegion: "Old House Front"), - new TunicPortal("g_elements", "", "Old House to Glyph Tower", granularRegion: "Old House Front"), - new TunicPortal("Overworld Redux", "under_checkpoint", "Old House Waterfall Exit", granularRegion: "Old House Back", ignoreScene: true, requiredItems: new Dictionary { { "Overworld Interiors, Overworld Redux_house", 1 } }), // since you get access to the center of a region from either portal, only one of these two is actually needed - - // new TunicPortal("Archipelagos Redux_", "", "_ShowfloorDemo2022 Portal"), // unused and disabled + "Ruined Atoll", + new Dictionary>> { + { + "Ruined Atoll Lower Entry Area", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Ruined Atoll Frog Mouth", + new List> { + new List { + "Hyperdash", + }, + new List { + "Wand", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Ruined Atoll Portal", + new List> { + new List { + "12", + }, + } + }, } }, { - "g_elements", // Relic tower - new List { - new TunicPortal("Overworld Interiors", "", "Glyph Tower Exit", granularRegion: "g_elements", deadEnd: true), + "Ruined Atoll Lower Entry Area", + new Dictionary>> { + { + "Ruined Atoll", + new List> { + new List { + "Hyperdash", + }, + new List { + "Wand", + }, + } + }, } }, { - "Changing Room", - new List { - new TunicPortal("Overworld Redux", "", "Changing Room Exit", granularRegion: "Changing Room", deadEnd: true), + "Ruined Atoll Frog Mouth", + new Dictionary>> { + { + "Ruined Atoll", + new List> { + new List { + "Hyperdash", + }, + new List { + "Wand", + }, + } + }, } }, { - "Town_FiligreeRoom", // the one next to the fountain - new List { - new TunicPortal("Overworld Redux", "", "Fountain HC Room Exit", granularRegion: "Town_FiligreeRoom", deadEnd: true), + "Ruined Atoll Portal", + new Dictionary>> { + { + "Ruined Atoll", + new List> { + } + }, } }, { - "CubeRoom", - new List { - new TunicPortal("Overworld Redux", "", "Cube Cave Exit", granularRegion: "CubeRoom", deadEnd: true), + "Frog's Domain", + new Dictionary>> { + { + "Frog's Domain Back", + new List> { + new List { + "Wand", + }, + } + }, } }, { - "PatrolCave", - new List { - new TunicPortal("Overworld Redux", "", "Guard Patrol Cave Exit", granularRegion: "PatrolCave", deadEnd: true), + "Library Exterior Ladder", + new Dictionary>> { + { + "Library Exterior Tree", + new List> { + new List { + "Hyperdash", "12", + }, + new List { + "Wand", "12", + }, + } + }, } }, { - "Ruined Shop", - new List { - new TunicPortal("Overworld Redux", "", "Ruined Shop Exit", granularRegion: "Ruined Shop", deadEnd: true), + "Library Exterior Tree", + new Dictionary>> { + { + "Library Exterior Ladder", + new List> { + new List { + "Hyperdash", + }, + new List { + "Wand", + }, + } + }, } }, { - "Furnace", // Under the west belltower - // I'm calling the "center" of this region the space accessible by the windmill and beach - new List { - new TunicPortal("Overworld Redux", "gyro_upper_north", "Furnace Exit towards Well", granularRegion: "Furnace", requiredItems: new Dictionary { {"Hyperdash", 1} }), - new TunicPortal("Crypt Redux", "", "Furnace Exit to Dark Tomb", granularRegion: "Furnace", requiredItems: new Dictionary { {"Hyperdash", 1} }, givesAccess: new List {"Furnace, Overworld Redux_gyro_west"}), - new TunicPortal("Overworld Redux", "gyro_west", "Furnace Exit towards West Garden", granularRegion: "Furnace", requiredItems : new Dictionary { {"Hyperdash", 1} }, givesAccess : new List {"Furnace, Crypt Redux_"}), - new TunicPortal("Overworld Redux", "gyro_lower", "Furnace Exit to Beach", granularRegion: "Furnace"), - new TunicPortal("Overworld Redux", "gyro_upper_east", "Furnace Exit under Windmill", granularRegion: "Furnace"), + "Library Hall", + new Dictionary>> { + { + "Library Hero's Grave", + new List> { + new List { + "12", + }, + } + }, } }, { - "Sword Cave", // Stick house - new List { - new TunicPortal("Overworld Redux", "", "Stick House Exit", granularRegion: "Sword Cave", deadEnd: true), + "Library Hero's Grave", + new Dictionary>> { + { + "Library Hall", + new List> { + } + }, } }, { - "Ruins Passage", // That little hallway with the key door near the start in Overworld - new List { - new TunicPortal("Overworld Redux", "east", "Ruined Passage Not-Door Exit", granularRegion: "Ruined Passage"), - new TunicPortal("Overworld Redux", "west", "Ruined Passage Door Exit", granularRegion: "Ruined Passage"), + "Library Lab Lower", + new Dictionary>> { + { + "Library Lab", + new List> { + new List { + "Hyperdash", + }, + new List { + "Wand", + }, + } + }, } }, { - "EastFiligreeCache", // The holy cross room with the 3 chests near swamp entrance - new List { - new TunicPortal("Overworld Redux", "", "Southeast HC Room Exit", granularRegion: "EastFiligreeCache", deadEnd: true), + "Library Lab", + new Dictionary>> { + { + "Library Lab Lower", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Library Portal", + new List> { + new List { + "12", + }, + } + }, } }, { - "Overworld Cave", // East beach, next to swamp entrance, rotating lights room - new List { - new TunicPortal("Overworld Redux", "", "Caustic Light Cave Exit", granularRegion: "Overworld Cave", deadEnd: true), + "Library Portal", + new Dictionary>> { + { + "Library Lab", + new List> { + } + }, } }, { - "Maze Room", // Invisible maze - new List { - new TunicPortal("Overworld Redux", "", "Maze Cave Exit", granularRegion: "Maze Room", deadEnd: true), + "Fortress Exterior from East Forest", + new Dictionary>> { + { + "Fortress Exterior from Overworld", + new List> { + new List { + "Hyperdash", + }, + new List { + "Wand", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Fortress Courtyard Upper", + new List> { + new List { + "Ladder Storage", + }, + } + }, + { + "Fortress Exterior near cave", + new List> { + new List { + "Ladder Storage", + }, + } + }, + { + "Fortress Courtyard", + new List> { + new List { + "Ladder Storage", + }, + } + }, } }, { - "Town Basement", // Hourglass cave - new List { - new TunicPortal("Overworld Redux", "beach", "Hourglass Cave Exit", granularRegion: "Town Basement", deadEnd: true), // yes, it has a tag even though it doesn't need one + "Fortress Exterior from Overworld", + new Dictionary>> { + { + "Fortress Exterior from East Forest", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Fortress Exterior near cave", + new List> { + new List { + "12", + }, + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Fortress Courtyard", + new List> { + new List { + "Hyperdash", + }, + new List { + "26", "Techbow", "Wand", "Stundagger", "nmg", + }, + new List { + "Ladder Storage", + }, + } + }, } }, { - "ShopSpecial", // Special shop, laurel across from that platform between east forest and fortress - new List { - new TunicPortal("Overworld Redux", "", "Special Shop Exit", granularRegion: "ShopSpecial", deadEnd: true), + "Fortress Exterior near cave", + new Dictionary>> { + { + "Fortress Exterior from Overworld", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Fortress Courtyard", + new List> { + new List { + "Ladder Storage", + }, + } + }, + { + "Fortress Courtyard Upper", + new List> { + new List { + "Ladder Storage", + }, + } + }, } }, { - "Temple", // Where you put the hexes - new List { - new TunicPortal("Overworld Redux", "rafters", "Temple Rafters Exit", granularRegion: "Temple"), - new TunicPortal("Overworld Redux", "main", "Temple Door Exit", granularRegion: "Temple"), + "Fortress Courtyard", + new Dictionary>> { + { + "Fortress Courtyard Upper", + new List> { + new List { + "26", "Techbow", "Wand", "Stundagger", "nmg", + }, + } + }, + { + "Fortress Exterior from Overworld", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Sewer", // Beneath the Well - new List { - new TunicPortal("Overworld Redux", "entrance", "Well Ladder Exit", granularRegion: "Sewer"), - new TunicPortal("Sewer_Boss", "", "Well to Well Boss", granularRegion: "Sewer"), - new TunicPortal("Overworld Redux", "west_aqueduct", "Well Exit towards Furnace", granularRegion: "Sewer"), + "Fortress Courtyard Upper", + new Dictionary>> { + { + "Fortress Courtyard", + new List> { + } + }, } }, { - "Sewer_Boss", // Boss room in the Beneath the Well - new List { - new TunicPortal("Sewer", "", "Well Boss to Well", granularRegion: "Sewer_Boss"), - new TunicPortal("Crypt Redux", "", "Checkpoint to Dark Tomb", granularRegion: "Sewer_Boss", requiredItemsOr: new List> { new Dictionary { { "Sewer_Boss, Sewer_", 1 } }, new Dictionary { { "Hyperdash", 1 } } }), + "Beneath the Vault Front", + new Dictionary>> { + { + "Beneath the Vault Back", + new List> { + new List { + "Lantern", + }, + } + }, } }, { - "Crypt Redux", - new List { - new TunicPortal("Overworld Redux", "", "Dark Tomb to Overworld", granularRegion: "Crypt Redux"), - new TunicPortal("Furnace", "", "Dark Tomb to Furnace", granularRegion: "Crypt Redux", requiredItems: new Dictionary { {"Lantern", 1} }), - new TunicPortal("Sewer_Boss", "", "Dark Tomb to Checkpoint", granularRegion: "Crypt Redux"), + "Beneath the Vault Back", + new Dictionary>> { + { + "Beneath the Vault Front", + new List> { + } + }, } }, { - "Archipelagos Redux", // West Garden - new List { - new TunicPortal("Overworld Redux", "lower", "West Garden Exit near Hero's Grave", granularRegion: "West Garden"), - new TunicPortal("archipelagos_house", "", "West Garden to Magic Dagger House", granularRegion: "West Garden"), - new TunicPortal("Overworld Redux", "upper", "West Garden after Boss", granularRegion: "West Garden", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { { "Sword", 1 }, { "Archipelagos Redux, Overworld Redux_lower", 1 } }, new Dictionary { { "Hyperdash", 1 }, {"Archipelagos Redux", 1 } } }), - new TunicPortal("Shop", "", "West Garden Shop", granularRegion: "West Garden"), // there's two of these, one is unused and disabled - new TunicPortal("Overworld Redux", "lowest", "West Garden Laurels Exit", granularRegion: "West Garden", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - new TunicPortal("RelicVoid", "teleporter_relic plinth", "West Garden Hero's Grave", granularRegion: "West Garden", prayerPortal: true), // Hero grave - new TunicPortal("Transit", "teleporter_archipelagos_teleporter", "West Garden to Far Shore", granularRegion: "West Garden Portal", prayerPortal: true, deadEnd: true), // Portal to the thing behind dagger house + "Fortress East Shortcut Lower", + new Dictionary>> { + { + "Fortress East Shortcut Upper", + new List> { + new List { + "26", "Techbow", "Wand", "Stundagger", "nmg", + }, + } + }, } }, { - "archipelagos_house", // Magic Dagger house in West Garden - new List { - new TunicPortal("Archipelagos Redux", "", "Magic Dagger House Exit", granularRegion: "Magic Dagger House", deadEnd: true), + "Fortress East Shortcut Upper", + new Dictionary>> { + { + "Fortress East Shortcut Lower", + new List> { + } + }, } }, { - "Atoll Redux", - new List { - new TunicPortal("Frog Stairs", "eye", "Frog Stairs Eye Entrance", granularRegion: "Atoll"), - new TunicPortal("Library Exterior", "", "Atoll Statue Teleporter", granularRegion: "Atoll", prayerPortal: true), - new TunicPortal("Overworld Redux", "upper", "Atoll Upper Exit", granularRegion: "Atoll"), - new TunicPortal("Overworld Redux", "lower", "Atoll Lower Exit", granularRegion: "Atoll", requiredItems: new Dictionary {{"Hyperdash", 1}}), - new TunicPortal("Frog Stairs", "mouth", "Frog Stairs Mouth Entrance", granularRegion: "Atoll", requiredItemsOr: new List> { new Dictionary { { "Wand", 1 } }, new Dictionary { { "Hyperdash", 1 } } }), - new TunicPortal("Shop", "", "Atoll Shop", granularRegion: "Atoll"), - new TunicPortal("Transit", "teleporter_atoll", "Atoll to Far Shore", granularRegion: "Atoll", prayerPortal: true), - // new TunicPortal("Forest Lake_", "teleporter", "Portal"), // Unused portal, same spot as library portal + "Eastern Vault Fortress", + new Dictionary>> { + { + "Eastern Vault Fortress Gold Door", + new List> { + new List { + "Wand", "Stundagger", "nmg", + }, + new List { + "12", "Fortress Exterior from Overworld", "Beneath the Vault Back", "Fortress Courtyard Upper", + }, + } + }, } }, { - "Frog Stairs", // Entrance to frog's domain - new List { - new TunicPortal("Atoll Redux", "mouth", "Frog Stairs Mouth Exit", granularRegion: "Frog Stairs"), - new TunicPortal("frog cave main", "Exit", "Frog Stairs to Frog's Domain's Exit", granularRegion: "Frog Stairs"), - new TunicPortal("Atoll Redux", "eye", "Frog Stairs Eye Exit", granularRegion: "Frog Stairs"), - new TunicPortal("frog cave main", "Entrance", "Frog Stairs to Frog's Domain's Entrance", granularRegion: "Frog Stairs"), + "Eastern Vault Fortress Gold Door", + new Dictionary>> { + { + "Eastern Vault Fortress", + new List> { + new List { + "Wand", "Stundagger", "nmg", + }, + } + }, } }, { - "frog cave main", // Frog's domain, yes it's lowercase - new List { - new TunicPortal("Frog Stairs", "Exit", "Frog's Domain Orb Exit", granularRegion: "Frog's Domain Back", ignoreScene: true, requiredItems: new Dictionary { { "Wand", 1 }, { "frog cave main, Frog Stairs_Entrance", 1 } }), - new TunicPortal("Frog Stairs", "Entrance", "Frog's Domain Ladder Exit", granularRegion: "Frog's Domain Front", ignoreScene: true, oneWay: true), + "Fortress Grave Path", + new Dictionary>> { + { + "Fortress Hero's Grave", + new List> { + new List { + "12", + }, + } + }, + { + "Fortress Grave Path Dusty Entrance", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Library Exterior", - new List { - new TunicPortal("Library Hall", "", "Library Exterior Ladder", granularRegion: "Library Exterior", requiredItemsOr: new List> { new Dictionary { { "Hyperdash", 1 } }, new Dictionary { { "Wand", 1} } }), - new TunicPortal("Atoll Redux", "", "Library Exterior Tree", granularRegion: "Library Exterior", requiredItemsOr: new List> { new Dictionary { { "Hyperdash", 1 }, { "12", 1 } }, new Dictionary { { "Wand", 1}, { "12", 1 } } }), + "Fortress Grave Path Upper", + new Dictionary>> { + { + "Fortress Grave Path", + new List> { + new List { + "26", "Stundagger", "Techbow", "Wand", "nmg", + }, + } + }, } }, { - "Library Hall", // Entry area with hero grave - new List { - new TunicPortal("Library Rotunda", "", "Library Hall to Rotunda", granularRegion: "Library Hall"), - new TunicPortal("Library Exterior", "", "Library Hall Bookshelf Exit", granularRegion: "Library Hall"), - new TunicPortal("RelicVoid", "teleporter_relic plinth", "Library Hero's Grave", granularRegion: "Library Hall", prayerPortal: true), + "Fortress Grave Path Dusty Entrance", + new Dictionary>> { + { + "Fortress Grave Path", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Library Rotunda", // The circular room with the ladder - new List { - new TunicPortal("Library Hall", "", "Library Rotunda Lower Exit", granularRegion: "Library Rotunda"), - new TunicPortal("Library Lab", "", "Library Rotunda Upper Exit", granularRegion: "Library Rotunda"), + "Fortress Hero's Grave", + new Dictionary>> { + { + "Fortress Grave Path", + new List> { + } + }, } }, { - "Library Lab", - new List { - new TunicPortal("Library Arena", "", "Library Lab to Librarian Arena", granularRegion: "Library Lab", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { { "Hyperdash", 1 }, { "Library Lab", 1 } }, new Dictionary { { "Wand", 1}, {"Library Lab", 1 } } }), - new TunicPortal("Library Rotunda", "", "Library Lab to Rotunda", granularRegion: "Library Lab", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { { "Hyperdash", 1 }, { "Library Lab", 1 } }, new Dictionary { { "Wand", 1}, {"Library Lab, Library Rotunda_", 1 } } }), - new TunicPortal("Transit", "teleporter_library teleporter", "Library to Far Shore", granularRegion: "Library Lab", ignoreScene: true, prayerPortal: true, requiredItemsOr: new List> { new Dictionary { { "Hyperdash", 1 }, { "Library Lab, Library Rotunda_", 1 } }, new Dictionary { { "Wand", 1}, {"Library Lab, Library Rotunda_", 1 } }, new Dictionary { { "Library Lab", 1 } } }), + "Fortress Arena", + new Dictionary>> { + { + "Fortress Arena Portal", + new List> { + new List { + "12", "Fortress Exterior from Overworld", "Beneath the Vault Back", "Eastern Vault Fortress", + }, + } + }, } }, { - "Library Arena", - new List { - new TunicPortal("Library Lab", "", "Librarian Arena Exit", granularRegion: "Library Arena", deadEnd: true), + "Fortress Arena Portal", + new Dictionary>> { + { + "Fortress Arena", + new List> { + } + }, } }, { - "East Forest Redux", - new List { - new TunicPortal("Sword Access", "lower", "Forest Grave Path Lower Entrance", granularRegion: "East Forest"), - new TunicPortal("East Forest Redux Laddercave", "upper", "Forest Dance Fox Outside Doorway", granularRegion: "East Forest", requiredItems: new Dictionary { { "Hyperdash", 1 } } ), - new TunicPortal("East Forest Redux Interior", "lower", "Forest Guard House 2 Lower Entrance", granularRegion: "East Forest"), - new TunicPortal("East Forest Redux Laddercave", "gate", "Forest Guard House 1 Gate Entrance", granularRegion: "East Forest"), - new TunicPortal("Sword Access", "upper", "Forest Grave Path Upper Entrance", granularRegion: "East Forest"), - new TunicPortal("East Forest Redux Interior", "upper", "Forest Guard House 2 Upper Entrance", granularRegion: "East Forest"), - new TunicPortal("East Forest Redux Laddercave", "lower", "Forest Guard House 1 Lower Entrance", granularRegion: "East Forest"), - new TunicPortal("Forest Belltower", "", "Forest to Belltower", granularRegion: "East Forest"), - new TunicPortal("Transit", "teleporter_forest teleporter", "Forest to Far Shore", granularRegion: "East Forest", prayerPortal: true), + "Lower Mountain", + new Dictionary>> { + { + "Lower Mountain Stairs", + new List> { + new List { + "21", + }, + } + }, } }, { - "East Forest Redux Laddercave", // the place with the two ladders that leads to the boss room - new List { - new TunicPortal("East Forest Redux", "upper", "Guard House 1 Dance Fox Exit", "Laddercave", ignoreScene: true, givesAccess: new List { "East Forest Redux Laddercave, East Forest Redux_upper" }, requiredItems: new Dictionary { { "East Forest Redux Laddercave, East Forest Redux_gate", 1 } }), // making the upper ones the "center" for easier logic writing - new TunicPortal("East Forest Redux", "lower", "Guard House 1 Lower Exit", "Laddercave", ignoreScene: true, givesAccess: new List { "East Forest Redux Laddercave, East Forest Redux_lower" }, requiredItems: new Dictionary { { "East Forest Redux Laddercave, East Forest Redux_gate", 1 } }), - new TunicPortal("East Forest Redux", "gate", "Guard House 1 Upper Forest Exit", "Laddercave"), - new TunicPortal("Forest Boss Room", "", "Guard House 1 to Guard Captain Room", "Laddercave"), + "Lower Mountain Stairs", + new Dictionary>> { + { + "Lower Mountain", + new List> { + new List { + "21", + }, + } + }, } }, { - "Sword Access", // East forest hero grave area - new List { - new TunicPortal("East Forest Redux", "upper", "Forest Grave Path Upper Exit", granularRegion: "Sword Access", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - new TunicPortal("East Forest Redux", "lower", "Forest Grave Path Lower Exit", granularRegion: "Sword Access"), - new TunicPortal("RelicVoid", "teleporter_relic plinth", "East Forest Hero's Grave", granularRegion: "Sword Access Back", ignoreScene: true, prayerPortal: true, requiredItems: new Dictionary { {"Sword Access, East Forest Redux_lower", 1 } }), // Can't open the gate from behind - - // new TunicPortal("Forest 1_", "lower", "Portal (1)"), - // new TunicPortal("Forest 1_", "", "Portal"), - // new TunicPortal("Forest 1_", "upper", "Portal (2)"), + "Monastery Back", + new Dictionary>> { + { + "Monastery Front", + new List> { + new List { + "Hyperdash", "nmg", + }, + } + }, + { + "Monastery Hero's Grave", + new List> { + new List { + "12", + }, + } + }, } }, { - "East Forest Redux Interior", // Guardhouse 2 - new List { - new TunicPortal("East Forest Redux", "lower", "Guard House 2 Lower Exit", "Guardhouse 2"), - new TunicPortal("East Forest Redux", "upper", "Guard House 2 Upper Exit", "Guardhouse 2"), + "Monastery Hero's Grave", + new Dictionary>> { + { + "Monastery Back", + new List> { + } + }, } }, { - "Forest Boss Room", - new List { - new TunicPortal("East Forest Redux Laddercave", "", "Guard Captain Room Non-Gate Exit", "Forest Boss"), // entering it from behind puts you in the room, not behind the gate - new TunicPortal("Forest Belltower", "", "Guard Captain Room Gate Exit", "Forest Boss"), - - // new TunicPortal("Archipelagos Redux_", "showfloordemo2022", "Portal (2)"), + "Monastery Front", + new Dictionary>> { + { + "Monastery Back", + new List> { + new List { + "Sword" + }, + new List { + "Techbow" + } + } + }, } }, { - "Forest Belltower", - new List { - new TunicPortal("Fortress Courtyard", "", "Forest Belltower to Fortress", granularRegion: "Forest Belltower Main"), - new TunicPortal("East Forest Redux", "", "Forest Belltower to Forest", granularRegion: "Forest Belltower Lower"), - new TunicPortal("Overworld Redux", "", "Forest Belltower to Overworld", granularRegion: "Forest Belltower Main"), - new TunicPortal("Forest Boss Room", "", "Forest Belltower to Guard Captain Room", granularRegion: "Forest Belltower Upper", ignoreScene: true, oneWay: true), + "Quarry Entry", + new Dictionary>> { + { + "Quarry Portal", + new List> { + new List { + "12", "Quarry Connector", "Wand" + }, + } + }, + { + "Quarry", + new List> { + new List { + "Techbow", + }, + new List { + "Sword", + }, + } + }, } }, { - "Fortress Courtyard", // Outside the fortress, the area connected to east forest and overworld. Center of the area is on the fortress-side of the bridge - new List { - new TunicPortal("Fortress Reliquary", "Lower", "Fortress Courtyard to Fortress Grave Path Lower", granularRegion: "Fortress Courtyard"), - new TunicPortal("Fortress Reliquary", "Upper", "Fortress Courtyard to Fortress Grave Path Upper", granularRegion: "Fortress Courtyard Upper", ignoreScene: true, oneWay: true, givesAccess: new List { "Fortress Courtyard, Fortress East_" }), - new TunicPortal("Fortress Main", "Big Door", "Fortress Courtyard to Fortress Interior", granularRegion: "Fortress Courtyard"), - new TunicPortal("Fortress East", "", "Fortress Courtyard to East Fortress", granularRegion: "Fortress Courtyard Upper", ignoreScene: true, oneWay: true, givesAccess: new List { "Fortress Courtyard, Fortress Reliquary_Upper" }), - new TunicPortal("Fortress Basement", "", "Fortress Courtyard to Beneath the Earth", granularRegion: "Fortress Courtyard", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { { "12", 1 }, { "Fortress Courtyard, Overworld Redux_", 1 } }, new Dictionary { { "Hyperdash", 1 }, { "Fortress Courtyard", 1 } }, new Dictionary { {"Fortress Courtyard, Shop_", 1 } } }), - new TunicPortal("Forest Belltower", "", "Fortress Courtyard to Forest Belltower", granularRegion: "Fortress Courtyard", requiredItems: new Dictionary{ { "Hyperdash", 1 } }), - new TunicPortal("Overworld Redux", "", "Fortress Courtyard to Overworld", granularRegion: "Fortress Courtyard", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { {"Hyperdash", 1}, { "Fortress Courtyard", 1 } }, new Dictionary { {"Fortress Courtyard, Fortress East_", 1} }, new Dictionary { { "Wand", 1 }, { "Fortress Courtyard, Forest Belltower_", 1 } } }), // remember, required items is just what you need to get to the center of a region -- prayer only gets you to the shop and beneath the earth - new TunicPortal("Shop", "", "Fortress Courtyard Shop", granularRegion: "Fortress Courtyard", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { { "12", 1 }, { "Fortress Courtyard, Overworld Redux_", 1 } }, new Dictionary { { "Hyperdash", 1 }, { "Fortress Courtyard", 1 } }, new Dictionary { {"Fortress Courtyard, Fortress Basement_", 1 } } }), - - // new TunicPortal("Overworld Redux_", "", "Portal (4)"), // unused and disabled + "Quarry Portal", + new Dictionary>> { + { + "Quarry Entry", + new List> { + } + }, + } + }, + { + "Quarry Monastery Entry", + new Dictionary>> { + { + "Quarry", + new List> { + new List { + "Techbow", + }, + new List { + "Sword", + }, + } + }, + { + "Quarry Back", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Monastery Rope", + new List> { + new List { + "Ladder Storage", + }, + } + }, } }, { - "Fortress Basement", // Under the fortress - new List - { - new TunicPortal("Fortress Main", "", "Beneath the Earth to Fortress Interior", "Fortress Basement"), - new TunicPortal("Fortress Courtyard", "", "Beneath the Earth to Fortress Courtyard", "Fortress Basement"), + "Quarry Back", + new Dictionary>> { + { + "Quarry", + new List> { + new List { + "Techbow", + }, + new List { + "Sword", + }, + } + }, + { + "Quarry Monastery Entry", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Fortress Main", // Inside the fortress - new List { - new TunicPortal("Fortress Courtyard", "Big Door", "Fortress Interior Main Exit", "Fortress Main"), - new TunicPortal("Fortress Basement", "", "Fortress Interior to Beneath the Earth", "Fortress Main"), - new TunicPortal("Fortress Arena", "", "Fortress Interior to Siege Engine Arena", "Fortress Main", requiredItems: new Dictionary { { "12", 1 }, {"Fortress Courtyard, Overworld Redux_", 1}, { "Fortress Courtyard, Fortress Reliquary_upper", 1 }, {"Fortress Main, Fortress Courtyard_Big Door", 1 } }), // requires that one prayer thing to be down - new TunicPortal("Shop", "", "Fortress Interior Shop", "Fortress Main"), - new TunicPortal("Fortress East", "upper", "Fortress Interior to East Fortress Upper", "Fortress Main"), - new TunicPortal("Fortress East", "lower", "Fortress Interior to East Fortress Lower", "Fortress Main"), + "Quarry", + new Dictionary>> { + { + "Lower Quarry", + new List> { + new List { + "Mask", + }, + } + }, + { + "Quarry Entry", + new List> { + } + }, + { + "Quarry Back", + new List> { + } + }, + { + "Quarry Monastery Entry", + new List> { + } + }, } }, { - "Fortress East", // that tiny area with the couple mages up high, and the ladder in the lower right - new List { - new TunicPortal("Fortress Main", "lower", "East Fortress to Interior Lower", granularRegion: "Fortress East Lower", ignoreScene: true, requiredItems: new Dictionary { { "Fortress East, Fortress Main_upper", 1} }), - new TunicPortal("Fortress Courtyard", "", "East Fortress to Courtyard", granularRegion: "Fortress East"), - new TunicPortal("Fortress Main", "upper", "East Fortress to Interior Upper", granularRegion: "Fortress East"), + "Lower Quarry", + new Dictionary>> { + { + "Lower Quarry Zig Door", + new List> { + new List { + "Quarry", "Quarry Connector", "Wand" + } + } + }, } }, { - "Fortress Reliquary", // Where the grave is - new List { - new TunicPortal("Fortress Courtyard", "Lower", "Fortress Grave Path Lower Exit", granularRegion: "Fortress Grave Path"), - new TunicPortal("Dusty", "", "Fortress Grave Path Dusty Entrance", granularRegion: "Fortress Grave Path", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - new TunicPortal("Fortress Courtyard", "Upper", "Fortress Grave Path Upper Exit", granularRegion: "Fortress Grave Path Upper", deadEnd: true), - new TunicPortal("RelicVoid", "teleporter_relic plinth", "Fortress Hero's Grave", granularRegion: "Fortress Grave Path", prayerPortal: true), + "Monastery Rope", + new Dictionary>> { + { + "Quarry Back", + new List> { + } + }, } }, { - "Dusty", // broom - new List { - new TunicPortal("Fortress Reliquary", "", "Dusty Exit", "Dusty", deadEnd: true), + "Rooted Ziggurat Upper Entry", + new Dictionary>> { + { + "Rooted Ziggurat Upper Front", + new List> { + } + }, } }, { - "Fortress Arena", // Where the boss is - new List { - new TunicPortal("Fortress Main", "", "Siege Engine Arena to Fortress", "Fortress Arena"), - new TunicPortal("Transit", "teleporter_spidertank", "Fortress to Far Shore", "Fortress Arena", entryItems: new Dictionary { { "12", 1 }, { "Fortress Basement, Fortress Main_", 1 }, {"Fortress Main, Fortress Courtyard_", 1}, { "Fortress Courtyard, Fortress Main_", 1 } }), - // new TunicPortal("Fortress Main_", "", "Portal"), // There's two of these, one is disabled + "Rooted Ziggurat Upper Front", + new Dictionary>> { + { + "Rooted Ziggurat Upper Back", + new List> { + new List { + "Sword", + }, + new List { + "Hyperdash", + }, + } + }, } }, { - "Mountain", - new List { - new TunicPortal("Mountaintop", "", "Stairs to Top of the Mountain", "Mountain", requiredItems: new Dictionary { { "21", 1 } }), - new TunicPortal("Quarry Redux", "", "Mountain to Quarry", "Mountain"), - new TunicPortal("Overworld Redux", "", "Mountain to Overworld", "Mountain"), + "Rooted Ziggurat Upper Back", + new Dictionary>> { + { + "Rooted Ziggurat Upper Front", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Mountaintop", - new List { - new TunicPortal("Mountain", "", "Top of the Mountain Exit", "Mountaintop", deadEnd: true), + "Rooted Ziggurat Middle Top", + new Dictionary>> { + { + "Rooted Ziggurat Middle Bottom", + new List> { + } + }, } }, { - "Darkwoods Tunnel", // connector between overworld and quarry - new List { - new TunicPortal("Overworld Redux", "", "Quarry Connector to Overworld", "Darkwoods"), - new TunicPortal("Quarry Redux", "", "Quarry Connector to Quarry", "Darkwoods"), + "Rooted Ziggurat Lower Front", + new Dictionary>> { + { + "Rooted Ziggurat Lower Back", + new List> { + new List { + "Hyperdash", + }, + new List { + "Sword", "12", + }, + } + }, } }, { - "Quarry Redux", - new List { - new TunicPortal("Darkwoods Tunnel", "", "Quarry to Overworld Exit", granularRegion: "Quarry"), - new TunicPortal("Shop", "", "Quarry Shop", granularRegion: "Quarry"), - new TunicPortal("Monastery", "front", "Quarry to Monastery Front", granularRegion: "Quarry"), - new TunicPortal("Monastery", "back", "Quarry to Monastery Back", granularRegion: "Monastery Rope", ignoreScene: true, oneWay: true), - new TunicPortal("Mountain", "", "Quarry to Mountain", granularRegion: "Quarry"), - new TunicPortal("ziggurat2020_0", "", "Quarry to Ziggurat", granularRegion: "Quarry", entryItems: new Dictionary { { "Wand", 1 }, { "Darkwood Tunnel, Quarry Redux_", 1 }, { "12", 1 } }), - new TunicPortal("Transit", "teleporter_quarry teleporter", "Quarry to Far Shore", granularRegion: "Quarry", prayerPortal: true, entryItems: new Dictionary { { "Wand", 1 }, { "Darkwood Tunnel, Quarry Redux_", 1 }, { "12", 1 } }), + "Rooted Ziggurat Lower Back", + new Dictionary>> { + { + "Rooted Ziggurat Lower Front", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Rooted Ziggurat Portal Room Entrance", + new List> { + new List { + "12", + }, + } + }, } }, { - "Monastery", - new List { - new TunicPortal("Quarry Redux", "back", "Monastery Rear Exit", "Monastery"), - new TunicPortal("Quarry Redux", "front", "Monastery Front Exit", "Monastery"), - new TunicPortal("RelicVoid", "teleporter_relic plinth", "Monastery Hero's Grave", "Monastery", prayerPortal: true), - - // new TunicPortal("Quarry_", "lower", "Portal (1)"), // Unused portal, disabled, and very high up + "Rooted Ziggurat Portal Room Entrance", + new Dictionary>> { + { + "Rooted Ziggurat Lower Back", + new List> { + } + }, } }, { - "ziggurat2020_0", // Zig entrance hallway - new List { - new TunicPortal("ziggurat2020_1", "", "Zig Entry Hallway to Zig Upper", "Zig 0"), - new TunicPortal("Quarry Redux", "", "Zig Entry Hallway to Quarry", "Zig 0"), + "Rooted Ziggurat Portal Room Exit", + new Dictionary>> { + { + "Rooted Ziggurat Portal", + new List> { + new List { + "12", + }, + } + }, } }, { - "ziggurat2020_1", // Upper zig - new List { - // new TunicPortal("ziggurat2020_3", "zig2_skip", "Zig Skip"), // the elevator skip to lower zig, put a secret here later - new TunicPortal("ziggurat2020_0", "", "Zig Upper to Zig Entry", granularRegion: "Zig 1 Top", ignoreScene: true, oneWay: true), - new TunicPortal("ziggurat2020_2", "", "Zig Upper to Zig Tower", granularRegion: "Zig 1 Bottom", deadEnd: true, ignoreScene: true, requiredItems: new Dictionary{{"ziggurat2020_1, ziggurat2020_0_", 1}}), + "Rooted Ziggurat Portal", + new Dictionary>> { + { + "Rooted Ziggurat Portal Room Exit", + new List> { + new List { + "12", "Rooted Ziggurat Lower Back", + }, + } + }, } }, { - "ziggurat2020_2", // Zig intermediate elevator - new List { - new TunicPortal("ziggurat2020_1", "", "Zig Tower to Zig Upper", granularRegion: "Zig 2 Top", ignoreScene: true, oneWay: true), - new TunicPortal("ziggurat2020_3", "", "Zig Tower to Zig Lower", granularRegion: "Zig 2 Bottom", deadEnd: true, ignoreScene: true, requiredItems: new Dictionary{{"ziggurat2020_2, ziggurat2020_1_", 1}}), + "Swamp", + new Dictionary>> { + { + "Swamp to Cathedral Main Entrance", + new List> { + new List { + "12", "Hyperdash", + }, + new List { + "Stundagger", "Wand", "nmg", + }, + } + }, + { + "Swamp to Cathedral Treasure Room", + new List> { + new List { + "21", + }, + } + }, + { + "Back of Swamp", + new List> { + new List { + "Ladder Storage", + }, + } + }, } }, { - "ziggurat2020_3", // Lower zig, center is designated as before the prayer spot with the two cube minibosses - new List { - new TunicPortal("ziggurat2020_FTRoom", "", "Zig Portal Room Entrance", granularRegion: "Zig 3", ignoreScene: true, prayerPortal: true, requiredItemsOr: new List> { new Dictionary { {"Hyperdash", 1 }, { "ziggurat2020_3, ziggurat2020_2_", 1 } }, new Dictionary { { "12", 1 }, { "ziggurat2020_3, ziggurat2020_2_", 1 } } }), // Prayer portal room - // new TunicPortal("ziggurat2020_1", "zig2_skip", "Zig Skip Exit"), // the elevator skip to lower zig - new TunicPortal("ziggurat2020_2", "", "Zig Lower to Zig Tower", granularRegion: "Zig 3"), + "Swamp to Cathedral Treasure Room Entrance", + new Dictionary>> { + { + "Swamp", + new List> { + } + }, } }, { - "ziggurat2020_FTRoom", // The room with the prayer portal - new List { - new TunicPortal("ziggurat2020_3", "", "Zig Portal Room Exit", "Zig Portal Room", ignoreScene: true, requiredItems: new Dictionary { { "12", 1 }, { "ziggurat2020_3, ziggurat2020_FTRoom", 1 } }), - new TunicPortal("Transit", "teleporter_ziggurat teleporter", "Zig to Far Shore", "Zig Portal Room", prayerPortal: true), + "Swamp to Cathedral Main Entrance", + new Dictionary>> { + { + "Swamp", + new List> { + new List { + "Stundagger", "Wand", "nmg", + }, + } + }, } }, { - "Swamp Redux 2", - new List { - new TunicPortal("Overworld Redux", "conduit", "Swamp Lower Exit", granularRegion: "Swamp Front"), - new TunicPortal("Cathedral Redux", "main", "Swamp to Cathedral Main Entrance", granularRegion: "Swamp Front", requiredItems: new Dictionary { { "12", 1 }, { "Hyperdash", 1 }, { "Overworld Redux, Swamp Redux 2_wall", 1 } } ), - new TunicPortal("Cathedral Redux", "secret", "Swamp to Cathedral Secret Legend Room Entrance", granularRegion: "Swamp Front", requiredItems: new Dictionary { { "21", 1 } }), - new TunicPortal("Cathedral Arena", "", "Swamp to Gauntlet", granularRegion: "Swamp Back", ignoreScene: true, requiredItemsOr: new List> { new Dictionary { { "Hyperdash", 1 }, {"Swamp Redux 2, Overworld Redux_wall", 1 } }, new Dictionary { { "Swamp Redux 2, RelicVoid_teleporter_relic plinth", 1 } } }), - new TunicPortal("Shop", "", "Swamp Shop", granularRegion: "Swamp Front"), - new TunicPortal("Overworld Redux", "wall", "Swamp Upper Exit", granularRegion: "Swamp Back", ignoreScene: true, requiredItems: new Dictionary { { "Hyperdash", 1 }, { "Swamp Redux 2, Cathedral Arena_", 1 } }), - new TunicPortal("RelicVoid", "teleporter_relic plinth", "Swamp Hero's Grave", granularRegion: "Swamp Back", ignoreScene: true, prayerPortal: true, requiredItems: new Dictionary { { "Swamp Redux 2, Cathedral Arena_", 1 } }), + "Back of Swamp", + new Dictionary>> { + { + "Back of Swamp Laurels Area", + new List> { + new List { + "Hyperdash", + }, + new List { + "Ladder Storage", + }, + } + }, + { + "Swamp Hero's Grave", + new List> { + new List { + "12", + }, + } + }, } }, { - "Cathedral Redux", - new List { - new TunicPortal("Swamp Redux 2", "main", "Cathedral Main Exit", granularRegion: "Cathedral"), - new TunicPortal("Cathedral Arena", "", "Cathedral Elevator", granularRegion: "Cathedral"), - new TunicPortal("Swamp Redux 2", "secret", "Cathedral Secret Legend Room Exit", granularRegion: "Cathedral Secret Legend", ignoreScene: true, deadEnd: true), // only one chest, just use item access rules for it + "Back of Swamp Laurels Area", + new Dictionary>> { + { + "Back of Swamp", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Swamp", + new List> { + new List { + "26", "Wand", "Techbow", "Stundagger", "nmg", + }, + } + }, + } + }, + { + "Swamp Hero's Grave", + new Dictionary>> { + { + "Back of Swamp", + new List> { + } + }, } }, { - "Cathedral Arena", // Gauntlet - new List { - new TunicPortal("Swamp Redux 2", "", "Gauntlet to Swamp", granularRegion: "Gauntlet Bottom", ignoreScene: true, deadEnd: true, requiredItems: new Dictionary{{"Cathedral Arena, Cathedral Redux_", 1}, {"Hyperdash", 1}}), - new TunicPortal("Cathedral Redux", "", "Gauntlet Elevator", granularRegion: "Gauntlet Top", ignoreScene: true, givesAccess: new List {"Cathedral Arena, Shop_"}, requiredItems: new Dictionary{{"Cathedral Arena, Shop_", 1}}), - new TunicPortal("Shop", "", "Gauntlet Shop", granularRegion: "Gauntlet Top", ignoreScene: true, givesAccess: new List {"Cathedral Arena, Swamp Redux 2_"}, requiredItems: new Dictionary{{"Cathedral Arena, Cathedral Redux_", 1}}), // we love gauntlet shop + "Cathedral Gauntlet Checkpoint", + new Dictionary>> { + { + "Cathedral Gauntlet", + new List> { + } + }, } }, { - "Shop", // Every shop is just this region - new List { - new TunicPortal("Previous Region", "", "Shop Portal", granularRegion: "Shop"), + "Cathedral Gauntlet", + new Dictionary>> { + { + "Cathedral Gauntlet Exit", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "RelicVoid", // Hero relic area - new List { - new TunicPortal("Fortress Reliquary", "teleporter_relic plinth", "Hero's Grave to Fortress", "RelicVoid", ignoreScene: true, deadEnd: true), - new TunicPortal("Monastery", "teleporter_relic plinth", "Hero's Grave to Monastery", "RelicVoid", ignoreScene: true, deadEnd: true), - new TunicPortal("Archipelagos Redux", "teleporter_relic plinth", "Hero's Grave to West Garden", "RelicVoid", ignoreScene: true, deadEnd: true), - new TunicPortal("Sword Access", "teleporter_relic plinth", "Hero's Grave to East Forest", "RelicVoid", ignoreScene: true, deadEnd: true), - new TunicPortal("Library Hall", "teleporter_relic plinth", "Hero's Grave to Library", "RelicVoid", ignoreScene: true, deadEnd: true), - new TunicPortal("Swamp Redux 2", "teleporter_relic plinth", "Hero's Grave to Swamp", "RelicVoid", ignoreScene: true, deadEnd: true), + "Cathedral Gauntlet Exit", + new Dictionary>> { + { + "Cathedral Gauntlet", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Transit", // Teleporter hub - new List { - new TunicPortal("Archipelagos Redux", "teleporter_archipelagos_teleporter", "Far Shore to West Garden", "Transit", entryItems: new Dictionary{ { "12", 1 }, { "Archipelagos Redux, Overworld Redux_lower", 1} }), - new TunicPortal("Library Lab", "teleporter_library teleporter", "Far Shore to Library", "Transit", entryItems: new Dictionary{ { "12", 1 }, { "Library Lab, Library Arena_", 1} }), - new TunicPortal("Quarry Redux", "teleporter_quarry teleporter", "Far Shore to Quarry", "Transit", entryItems: new Dictionary{ { "12", 1 }, { "Quarry Redux, Darkwoods Tunnel_", 1 }, {"Darkwoods Tunnel, Quarry Redux_", 1 }, { "Wand", 1 } }), - new TunicPortal("East Forest Redux", "teleporter_forest teleporter", "Far Shore to East Forest", "Transit", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - new TunicPortal("Fortress Arena", "teleporter_spidertank", "Far Shore to Fortress", "Transit", entryItems: new Dictionary { { "12", 1 }, { "Fortress Basement, Fortress Main_", 1 }, {"Fortress Courtyard, Overworld Redux_", 1}, { "Fortress Courtyard, Fortress Main_", 1 } }), - new TunicPortal("Atoll Redux", "teleporter_atoll", "Far Shore to Atoll", "Transit"), - new TunicPortal("ziggurat2020_FTRoom", "teleporter_ziggurat teleporter", "Far Shore to Zig", "Transit"), - new TunicPortal("Spirit Arena", "teleporter_spirit arena", "Far Shore to Heir", "Transit"), - new TunicPortal("Overworld Redux", "teleporter_town", "Far Shore to Town", "Transit"), - new TunicPortal("Overworld Redux", "teleporter_starting island", "Far Shore to Spawn", "Transit", requiredItems: new Dictionary { { "Hyperdash", 1 } }), - - // new TunicPortal("Transit_", "teleporter_", "Portal"), // Unused portal, far away and not enabled + "Far Shore", + new Dictionary>> { + { + "Far Shore to Spawn", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Far Shore to East Forest", + new List> { + new List { + "Hyperdash", + }, + } + }, + { + "Far Shore to Quarry", + new List> { + new List { + "12", "Quarry Connector", "Quarry", "Wand" + }, + } + }, + { + "Far Shore to Library", + new List> { + new List { + "12", "Library Lab", + }, + } + }, + { + "Far Shore to West Garden", + new List> { + new List { + "12", "West Garden", + }, + } + }, + { + "Far Shore to Fortress", + new List> { + new List { + "12", "Fortress Exterior from Overworld", "Beneath the Vault Back", "Eastern Vault Fortress", + }, + } + }, + } + }, + { + "Far Shore to Spawn", + new Dictionary>> { + { + "Far Shore", + new List> { + new List { + "Hyperdash", + }, + } + }, } }, { - "Spirit Arena", // Heir fight - new List { - new TunicPortal("Transit", "teleporter_spirit arena", "Heir Arena Exit", "Heir Arena", deadEnd: true), + "Far Shore to East Forest", + new Dictionary>> { + { + "Far Shore", + new List> { + new List { + "Hyperdash", + }, + } + }, + } + }, + { + "Far Shore to Quarry", + new Dictionary>> { + { + "Far Shore", + new List> { + } + }, + } + }, + { + "Far Shore to Library", + new Dictionary>> { + { + "Far Shore", + new List> { + } + }, + } + }, + { + "Far Shore to West Garden", + new Dictionary>> { + { + "Far Shore", + new List> { + } + }, + } + }, + { + "Far Shore to Fortress", + new Dictionary>> { + { + "Far Shore", + new List> { + } + }, + } + }, + { + "Shop Entrance 1", + new Dictionary>> { + { + "Shop", + new List> { + } + }, + } + }, + { + "Shop Entrance 2", + new Dictionary>> { + { + "Shop", + new List> { + } + }, + } + }, + { + "Shop Entrance 3", + new Dictionary>> { + { + "Shop", + new List> { + } + }, + } + }, + { + "Shop Entrance 4", + new Dictionary>> { + { + "Shop", + new List> { + } + }, + } + }, + { + "Shop Entrance 5", + new Dictionary>> { + { + "Shop", + new List> { + } + }, } }, { - "Purgatory", // Second save hallway - new List { - new TunicPortal("Purgatory", "bottom", "Purgatory Bottom Exit", "Purgatory"), - new TunicPortal("Purgatory", "top", "Purgatory Top Exit", "Purgatory"), + "Shop Entrance 6", + new Dictionary>> { + { + "Shop", + new List> { + } + }, } }, }; + public static void ShuffleList(IList list, int seed) { + var rng = new System.Random(seed); + int n = list.Count; + + while (n > 1) { + n--; + int k = rng.Next(n + 1); + T value = list[k]; + list[k] = list[n]; + list[n] = value; + } + } + + + // making a separate lists for portals connected to one, two, or three+ regions, to be populated by the foreach coming up next + public static List deadEndPortals = new List(); + public static List twoPlusPortals = new List(); + + public static Dictionary UpdateReachableRegions(Dictionary inventory) { + int inv_count = inventory.Count; + // for each origin region + foreach (KeyValuePair>>> traversal_group in TraversalReqs) { + string origin = traversal_group.Key; + if (!inventory.ContainsKey(origin)) { + continue; + } + //Logger.LogInfo("checking traversal for " + origin); + // for each destination in an origin's group + foreach (KeyValuePair>> destination_group in traversal_group.Value) { + string destination = destination_group.Key; + //Logger.LogInfo("checking traversal to " + destination); + // if we can already reach this region, skip it + if (inventory.ContainsKey(destination)) { + //Logger.LogInfo("we already have it"); + continue; + } + // met is whether you meet any of the requirement lists for a destination + bool met = false; + if (destination_group.Value.Count == 0) { + //Logger.LogInfo("no requirement groups, met is true"); + met = true; + } + // check through each list of requirements + foreach (List reqs in destination_group.Value) { + if (reqs.Count == 0) { + // if a group is empty, you can just walk there + met = true; + //Logger.LogInfo("group is empty, so met is true"); + } else { + // check if we have the items in our inventory to traverse this path + int met_count = 0; + foreach (string req in reqs) { + //Logger.LogInfo("req is " + req); + if (inventory.ContainsKey(req)) { + met_count++; + //Logger.LogInfo("we met this requirement"); + } + } + // if you have all the requirements, you can traverse this path + if (met_count == reqs.Count) { + //Logger.LogInfo("met is true"); + met = true; + } + } + // if we meet one list of requirements, we don't have to do the rest + if (met == true) { + break; + } + } + if (met == true) { + //Logger.LogInfo("adding " + destination + " to inventory"); + inventory.Add(destination, 1); + } else { + //Logger.LogInfo("did not add " + destination + ", we did not meet the requirements"); + } + } + } + // if we gained any regions, rerun this to get any new regions + if (inv_count != inventory.Count) { + UpdateReachableRegions(inventory); + } + return inventory; + } + + public static List UpdateReachableRegions(List inventory) { + int inv_count = inventory.Count; + // for each origin region + foreach (KeyValuePair>>> traversal_group in TraversalReqs) { + string origin = traversal_group.Key; + // for each destination in an origin's group + foreach (KeyValuePair>> destination_group in traversal_group.Value) { + string destination = destination_group.Key; + // if we don't have the origin region, skip it + if (!inventory.Contains(origin)) { + continue; + } + // if we can already reach this region, skip it + if (inventory.Contains(destination)) { + continue; + } + // met is whether you meet any of the requirement lists for a destination + bool met = false; + // check through each list of requirements + foreach (List reqs in destination_group.Value) { + if (reqs.Count == 0) { + // if a group is empty, you can just walk there + met = true; + } else { + // check if we have the items in our inventory to traverse this path + int met_count = 0; + foreach (string req in reqs) { + if (inventory.Contains(req)) { + met_count++; + } + } + // if you have all the requirements, you can traverse this path + if (met_count == reqs.Count) { + met = true; + } + } + // if we meet one list of requirements, we don't have to do the rest + if (met == true) { + break; + } + } + if (met == true) { + inventory.Add(destination); + } + } + } + // if we gained any regions, rerun this to get any new regions + if (inv_count != inventory.Count) { + UpdateReachableRegions(inventory); + } + return inventory; + } + + // create a list of all portals with their information loaded in, just a slightly expanded version of the above to include destinations + public static void RandomizePortals(int seed) { + RandomizedPortals.Clear(); + + // separate the portals into their respective lists + foreach (KeyValuePair>> scene_group in RegionPortalsList) { + string scene_name = scene_group.Key; + if (scene_name == "Shop") { + continue; + } + foreach (KeyValuePair> region_group in scene_group.Value) { + string region_name = region_group.Key; + List region_portals = region_group.Value; + foreach (TunicPortal portal in region_portals) { + Portal TunicPortal = new Portal(name: portal.Name, destination: portal.Destination, scene: scene_name, region: region_name); + if (RegionDict[region_name].DeadEnd == true) { + deadEndPortals.Add(TunicPortal); + } else { + twoPlusPortals.Add(TunicPortal); + } + // need to throw fairy cave into the twoPlus list if laurels is at 10 fairies + if (TunicPortal.Region == "Secret Gathering Place" && SaveFile.GetInt("randomizer laurels location") == 3) { + deadEndPortals.Remove(TunicPortal); + twoPlusPortals.Add(TunicPortal); + } + } + } + } + if (SaveFile.GetInt("randomizer ER fixed shop") == 1) { + foreach (Portal portal in twoPlusPortals) { + if (portal.SceneDestinationTag == "Overworld Redux, Windmill_") { + twoPlusPortals.Remove(portal); + break; + } + } + } + + // modify later if we ever do random start location + string start_region = "Overworld"; + + Dictionary MaxItems = new Dictionary { + { "Stick", 1 }, { "Sword", 1 }, { "Wand", 1 }, { "Stundagger", 1 }, { "Techbow", 1 }, { "Hyperdash", 1 }, { "Mask", 1 }, { "Lantern", 1 }, { "12", 1 }, { "21", 1 }, { "26", 1 }, { "Key", 2 }, { "Key (House)", 1 } + }; + + Dictionary FullInventory = new Dictionary(); + foreach (KeyValuePair item in MaxItems) { + FullInventory.Add(item.Key, item.Value); + } + // if laurels is at 10 fairies, remove laurels until the fairy cave is connected + if (SaveFile.GetInt("randomizer laurels location") == 3) { + FullInventory.Remove("Hyperdash"); + } + FullInventory.Add(start_region, 1); + FullInventory = UpdateReachableRegions(FullInventory); + + // get the total number of regions to get before doing dead ends + int total_nondeadend_count = 0; + foreach (KeyValuePair region in RegionDict) { + if (region.Value.DeadEnd == false) { + total_nondeadend_count++; + } + } + // added fairy cave to the non-dead end regions, so it should increase the count here too + if (SaveFile.GetInt("randomizer laurels location") == 3) { + total_nondeadend_count++; + } + + // create a portal combo for every region in the threePlusRegions list, so that every region can now be accessed (ignoring rules for now) + int comboNumber = 0; + while (FullInventory.Count < total_nondeadend_count + MaxItems.Count) { + ShuffleList(twoPlusPortals, seed); + Portal portal1 = null; + Portal portal2 = null; + foreach (Portal portal in twoPlusPortals) { + // find a portal in a region we can't access yet + if (!FullInventory.ContainsKey(portal.Region)) { + portal1 = portal; + } + } + if (portal1 == null) { + Logger.LogInfo("something messed up in portal pairing for portal 1"); + } + twoPlusPortals.Remove(portal1); + ShuffleList(twoPlusPortals, seed); + foreach (Portal secondPortal in twoPlusPortals) { + if (FullInventory.ContainsKey(secondPortal.Region)) { + portal2 = secondPortal; + twoPlusPortals.Remove(secondPortal); + break; + } + } + if (portal2 == null) { + Logger.LogInfo("something messed up in portal pairing for portal 2"); + } + // add the portal combo to the randomized portals list + RandomizedPortals.Add(comboNumber.ToString(), new PortalCombo(portal1, portal2)); + + FullInventory.Add(portal1.Region, 1); + // if laurels is at fairy cave, add it when we connect fairy cave + if (portal1.Region == "Secret Gathering Place" && SaveFile.GetInt("randomizer laurels location") == 3) { + FullInventory.Add("Hyperdash", 1); + } + FullInventory = UpdateReachableRegions(FullInventory); + comboNumber++; + } + + // since the dead ends only have one exit, we just append them 1 to 1 to a random portal in the two plus list + ShuffleList(deadEndPortals, seed); + ShuffleList(twoPlusPortals, seed); + while (deadEndPortals.Count > 0) { + comboNumber++; + RandomizedPortals.Add(comboNumber.ToString(), new PortalCombo(deadEndPortals[0], twoPlusPortals[0])); + deadEndPortals.RemoveAt(0); + twoPlusPortals.RemoveAt(0); + + } + + // shops get added separately cause they're weird + List shopSceneList = new List(); + int shopCount = 6; + if (SaveFile.GetInt("randomizer ER fixed shop") == 1) { + shopCount = 1; + Portal windmillPortal = new Portal(name: "Windmill Entrance", destination: "Windmill_", scene: "Overworld Redux", region: "Overworld"); + Portal shopPortal = new Portal(name: "Shop Portal", destination: "Previous Region", scene: "Shop", region: "Shop Entrance 2"); + RandomizedPortals.Add("fixedshop", new PortalCombo(windmillPortal, shopPortal)); + shopSceneList.Add("Overworld Redux"); + } + int regionNumber = 0; + while (shopCount > 0) { + // manually making a portal for the shop, because it has some special properties + Portal shopPortal = new Portal(name: "Shop Portal", destination: "Previous Region", scene: "Shop", region: $"Shop Entrance {shopCount}"); + // check that a shop has not already been added to this region, since two shops in the same region causes problems + if (!shopSceneList.Contains(twoPlusPortals[regionNumber].Scene)) { + comboNumber++; + shopSceneList.Add(twoPlusPortals[regionNumber].Scene); + //Logger.LogInfo("adding scene " + twoPlusPortals[regionNumber].Scene + " to shop scene list"); + RandomizedPortals.Add(comboNumber.ToString(), new PortalCombo(twoPlusPortals[regionNumber], shopPortal)); + twoPlusPortals.RemoveAt(regionNumber); + shopCount--; + } else { + regionNumber++; + } + if (regionNumber == twoPlusPortals.Count - 1) { + Logger.LogInfo("too many shops, not enough regions, add more shops"); + } + } + // now we have every region accessible + // the twoPlusPortals list still has items left in it, so now we pair them off + while (twoPlusPortals.Count > 1) { + comboNumber++; + RandomizedPortals.Add(comboNumber.ToString(), new PortalCombo(twoPlusPortals[0], twoPlusPortals[1])); + twoPlusPortals.RemoveAt(1); // I could do removeat0 twice, but I don't like how that looks + twoPlusPortals.RemoveAt(0); + } + if (twoPlusPortals.Count == 1) { + // if this triggers, there's an odd number of portals total + Logger.LogInfo("one extra dead end remaining alone, rip. It's " + twoPlusPortals[0].Name); + } + // todo: figure out why the quarry portal isn't working right + //Portal betaQuarryPortal = new Portal(destination: "Darkwoods", tag: "", name: "Beta Quarry", scene: "Quarry", region: "Quarry", requiredItems: new Dictionary(), givesAccess: new List(), deadEnd: true, prayerPortal: false, oneWay: false, ignoreScene: false); + //Portal zigSkipPortal = new Portal(destination: "ziggurat2020_3", tag: "zig2_skip", name: "Zig Skip", scene: "ziggurat2020_1", region: "Zig 1", requiredItems: new Dictionary(), givesAccess: new List(), deadEnd: true, prayerPortal: false, oneWay: false, ignoreScene: false); + //RandomizedPortals.Add("zigsecret", new PortalCombo(betaQuarryPortal, zigSkipPortal)); + } + // this is for using the info from Archipelago to pair up the portals public static void CreatePortalPairs(Dictionary APPortalStrings) { RandomizedPortals.Clear(); List portalsList = new List(); int comboNumber = 0; - // turn the TunicPortals into Portals (so we can get the scene name in) - foreach (KeyValuePair> region_group in PortalList) { - string region_name = region_group.Key; - List region_portals = region_group.Value; - foreach (TunicPortal portal in region_portals) { - Portal newPortal = new Portal(destination: portal.Destination, tag: portal.DestinationTag, name: portal.PortalName, scene: region_name, region: portal.GranularRegion, requiredItems: portal.RequiredItems, requiredItemsOr: portal.RequiredItemsOr, entryItems: portal.EntryItems, givesAccess: portal.GivesAccess, deadEnd: portal.DeadEnd, prayerPortal: portal.PrayerPortal, oneWay: portal.OneWay, ignoreScene: portal.IgnoreScene); - portalsList.Add(newPortal); + foreach (KeyValuePair>> scene_group in RegionPortalsList) { + string scene_name = scene_group.Key; + foreach (KeyValuePair> region_group in scene_group.Value) { + string region_name = region_group.Key; + List region_portals = region_group.Value; + foreach (TunicPortal portal in region_portals) { + Portal TunicPortal = new Portal(name: portal.Name, destination: portal.Destination, scene: scene_name, region: region_name); + portalsList.Add(TunicPortal); + } } } @@ -693,7 +3998,7 @@ public static void CreatePortalPairs(Dictionary APPortalStrings) foreach (Portal portal in portalsList) { if (portal1SDT == portal.SceneDestinationTag) { - portal1 = portal; + portal1 = portal; } if (portal2SDT == portal.SceneDestinationTag) { portal2 = portal; @@ -706,9 +4011,9 @@ public static void CreatePortalPairs(Dictionary APPortalStrings) } // a function to apply the randomized portal list to portals during onSceneLoaded - // todo: get silent to show me how to reference an object instead of feeding portalPairs back into this public static void ModifyPortals(Scene loadingScene) { - var Portals = Resources.FindObjectsOfTypeAll(); + var Portals = Resources.FindObjectsOfTypeAll().Where(portal => portal.gameObject.scene.name == SceneManager.GetActiveScene().name + && !portal.FullID.Contains("heirfasttravel") && !portal.id.Contains("heirfasttravel")); foreach (var portal in Portals) { // go through the list of randomized portals and see if either the first or second portal matches the one we're looking at foreach (KeyValuePair portalCombo in RandomizedPortals) { @@ -716,29 +4021,32 @@ public static void ModifyPortals(Scene loadingScene) { Portal portal1 = portalCombo.Value.Portal1; Portal portal2 = portalCombo.Value.Portal2; - if (portal1.Scene == loadingScene.name && portal1.Tag == portal.id && portal1.Destination == portal.destinationSceneName) { + if (portal1.Scene == loadingScene.name && portal1.Destination == portal.FullID) { if (portal2.Scene == "Shop") { portal.destinationSceneName = portal2.Scene; portal.id = ""; portal.optionalIDToSpawnAt = ""; + portal.name = portal2.Name; } else { portal.destinationSceneName = portal2.Scene; portal.id = comboTag; portal.optionalIDToSpawnAt = comboTag + comboTag + comboTag + comboTag; // quadrupling since doubling and tripling can have overlaps + portal.name = portal2.Name; } break; } - - if (portal2.Scene == loadingScene.name && portal2.Tag == portal.id && portal2.Destination == portal.destinationSceneName) { + if (portal2.Scene == loadingScene.name && portal2.Destination == portal.FullID) { if (portal1.Scene == "Shop") { portal.destinationSceneName = portal1.Scene; portal.id = ""; portal.optionalIDToSpawnAt = ""; + portal.name = portal1.Name; } else { portal.destinationSceneName = portal1.Scene; portal.id = comboTag + comboTag + comboTag + comboTag; portal.optionalIDToSpawnAt = comboTag; // quadrupling since doubling and tripling can have overlaps + portal.name = portal2.Name; } break; } @@ -747,16 +4055,16 @@ public static void ModifyPortals(Scene loadingScene) { } // this is for use in PlayerCharacterPatches. Will need to refactor later if we do random player spawn - // todo: get silent to show me how to reference an object instead of feeding portalPairs back into this public static void AltModifyPortals() { - var Portals = Resources.FindObjectsOfTypeAll(); + var Portals = Resources.FindObjectsOfTypeAll().Where(portal => portal.gameObject.scene.name == SceneManager.GetActiveScene().name + && !portal.FullID.Contains("heirfasttravel") && !portal.id.Contains("heirfasttravel")); foreach (var portal in Portals) { // go through the list of randomized portals and see if either the first or second portal matches the one we're looking at foreach (KeyValuePair portalCombo in RandomizedPortals) { string comboTag = portalCombo.Key; Portal portal1 = portalCombo.Value.Portal1; Portal portal2 = portalCombo.Value.Portal2; - if (portal1.Scene == "Overworld Redux" && portal1.Tag == portal.id && portal1.Destination == portal.destinationSceneName) { + if (portal1.Scene == "Overworld Redux" && portal1.Destination == portal.FullID) { if (portal2.Scene == "Shop") { portal.destinationSceneName = portal2.Scene; portal.id = ""; @@ -769,7 +4077,7 @@ public static void AltModifyPortals() { break; } - if (portal2.Scene == "Overworld Redux" && portal2.Tag == portal.id && portal2.Destination == portal.destinationSceneName) { + if (portal2.Scene == "Overworld Redux" && portal2.Destination == portal.FullID) { if (portal1.Scene == "Shop") { portal.destinationSceneName = portal1.Scene; portal.id = ""; @@ -785,5 +4093,21 @@ public static void AltModifyPortals() { } } + public static void MarkPortals() { + var Portals = Resources.FindObjectsOfTypeAll().Where(portal => portal.gameObject.scene.name == SceneManager.GetActiveScene().name + && !portal.FullID.Contains("heirfasttravel") && !portal.id.Contains("heirfasttravel")); + + foreach (var portal in Portals) { + if (portal.FullID == PlayerCharacterSpawn.portalIDToSpawnAt) { + foreach (KeyValuePair portalCombo in TunicPortals.RandomizedPortals) { + if (portal.name == portalCombo.Value.Portal1.Name || portal.name == portalCombo.Value.Portal2.Name) { + SaveFile.SetInt("randomizer entered portal " + portalCombo.Value.Portal1.Name, 1); + SaveFile.SetInt("randomizer entered portal " + portalCombo.Value.Portal2.Name, 1); + } + } + } + } + } + } } diff --git a/src/Patches/WaveSpell.cs b/src/Patches/WaveSpell.cs index 2056fb4..9a88bd1 100644 --- a/src/Patches/WaveSpell.cs +++ b/src/Patches/WaveSpell.cs @@ -48,7 +48,7 @@ public void DoWave() { if (!OptionsGUIPatches.BonusOptionsUnlocked) { AreaData AreaData = ScriptableObject.CreateInstance(); AreaData.topLine = ScriptableObject.CreateInstance(); - AreaData.topLine.text = $"\"BONUS CUSTOMIZATION UNLOCKED\""; + AreaData.topLine.text = TunicArchipelago.Settings.UseTrunicTranslations ? "bOnuhs kuhstuhmizA$uhn uhnlawkd" : $"\"BONUS CUSTOMIZATION UNLOCKED\""; AreaData.bottomLine = ScriptableObject.CreateInstance(); AreaData.bottomLine.text = $"%ah^ks for plAi^! (prehs 3 too wAv)"; AreaLabel.ShowLabel(AreaData); @@ -61,6 +61,11 @@ public static void MagicSpell_CheckInput_PostfixPatch(MagicSpell __instance, Il2 if (WaveSpell != null) { WaveSpell.CheckInput(inputs, length); } + + EntranceSeekerSpell EntranceSeekerSpell = __instance.TryCast(); + if (EntranceSeekerSpell != null) { + EntranceSeekerSpell.CheckInput(inputs, length); + } } } } diff --git a/src/PluginInfo.cs b/src/PluginInfo.cs index 6cf6c81..f3507de 100644 --- a/src/PluginInfo.cs +++ b/src/PluginInfo.cs @@ -1,7 +1,7 @@ namespace TunicArchipelago { public class PluginInfo { - public const string NAME = "Tunic Archipelago"; - public const string VERSION = "1.1.0"; - public const string GUID = "com.silentdestroyer.tunicarchipelago"; + public const string NAME = "Tunic Randomizer"; + public const string VERSION = "3.0.0"; + public const string GUID = "silentdestroyer.tunic.randomizer"; } } diff --git a/src/TunicArchipelago.cs b/src/TunicArchipelago.cs index 2da3d12..f191601 100644 --- a/src/TunicArchipelago.cs +++ b/src/TunicArchipelago.cs @@ -24,10 +24,9 @@ public class TunicArchipelago : BasePlugin { public static ManualLogSource Logger; public static RandomizerSettings Settings = new RandomizerSettings(); - - public static string SettingsPath = Application.dataPath + "/../BepInEx/plugins/Tunic Archipelago/ArchipelagoSettings.json"; + public static string SettingsPath = Application.persistentDataPath + "/Randomizer/Settings.json"; public static string ItemTrackerPath = Application.persistentDataPath + "/Randomizer/ItemTracker.json"; - public static string SpoilerLogPath = Application.persistentDataPath + "/Randomizer/ArchipelagoSpoiler.log"; + public static string SpoilerLogPath = Application.persistentDataPath + "/Randomizer/Spoiler.log"; public static ItemTracker Tracker; public override void Load() { @@ -40,6 +39,7 @@ public override void Load() { ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); + ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); ClassInjector.RegisterTypeInIl2Cpp(); @@ -63,10 +63,6 @@ public override void Load() { Directory.CreateDirectory(Application.persistentDataPath + "/Randomizer/"); } - if (!Directory.Exists(Application.dataPath + "/../BepInEx/plugins/Tunic Archipelago")) { - Directory.CreateDirectory(Application.dataPath + "/../BepInEx/plugins/Tunic Archipelago"); - } - if (!File.Exists(SettingsPath)) { Settings = new RandomizerSettings(); File.WriteAllText(SettingsPath, JsonConvert.SerializeObject(Settings, Formatting.Indented)); @@ -205,6 +201,13 @@ public override void Load() { Harmony.Patch(AccessTools.Method(typeof(FileManagementGUI), "LoadFileAndStart"), new HarmonyMethod(AccessTools.Method(typeof(QuickSettings), "FileManagement_LoadFileAndStart_PrefixPatch"))); Harmony.Patch(AccessTools.Method(typeof(SpecialSwampTrigger), "OnTriggerEnter"), new HarmonyMethod(AccessTools.Method(typeof(InteractionPatches), "SpecialSwampTrigger_OnTriggerEnter_PrefixPatch"))); + + Harmony.Patch(AccessTools.Method(typeof(ForcewandItemBehaviour._throwBeamCoroutine_d__32), "MoveNext"), new HarmonyMethod(AccessTools.Method(typeof(CustomItemBehaviors), "ForcewandItemBehaviour__throwBeamCoroutine_d__32_MoveNext_PrefixPatch"))); + + Harmony.Patch(AccessTools.Method(typeof(Ladder), "ClimbOn"), new HarmonyMethod(AccessTools.Method(typeof(PlayerCharacterPatches), "Ladder_ClimbOn_PrefixPatch"))); + + Harmony.Patch(AccessTools.Method(typeof(UpgradeMenu), "__Buy"), new HarmonyMethod(AccessTools.Method(typeof(ItemPatches), "UpgradeMenu___Buy_PrefixPatch"))); + } } } diff --git a/tunic.apworld b/tunic.apworld index 7f858ab..8593b99 100644 Binary files a/tunic.apworld and b/tunic.apworld differ