diff --git a/README.md b/README.md index 83fdeea6..c1e89bac 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ Currently, the following games are supported: * Factorio * Minecraft * Subnautica -* Slay the Spire * Risk of Rain 2 * The Legend of Zelda: Ocarina of Time * Timespinner diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 88b5060d..dee8a6fd 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -184,9 +184,6 @@ # Secret of Evermore /worlds/soe/ @black-sliver -# Slay the Spire -/worlds/spire/ @KonoTyran - # Stardew Valley /worlds/stardew_valley/ @agilbert1412 diff --git a/docs/network diagram/network diagram.md b/docs/network diagram/network diagram.md index cd61d9fe..d660e888 100644 --- a/docs/network diagram/network diagram.md +++ b/docs/network diagram/network diagram.md @@ -117,8 +117,6 @@ flowchart LR %% Java Based Games subgraph Java JM[Mod with Archipelago.MultiClient.Java] - STS[Slay the Spire] - JM <-- Mod the Spire --> STS subgraph Minecraft MCS[Minecraft Forge Server] JMC[Any Java Minecraft Clients] diff --git a/setup.py b/setup.py index ebef215e..8d415932 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,6 @@ non_apworlds: Set[str] = { "Ocarina of Time", "Overcooked! 2", "Raft", - "Slay the Spire", "Sudoku", "Super Mario 64", "VVVVVV", diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index e78eb915..6f0520fe 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -278,7 +278,7 @@ one file, removing the need to manage separate files if one chooses to do so. As a precautionary measure, before submitting a multi-game yaml like this one in a synchronous/sync multiworld, please confirm that the other players in the multi are OK with what you are submitting, and please be fairly reasonable about the submission. (i.e. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered -reasonable, but submitting a ChecksFinder alongside another game OR submitting multiple Slay the Spire runs is likely +reasonable, but submitting a ChecksFinder alongside another game is likely OK) To configure your file to generate multiple worlds, use 3 dashes `---` on an empty line to separate the ending of one @@ -335,7 +335,7 @@ Minecraft: --- -description: Example of generating multiple worlds. World 3 of 3 +description: Example of generating multiple worlds. World 2 of 2 name: ExampleFinder game: ChecksFinder diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index 1980e81c..94696247 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -104,15 +104,7 @@ A list of all available items and locations can be found in the [website's datap - Spirit Temple Silver Gauntlets Chest world: false - # example block 3 - Slay the Spire - - items: - Boss Relic: 3 - locations: - - Boss Relic 1 - - Boss Relic 2 - - Boss Relic 3 - - # example block 4 - Factorio + # example block 3 - Factorio - items: progressive-electric-energy-distribution: 2 electric-energy-accumulators: 1 @@ -125,7 +117,7 @@ A list of all available items and locations can be found in the [website's datap percentage: 80 force: true - # example block 5 - Secret of Evermore + # example block 4 - Secret of Evermore - items: Levitate: 1 Revealer: 1 @@ -136,7 +128,7 @@ A list of all available items and locations can be found in the [website's datap world: true count: 2 - # example block 6 - A Link to the Past + # example block 5 - A Link to the Past - items: Progressive Sword: 4 world: @@ -150,12 +142,11 @@ A list of all available items and locations can be found in the [website's datap player's Starter Chest 1 and removes the chosen item from the item pool. 2. This block will always trigger and will place the player's swords, bow, magic meter, strength upgrades, and hookshots in their own dungeon major item chests. -3. This block will always trigger and will lock boss relics on the bosses. -4. This block has an 80% chance of occurring, and when it does, it will place all but 1 of the items randomly among the +3. This block has an 80% chance of occurring, and when it does, it will place all but 1 of the items randomly among the four locations chosen here. -5. This block will always trigger and will attempt to place a random 2 of Levitate, Revealer and Energize into +4. This block will always trigger and will attempt to place a random 2 of Levitate, Revealer and Energize into other players' Master Sword Pedestals or Boss Relic 1 locations. -6. This block will always trigger and will attempt to place a random number, between 1 and 4, of progressive swords +5. This block will always trigger and will attempt to place a random number, between 1 and 4, of progressive swords into any locations within the game slots named BobsSlaytheSpire and BobsRogueLegacy. diff --git a/worlds/spire/Items.py b/worlds/spire/Items.py deleted file mode 100644 index 188b1e03..00000000 --- a/worlds/spire/Items.py +++ /dev/null @@ -1,39 +0,0 @@ -import typing - -from BaseClasses import Item -from typing import Dict - - -class ItemData(typing.NamedTuple): - code: typing.Optional[int] - progression: bool - event: bool = False - - -item_table: Dict[str, ItemData] = { - 'Card Draw': ItemData(8000, True), - 'Rare Card Draw': ItemData(8001, True), - 'Relic': ItemData(8002, True), - 'Boss Relic': ItemData(8003, True), - - # Event Items - 'Victory': ItemData(None, True, True), - 'Beat Act 1 Boss': ItemData(None, True, True), - 'Beat Act 2 Boss': ItemData(None, True, True), - 'Beat Act 3 Boss': ItemData(None, True, True), - -} - -item_pool: Dict[str, int] = { - 'Card Draw': 15, - 'Rare Card Draw': 2, - 'Relic': 10, - 'Boss Relic': 2 -} - -event_item_pairs: Dict[str, str] = { - "Heart Room": "Victory", - "Act 1 Boss": "Beat Act 1 Boss", - "Act 2 Boss": "Beat Act 2 Boss", - "Act 3 Boss": "Beat Act 3 Boss" -} diff --git a/worlds/spire/Locations.py b/worlds/spire/Locations.py deleted file mode 100644 index e20ab6f5..00000000 --- a/worlds/spire/Locations.py +++ /dev/null @@ -1,35 +0,0 @@ -location_table = { - 'Card Draw 1': 19001, - 'Card Draw 2': 19002, - 'Card Draw 3': 19003, - 'Card Draw 4': 19004, - 'Card Draw 5': 19005, - 'Card Draw 6': 19006, - 'Card Draw 7': 19007, - 'Card Draw 8': 19008, - 'Card Draw 9': 19009, - 'Card Draw 10': 19010, - 'Card Draw 11': 19011, - 'Card Draw 12': 19012, - 'Card Draw 13': 19013, - 'Card Draw 14': 19014, - 'Card Draw 15': 19015, - 'Rare Card Draw 1': 21001, - 'Rare Card Draw 2': 21002, - 'Relic 1': 20001, - 'Relic 2': 20002, - 'Relic 3': 20003, - 'Relic 4': 20004, - 'Relic 5': 20005, - 'Relic 6': 20006, - 'Relic 7': 20007, - 'Relic 8': 20008, - 'Relic 9': 20009, - 'Relic 10': 20010, - 'Boss Relic 1': 22001, - 'Boss Relic 2': 22002, - 'Heart Room': None, - 'Act 1 Boss': None, - 'Act 2 Boss': None, - 'Act 3 Boss': None -} \ No newline at end of file diff --git a/worlds/spire/Options.py b/worlds/spire/Options.py deleted file mode 100644 index 9c947566..00000000 --- a/worlds/spire/Options.py +++ /dev/null @@ -1,74 +0,0 @@ -import typing -from dataclasses import dataclass - -from Options import TextChoice, Range, Toggle, PerGameCommonOptions - - -class Character(TextChoice): - """Enter the internal ID of the character to use. - - if you don't know the exact ID to enter with the mod installed go to - `Mods -> Archipelago Multi-world -> config` to view a list of installed modded character IDs. - - the downfall characters will only work if you have downfall installed. - - Spire Take the Wheel will have your client pick a random character from the list of all your installed characters - including custom ones. - - if the chosen character mod is not installed it will default back to 'The Ironclad' - """ - display_name = "Character" - option_The_Ironclad = 0 - option_The_Silent = 1 - option_The_Defect = 2 - option_The_Watcher = 3 - option_The_Hermit = 4 - option_The_Slime_Boss = 5 - option_The_Guardian = 6 - option_The_Hexaghost = 7 - option_The_Champ = 8 - option_The_Gremlins = 9 - option_The_Automaton = 10 - option_The_Snecko = 11 - option_spire_take_the_wheel = 12 - - -class Ascension(Range): - """What Ascension do you wish to play with.""" - display_name = "Ascension" - range_start = 0 - range_end = 20 - default = 0 - - -class FinalAct(Toggle): - """Whether you will need to collect the 3 keys and beat the final act to complete the game.""" - display_name = "Final Act" - option_true = 1 - option_false = 0 - default = 0 - - -class Downfall(Toggle): - """When Downfall is Installed this will switch the played mode to Downfall""" - display_name = "Downfall" - option_true = 1 - option_false = 0 - default = 0 - - -class DeathLink(Range): - """Percentage of health to lose when a death link is received.""" - display_name = "Death Link %" - range_start = 0 - range_end = 100 - default = 0 - - -@dataclass -class SpireOptions(PerGameCommonOptions): - character: Character - ascension: Ascension - final_act: FinalAct - downfall: Downfall - death_link: DeathLink diff --git a/worlds/spire/Regions.py b/worlds/spire/Regions.py deleted file mode 100644 index 9e2ac0d3..00000000 --- a/worlds/spire/Regions.py +++ /dev/null @@ -1,11 +0,0 @@ -def create_regions(world, player: int): - from . import create_region - from .Locations import location_table - - world.regions += [ - create_region(world, player, 'Menu', None, ['Neow\'s Room']), - create_region(world, player, 'The Spire', [location for location in location_table]) - ] - - # link up our region with the entrance we just made - world.get_entrance('Neow\'s Room', player).connect(world.get_region('The Spire', player)) diff --git a/worlds/spire/Rules.py b/worlds/spire/Rules.py deleted file mode 100644 index 3c6f09b3..00000000 --- a/worlds/spire/Rules.py +++ /dev/null @@ -1,74 +0,0 @@ -from BaseClasses import MultiWorld -from ..AutoWorld import LogicMixin -from ..generic.Rules import set_rule - - -class SpireLogic(LogicMixin): - def _spire_has_relics(self, player: int, amount: int) -> bool: - count: int = self.count("Relic", player) + self.count("Boss Relic", player) - return count >= amount - - def _spire_has_cards(self, player: int, amount: int) -> bool: - count = self.count("Card Draw", player) + self.count("Rare Card Draw", player) - return count >= amount - - -def set_rules(world: MultiWorld, player: int): - - # Act 1 Card Draws - set_rule(world.get_location("Card Draw 1", player), lambda state: True) - set_rule(world.get_location("Card Draw 2", player), lambda state: True) - set_rule(world.get_location("Card Draw 3", player), lambda state: True) - set_rule(world.get_location("Card Draw 4", player), lambda state: state._spire_has_relics(player, 1)) - set_rule(world.get_location("Card Draw 5", player), lambda state: state._spire_has_relics(player, 1)) - - # Act 1 Relics - set_rule(world.get_location("Relic 1", player), lambda state: state._spire_has_cards(player, 1)) - set_rule(world.get_location("Relic 2", player), lambda state: state._spire_has_cards(player, 2)) - set_rule(world.get_location("Relic 3", player), lambda state: state._spire_has_cards(player, 2)) - - # Act 1 Boss Event - set_rule(world.get_location("Act 1 Boss", player), lambda state: state._spire_has_cards(player, 3) and state._spire_has_relics(player, 2)) - - # Act 1 Boss Rewards - set_rule(world.get_location("Rare Card Draw 1", player), lambda state: state.has("Beat Act 1 Boss", player)) - set_rule(world.get_location("Boss Relic 1", player), lambda state: state.has("Beat Act 1 Boss", player)) - - # Act 2 Card Draws - set_rule(world.get_location("Card Draw 6", player), lambda state: state.has("Beat Act 1 Boss", player)) - set_rule(world.get_location("Card Draw 7", player), lambda state: state.has("Beat Act 1 Boss", player)) - set_rule(world.get_location("Card Draw 8", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 6) and state._spire_has_relics(player, 3)) - set_rule(world.get_location("Card Draw 9", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 6) and state._spire_has_relics(player, 4)) - set_rule(world.get_location("Card Draw 10", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 7) and state._spire_has_relics(player, 4)) - - # Act 2 Relics - set_rule(world.get_location("Relic 4", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 7) and state._spire_has_relics(player, 2)) - set_rule(world.get_location("Relic 5", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 7) and state._spire_has_relics(player, 2)) - set_rule(world.get_location("Relic 6", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 7) and state._spire_has_relics(player, 3)) - - # Act 2 Boss Event - set_rule(world.get_location("Act 2 Boss", player), lambda state: state.has("Beat Act 1 Boss", player) and state._spire_has_cards(player, 7) and state._spire_has_relics(player, 4) and state.has("Boss Relic", player)) - - # Act 2 Boss Rewards - set_rule(world.get_location("Rare Card Draw 2", player), lambda state: state.has("Beat Act 2 Boss", player)) - set_rule(world.get_location("Boss Relic 2", player), lambda state: state.has("Beat Act 2 Boss", player)) - - # Act 3 Card Draws - set_rule(world.get_location("Card Draw 11", player), lambda state: state.has("Beat Act 2 Boss", player)) - set_rule(world.get_location("Card Draw 12", player), lambda state: state.has("Beat Act 2 Boss", player)) - set_rule(world.get_location("Card Draw 13", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 4)) - set_rule(world.get_location("Card Draw 14", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 4)) - set_rule(world.get_location("Card Draw 15", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 4)) - - # Act 3 Relics - set_rule(world.get_location("Relic 7", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 4)) - set_rule(world.get_location("Relic 8", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 5)) - set_rule(world.get_location("Relic 9", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 5)) - set_rule(world.get_location("Relic 10", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 5)) - - # Act 3 Boss Event - set_rule(world.get_location("Act 3 Boss", player), lambda state: state.has("Beat Act 2 Boss", player) and state._spire_has_relics(player, 7) and state.has("Boss Relic", player, 2)) - - set_rule(world.get_location("Heart Room", player), lambda state: state.has("Beat Act 3 Boss", player)) - - world.completion_condition[player] = lambda state: state.has("Victory", player) diff --git a/worlds/spire/__init__.py b/worlds/spire/__init__.py deleted file mode 100644 index a0a6a794..00000000 --- a/worlds/spire/__init__.py +++ /dev/null @@ -1,103 +0,0 @@ -import string - -from BaseClasses import Entrance, Item, ItemClassification, Location, MultiWorld, Region, Tutorial -from .Items import event_item_pairs, item_pool, item_table -from .Locations import location_table -from .Options import SpireOptions -from .Regions import create_regions -from .Rules import set_rules -from ..AutoWorld import WebWorld, World - - -class SpireWeb(WebWorld): - tutorials = [Tutorial( - "Multiworld Setup Guide", - "A guide to setting up Slay the Spire for Archipelago. " - "This guide covers single-player, multiworld, and related software.", - "English", - "slay-the-spire_en.md", - "slay-the-spire/en", - ["Phar"] - )] - - -class SpireWorld(World): - """ - A deck-building roguelike where you must craft a unique deck, encounter bizarre creatures, discover relics of - immense power, and Slay the Spire! - """ - - options_dataclass = SpireOptions - options: SpireOptions - game = "Slay the Spire" - topology_present = False - web = SpireWeb() - required_client_version = (0, 3, 7) - - item_name_to_id = {name: data.code for name, data in item_table.items()} - location_name_to_id = location_table - - def create_items(self): - # Fill out our pool with our items from item_pool, assuming 1 item if not present in item_pool - pool = [] - for name, data in item_table.items(): - if not data.event: - for amount in range(item_pool.get(name, 1)): - item = SpireItem(name, self.player) - pool.append(item) - - self.multiworld.itempool += pool - - # Pair up our event locations with our event items - for event, item in event_item_pairs.items(): - event_item = SpireItem(item, self.player) - self.multiworld.get_location(event, self.player).place_locked_item(event_item) - - def set_rules(self): - set_rules(self.multiworld, self.player) - - def create_item(self, name: str) -> Item: - return SpireItem(name, self.player) - - def create_regions(self): - create_regions(self.multiworld, self.player) - - def fill_slot_data(self) -> dict: - slot_data = { - 'seed': "".join(self.random.choice(string.ascii_letters) for i in range(16)) - } - slot_data.update(self.options.as_dict("character", "ascension", "final_act", "downfall", "death_link")) - return slot_data - - def get_filler_item_name(self) -> str: - return self.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"]) - - -def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): - ret = Region(name, player, world) - if locations: - for location in locations: - loc_id = location_table.get(location, 0) - location = SpireLocation(player, location, loc_id, ret) - ret.locations.append(location) - if exits: - for exit in exits: - ret.exits.append(Entrance(player, exit, ret)) - - return ret - - -class SpireLocation(Location): - game: str = "Slay the Spire" - - -class SpireItem(Item): - game = "Slay the Spire" - - def __init__(self, name, player: int = None): - item_data = item_table[name] - super(SpireItem, self).__init__( - name, - ItemClassification.progression if item_data.progression else ItemClassification.filler, - item_data.code, player - ) diff --git a/worlds/spire/docs/en_Slay the Spire.md b/worlds/spire/docs/en_Slay the Spire.md deleted file mode 100644 index 4591db58..00000000 --- a/worlds/spire/docs/en_Slay the Spire.md +++ /dev/null @@ -1,35 +0,0 @@ -# Slay the Spire (PC) - -## Where is the options page? - -The [player options page for this game](../player-options) contains all the options you need to configure and export a -config file. - -## What does randomization do to this game? - -Every non-boss relic drop, every boss relic and rare card drop, and every other card draw is replaced with an -archipelago item. In heart runs, the blue key is also disconnected from the Archipelago item, so you can gather both. - -## What items and locations get shuffled? - -15 card packs, 10 relics, and 3 boss relics and rare card drops are shuffled into the item pool and can be found at any -location that would normally give you these items, except for card packs, which are found at every other normal enemy -encounter. - -## Which items can be in another player's world? - -Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit -certain items to your own world. - -## When the player receives an item, what happens? - -When the player receives an item, you will see the counter in the top right corner with the Archipelago symbol increment -by one. By clicking on this icon, it'll open a menu that lists all the items you received, but have not yet accepted. -You can take any relics and card packs sent to you and add them to your current run. It is advised that you do not open -this menu until you are outside an encounter or event to prevent the game from soft-locking. - -## What happens if a player dies in a run? - -When a player dies, they will be taken back to the main menu and will need to reconnect to start climbing the spire from -the beginning, but they will have access to all the items ever sent to them in the Archipelago menu in the top right. -Any items found in an earlier run will not be sent again if you encounter them in the same location. diff --git a/worlds/spire/docs/slay-the-spire_en.md b/worlds/spire/docs/slay-the-spire_en.md deleted file mode 100644 index daeb6541..00000000 --- a/worlds/spire/docs/slay-the-spire_en.md +++ /dev/null @@ -1,69 +0,0 @@ -# Slay the Spire Setup Guide - -## Required Software - -For Steam-based installation, subscribe to the following mods: - -- [ModTheSpire](https://steamcommunity.com/sharedfiles/filedetails/?id=1605060445) -- [BaseMod](https://steamcommunity.com/workshop/filedetails/?id=1605833019) -- [Archipelago Multiworld Randomizer](https://steamcommunity.com/sharedfiles/filedetails/?id=2596397288) -- (optional) [Downfall](https://steamcommunity.com/sharedfiles/filedetails/?id=1610056683) -- (required for downfall) [StSLib](https://steamcommunity.com/workshop/filedetails/?id=1609158507) - -For GOG or Xbox PC Game Pass installation: - -1. Download the official Steam Console Client [SteamCMD](https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip). -2. Unpack that .zip file into some folder and double-click on `steamcmd.exe`. -3. The client will now update itself. When it's ready type `login anonymous`. Now you are ready to download the actual - mods. -4. Run the following commands to download the required mod files: - - Mod the Spire: `workshop_download_item 646570 1605060445` - - BaseMod: `workshop_download_item 646570 1605833019` - - ArchipelagoMW: `workshop_download_item 646570 2596397288` - - (optional) Downfall: `workshop_download_item 646570 1610056683` - - (required for Downfall) StSLib: `workshop_download_item 646570 1609158507` -5. Open your Slay the Spire installation directory. By default on GOG this is `C:\GOG Games\Slay the Spire`, on PC Game - Pass this is `C:\XboxGames\Slay The Spire\Content`. -6. In the folder where you unzipped SteamCMD there will now be a `steamapps` folder. Copy ModTheSpire.jar from - `steamapps\workshop\content\646570\1605060445\ModTheSpire.jar` to your Slay The Spire installation directory. -7. Create a folder named `mods` inside the Slay the Spire installation directory. Each folder inside - `steamapps\workshop\content\646570` will have a single .jar file. Copy each of them except ModTheSpire.jar into the - `mods` folder you made. -8. Now open Notepad. Paste in the following text: `jre\bin\java.exe -jar ModTheSpire.jar`. Go to "File -> Save as" and - save it into your Slay the Spire installation directory with the name `"start.bat"`. Make sure to include the quotes - in the file name! - -## Configuring your YAML file - -### What is a YAML file and why do I need one? - -Your YAML file contains a set of configuration options which provide the generator with information about how it should -generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy -an experience customized for their taste, and different players in the same multiworld can all have different options. - -### Where do I get a YAML file? - -you can customize your options by visiting -the [Slay the Spire Options Page](/games/Slay%20the%20Spire/player-options). - -### Connect to the MultiServer - -For Steam-based installations, if you are subscribed to ModTheSpire, when you launch the game, you should have the -option to launch the game with mods. - -For GOG or Xbox PC Game Pass intallations, launch the game by double-clicking the `start.bat` file you created earlier -which will give you the option to launch the game with mods. - -On the mod loader screen, ensure you only have the following mods enabled and then start the game: - -- BaseMod -- Archipelago Multiworld Randomizer - -If playing with Downfall, also make sure the following are enabled: - -- Downfall -- StSLib - -Once you are in-game, you will be able to click the **Archipelago** menu option and enter the ip and port (separated by -a colon) in the hostname field and enter your player slot name in the Slot Name field. Then click connect, and now you -are ready to climb the spire!