diff --git a/Generate.py b/Generate.py index f646e994dc..a04e913d6e 100644 --- a/Generate.py +++ b/Generate.py @@ -21,7 +21,6 @@ from BaseClasses import seeddigits, get_seed, PlandoOptions from Main import main as ERmain from settings import get_settings from Utils import parse_yamls, version_tuple, __version__, tuplize_version -from worlds.alttp import Options as LttPOptions from worlds.alttp.EntranceRandomizer import parse_arguments from worlds.alttp.Text import TextTable from worlds.AutoWorld import AutoWorldRegister @@ -35,8 +34,8 @@ def mystery_argparse(): parser = argparse.ArgumentParser(description="CMD Generation Interface, defaults come from host.yaml.") parser.add_argument('--weights_file_path', default=defaults.weights_file_path, - help='Path to the weights file to use for rolling game settings, urls are also valid') - parser.add_argument('--samesettings', help='Rolls settings per weights file rather than per player', + help='Path to the weights file to use for rolling game options, urls are also valid') + parser.add_argument('--sameoptions', help='Rolls options per weights file rather than per player', action='store_true') parser.add_argument('--player_files_path', default=defaults.player_files_path, help="Input directory for player files.") @@ -104,8 +103,8 @@ def main(args=None, callback=ERmain): del(meta_weights["meta_description"]) except Exception as e: raise ValueError("No meta description found for meta.yaml. Unable to verify.") from e - if args.samesettings: - raise Exception("Cannot mix --samesettings with --meta") + if args.sameoptions: + raise Exception("Cannot mix --sameoptions with --meta") else: meta_weights = None player_id = 1 @@ -157,7 +156,7 @@ def main(args=None, callback=ERmain): erargs.skip_output = args.skip_output settings_cache: Dict[str, Tuple[argparse.Namespace, ...]] = \ - {fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.samesettings else None) + {fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None) for fname, yamls in weights_cache.items()} if meta_weights: @@ -311,13 +310,6 @@ def handle_name(name: str, player: int, name_counter: Counter): return new_name -def prefer_int(input_data: str) -> Union[str, int]: - try: - return int(input_data) - except: - return input_data - - def roll_percentage(percentage: Union[int, float]) -> bool: """Roll a percentage chance. percentage is expected to be in range [0, 100]""" diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index 9083867120..7254dd46e1 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -6,6 +6,7 @@ import multiprocessing import threading import time import typing +from uuid import UUID from datetime import timedelta, datetime from pony.orm import db_session, select, commit @@ -62,6 +63,16 @@ def autohost(config: dict): def keep_running(): try: with Locker("autohost"): + # delete unowned user-content + with db_session: + # >>> bool(uuid.UUID(int=0)) + # True + rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True) + seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True) + slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True) + # Command gets deleted by ponyorm Cascade Delete, as Room is Required + if rooms or seeds or slots: + logging.info(f"{rooms} Rooms, {seeds} Seeds and {slots} Slots have been deleted.") run_guardian() while 1: time.sleep(0.1) @@ -191,6 +202,6 @@ def run_guardian(): guardian = threading.Thread(name="Guardian", target=guard) -from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed +from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed, Slot from .customserver import run_server_process, get_static_server_data from .generate import gen_game diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index ee04e56fd7..ec461e7d47 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -49,12 +49,6 @@ def weighted_options(): return render_template("weighted-options.html") -# TODO for back compat. remove around 0.4.5 -@app.route("/games//player-settings") -def player_settings(game: str): - return redirect(url_for("player_options", game=game), 301) - - # Player options pages @app.route("/games//player-options") @cache.cached() diff --git a/WebHostLib/templates/userContent.html b/WebHostLib/templates/userContent.html index c20d39f46d..3603d4112d 100644 --- a/WebHostLib/templates/userContent.html +++ b/WebHostLib/templates/userContent.html @@ -25,6 +25,7 @@ Players Created (UTC) Last Activity (UTC) + Mark for deletion @@ -35,6 +36,7 @@ {{ room.seed.slots|length }} {{ room.creation_time.strftime("%Y-%m-%d %H:%M") }} {{ room.last_activity.strftime("%Y-%m-%d %H:%M") }} + Delete next maintenance. {% endfor %} @@ -51,6 +53,7 @@ Seed Players Created (UTC) + Mark for deletion @@ -60,6 +63,7 @@ {% if seed.multidata %}{{ seed.slots|length }}{% else %}1{% endif %} {{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }} + Delete next maintenance. {% endfor %} diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index af4ed264aa..884c1913f8 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -7,7 +7,7 @@ import zipfile import zlib from io import BytesIO -from flask import request, flash, redirect, url_for, session, render_template +from flask import request, flash, redirect, url_for, session, render_template, abort from markupsafe import Markup from pony.orm import commit, flush, select, rollback from pony.orm.core import TransactionIntegrityError @@ -219,3 +219,29 @@ def user_content(): rooms = select(room for room in Room if room.owner == session["_id"]) seeds = select(seed for seed in Seed if seed.owner == session["_id"]) return render_template("userContent.html", rooms=rooms, seeds=seeds) + + +@app.route("/disown_seed/", methods=["GET"]) +def disown_seed(seed): + seed = Seed.get(id=seed) + if not seed: + return abort(404) + if seed.owner != session["_id"]: + return abort(403) + + seed.owner = 0 + + return redirect(url_for("user_content")) + + +@app.route("/disown_room/", methods=["GET"]) +def disown_room(room): + room = Room.get(id=room) + if not room: + return abort(404) + if room.owner != session["_id"]: + return abort(403) + + room.owner = 0 + + return redirect(url_for("user_content")) diff --git a/data/basepatch.bsdiff4 b/data/basepatch.bsdiff4 index aa8f1375c5..379eee80c6 100644 Binary files a/data/basepatch.bsdiff4 and b/data/basepatch.bsdiff4 differ diff --git a/docs/settings api.md b/docs/settings api.md index 41023879ad..bfc642d4b5 100644 --- a/docs/settings api.md +++ b/docs/settings api.md @@ -1,7 +1,7 @@ # Archipelago Settings API The settings API describes how to use installation-wide config and let the user configure them, like paths, etc. using -host.yaml. For the player settings / player yamls see [options api.md](options api.md). +host.yaml. For the player options / player yamls see [options api.md](options api.md). The settings API replaces `Utils.get_options()` and `Utils.get_default_options()` as well as the predefined `host.yaml` in the repository. diff --git a/settings.py b/settings.py index c58eadf155..390920433c 100644 --- a/settings.py +++ b/settings.py @@ -1,6 +1,6 @@ """ Application settings / host.yaml interface using type hints. -This is different from player settings. +This is different from player options. """ import os.path diff --git a/worlds/adventure/docs/setup_en.md b/worlds/adventure/docs/setup_en.md index 94a735bb74..060225e397 100644 --- a/worlds/adventure/docs/setup_en.md +++ b/worlds/adventure/docs/setup_en.md @@ -43,7 +43,7 @@ an experience customized for their taste, and different players in the same mult You can generate a yaml or download a template by visiting the [Adventure Options Page](/games/Adventure/player-options) -### What are recommended settings to tweak for beginners to the rando? +### What are recommended options to tweak for beginners to the rando? Setting difficulty_switch_a and lowering the dragons' speeds makes the dragons easier to avoid. Adding Chalice to local_items guarantees you'll visit at least one of the interesting castles, as it can only be placed in a castle or the credits room. diff --git a/worlds/adventure/docs/setup_fr.md b/worlds/adventure/docs/setup_fr.md index 07881ce94d..e8346fe6f0 100644 --- a/worlds/adventure/docs/setup_fr.md +++ b/worlds/adventure/docs/setup_fr.md @@ -42,7 +42,7 @@ une expérience personnalisée à leur goût, et différents joueurs dans le mê ### Où puis-je obtenir un fichier YAML ? -Vous pouvez générer un yaml ou télécharger un modèle en visitant la [page des paramètres d'aventure](/games/Adventure/player-settings) +Vous pouvez générer un yaml ou télécharger un modèle en visitant la [page des paramètres d'aventure](/games/Adventure/player-options) ### Quels sont les paramètres recommandés pour s'initier à la rando ? Régler la difficulty_switch_a et réduire la vitesse des dragons rend les dragons plus faciles à éviter. Ajouter Calice à @@ -72,4 +72,4 @@ configuré pour le faire automatiquement. Pour connecter le client au multiserveur, mettez simplement `:` dans le champ de texte en haut et appuyez sur Entrée (si le le serveur utilise un mot de passe, saisissez dans le champ de texte inférieur `/connect  : [mot de passe]`) -Appuyez sur Réinitialiser et commencez à jouer \ No newline at end of file +Appuyez sur Réinitialiser et commencez à jouer diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 6ef1f0db19..747f61498e 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -4,7 +4,7 @@ import Utils import worlds.Files LTTPJPN10HASH: str = "03a63945398191337e896e5771f77173" -RANDOMIZERBASEHASH: str = "35d010bc148e0ea0ee68e81e330223f1" +RANDOMIZERBASEHASH: str = "8704fb9b9fa4fad52d4d2f9a95fb5360" ROM_PLAYER_LIMIT: int = 255 import io diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index a3b1dfa658..8baeeb6dc2 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -345,42 +345,43 @@ class ALTTPWorld(World): def create_regions(self): player = self.player - world = self.multiworld + multiworld = self.multiworld - if world.mode[player] != 'inverted': - create_regions(world, player) + if multiworld.mode[player] != 'inverted': + create_regions(multiworld, player) else: - create_inverted_regions(world, player) - create_shops(world, player) + create_inverted_regions(multiworld, player) + create_shops(multiworld, player) self.create_dungeons() - if world.glitches_required[player] not in ["no_glitches", "minor_glitches"] and world.entrance_shuffle[player] in \ - {"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"}: - world.fix_fake_world[player] = False + if (multiworld.glitches_required[player] not in ["no_glitches", "minor_glitches"] and + multiworld.entrance_shuffle[player] in [ + "vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]): + multiworld.fix_fake_world[player] = False # seeded entrance shuffle - old_random = world.random - world.random = random.Random(self.er_seed) + old_random = multiworld.random + multiworld.random = random.Random(self.er_seed) - if world.mode[player] != 'inverted': - link_entrances(world, player) - mark_light_world_regions(world, player) + if multiworld.mode[player] != 'inverted': + link_entrances(multiworld, player) + mark_light_world_regions(multiworld, player) for region_name, entrance_name in indirect_connections_not_inverted.items(): - world.register_indirect_condition(world.get_region(region_name, player), - world.get_entrance(entrance_name, player)) + multiworld.register_indirect_condition(multiworld.get_region(region_name, player), + multiworld.get_entrance(entrance_name, player)) else: - link_inverted_entrances(world, player) - mark_dark_world_regions(world, player) + link_inverted_entrances(multiworld, player) + mark_dark_world_regions(multiworld, player) for region_name, entrance_name in indirect_connections_inverted.items(): - world.register_indirect_condition(world.get_region(region_name, player), - world.get_entrance(entrance_name, player)) + multiworld.register_indirect_condition(multiworld.get_region(region_name, player), + multiworld.get_entrance(entrance_name, player)) - world.random = old_random - plando_connect(world, player) + multiworld.random = old_random + plando_connect(multiworld, player) for region_name, entrance_name in indirect_connections.items(): - world.register_indirect_condition(world.get_region(region_name, player), - world.get_entrance(entrance_name, player)) + multiworld.register_indirect_condition(multiworld.get_region(region_name, player), + multiworld.get_entrance(entrance_name, player)) def collect_item(self, state: CollectionState, item: Item, remove=False): item_name = item.name diff --git a/worlds/alttp/docs/multiworld_de.md b/worlds/alttp/docs/multiworld_de.md index 8ccd1a87a6..c8c802d750 100644 --- a/worlds/alttp/docs/multiworld_de.md +++ b/worlds/alttp/docs/multiworld_de.md @@ -47,12 +47,12 @@ wählen können! ### Wo bekomme ich so eine YAML-Datei her? -Die [Player Settings](/games/A Link to the Past/player-settings) Seite auf der Website ermöglicht das einfache Erstellen +Die [Player Options](/games/A Link to the Past/player-options) Seite auf der Website ermöglicht das einfache Erstellen und Herunterladen deiner eigenen `yaml` Datei. Drei verschiedene Voreinstellungen können dort gespeichert werden. ### Deine YAML-Datei ist gewichtet! -Die **Player Settings** Seite hat eine Menge Optionen, die man per Schieber einstellen kann. Das ermöglicht es, +Die **Player Options** Seite hat eine Menge Optionen, die man per Schieber einstellen kann. Das ermöglicht es, verschiedene Optionen mit unterschiedlichen Wahrscheinlichkeiten in einer Kategorie ausgewürfelt zu werden Als Beispiel kann man sich die Option "Map Shuffle" als einen Eimer mit Zetteln zur Abstimmung Vorstellen. So kann man diff --git a/worlds/alttp/docs/multiworld_es.md b/worlds/alttp/docs/multiworld_es.md index 37aeda2a63..0c907b1f7a 100644 --- a/worlds/alttp/docs/multiworld_es.md +++ b/worlds/alttp/docs/multiworld_es.md @@ -59,7 +59,7 @@ de multiworld puede tener diferentes opciones. ### Donde puedo obtener un fichero YAML? -La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-settings)" en el sitio web te permite configurar tu +La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-options)" en el sitio web te permite configurar tu configuración personal y descargar un fichero "YAML". ### Configuración YAML avanzada @@ -86,7 +86,7 @@ Si quieres validar que tu fichero YAML para asegurarte que funciona correctament ## Generar una partida para un jugador -1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-settings), configura tus opciones, haz +1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-options), configura tus opciones, haz click en el boton "Generate game". 2. Se te redigirá a una pagina "Seed Info", donde puedes descargar tu archivo de parche. 3. Haz doble click en tu fichero de parche, y el emulador debería ejecutar tu juego automáticamente. Como el Cliente no diff --git a/worlds/alttp/docs/multiworld_fr.md b/worlds/alttp/docs/multiworld_fr.md index 078a270f08..f2d55787f7 100644 --- a/worlds/alttp/docs/multiworld_fr.md +++ b/worlds/alttp/docs/multiworld_fr.md @@ -60,7 +60,7 @@ peuvent avoir différentes options. ### Où est-ce que j'obtiens un fichier YAML ? -La page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-settings) vous permet de configurer vos +La page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options) vous permet de configurer vos paramètres personnels et de les exporter vers un fichier YAML. ### Configuration avancée du fichier YAML @@ -87,7 +87,7 @@ Si vous voulez valider votre fichier YAML pour être sûr qu'il fonctionne, vous ## Générer une partie pour un joueur -1. Aller sur la page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-settings), configurez vos options, +1. Aller sur la page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options), configurez vos options, et cliquez sur le bouton "Generate Game". 2. Il vous sera alors présenté une page d'informations sur la seed, où vous pourrez télécharger votre patch. 3. Double-cliquez sur le patch et l'émulateur devrait se lancer automatiquement avec la seed. Etant donné que le client @@ -207,4 +207,4 @@ Le logiciel recommandé pour l'auto-tracking actuellement est 3. Sélectionnez votre appareil SNES dans la liste déroulante. 4. Si vous voulez tracquer les petites clés ainsi que les objets des donjons, cochez la case **Race Illegal Tracking** 5. Cliquez sur le bouton **Start Autotracking** -6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire \ No newline at end of file +6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire diff --git a/worlds/archipidle/docs/guide_en.md b/worlds/archipidle/docs/guide_en.md index f9d7f08aab..c450ec421d 100644 --- a/worlds/archipidle/docs/guide_en.md +++ b/worlds/archipidle/docs/guide_en.md @@ -8,5 +8,5 @@ [ArchipIDLE GitHub Releases Page](https://github.com/ArchipelagoMW/archipidle/releases) 3. Enter the server address in the `Server Address` field and press enter 4. Enter your slot name when prompted. This should be the same as the `name` you entered on the - setting page above, or the `name` field in your yaml file. + options page above, or the `name` field in your yaml file. 5. Click the "Begin!" button. diff --git a/worlds/archipidle/docs/guide_fr.md b/worlds/archipidle/docs/guide_fr.md index c3842ed7db..dc0c8af321 100644 --- a/worlds/archipidle/docs/guide_fr.md +++ b/worlds/archipidle/docs/guide_fr.md @@ -1,11 +1,10 @@ # Guide de configuration d'ArchipIdle ## Rejoindre une partie MultiWorld -1. Générez un fichier `.yaml` à partir de la [page des paramètres du lecteur ArchipIDLE](/games/ArchipIDLE/player-settings) +1. Générez un fichier `.yaml` à partir de la [page des paramètres du lecteur ArchipIDLE](/games/ArchipIDLE/player-options) 2. Ouvrez le client ArchipIDLE dans votre navigateur Web en : - - Accédez au [Client ArchipIDLE](http://idle.multiworld.link) - - Téléchargez le client et exécutez-le localement à partir du - [Page des versions d'ArchipIDLE GitHub](https://github.com/ArchipelagoMW/archipidle/releases) + - Accédez au [Client ArchipIDLE](http://idle.multiworld.link) + - Téléchargez le client et exécutez-le localement à partir du [Page des versions d'ArchipIDLE GitHub](https://github.com/ArchipelagoMW/archipidle/releases) 3. Entrez l'adresse du serveur dans le champ `Server Address` et appuyez sur Entrée 4. Entrez votre nom d'emplacement lorsque vous y êtes invité. Il doit être le même que le `name` que vous avez saisi sur le page de configuration ci-dessus, ou le champ `name` dans votre fichier yaml. diff --git a/worlds/dark_souls_3/docs/setup_fr.md b/worlds/dark_souls_3/docs/setup_fr.md index 769d331bb9..ea4d8f8186 100644 --- a/worlds/dark_souls_3/docs/setup_fr.md +++ b/worlds/dark_souls_3/docs/setup_fr.md @@ -29,5 +29,5 @@ placez-le à la racine du jeu (ex: "SteamLibrary\steamapps\common\DARK SOULS III ## Où trouver le fichier de configuration ? -La [Page de configuration](/games/Dark%20Souls%20III/player-settings) sur le site vous permez de configurer vos +La [Page de configuration](/games/Dark%20Souls%20III/player-options) sur le site vous permez de configurer vos paramètres et de les exporter sous la forme d'un fichier. diff --git a/worlds/dlcquest/docs/fr_DLCQuest.md b/worlds/dlcquest/docs/fr_DLCQuest.md index 95a8048dfe..25f2d72816 100644 --- a/worlds/dlcquest/docs/fr_DLCQuest.md +++ b/worlds/dlcquest/docs/fr_DLCQuest.md @@ -2,7 +2,7 @@ ## Où se trouve la page des paramètres ? -La [page des paramètres du joueur pour ce jeu](../player-settings) contient tous les paramètres dont vous avez besoin pour configurer et exporter le fichier. +La [page des paramètres du joueur pour ce jeu](../player-options) contient tous les paramètres dont vous avez besoin pour configurer et exporter le fichier. ## Quel est l'effet de la randomisation sur ce jeu ? @@ -46,4 +46,4 @@ Il y a aussi de nouveaux objets pièges, utilisés comme substituts, basés sur Chaque fois qu'un objet est reçu en ligne, une notification apparaît à l'écran pour en informer le joueur. Certains objets sont accompagnés d'une animation ou d'une scène qui se déroule immédiatement après leur réception. -Les objets reçus hors ligne ne sont pas accompagnés d'une animation ou d'une scène, et sont simplement activés lors de la connexion. \ No newline at end of file +Les objets reçus hors ligne ne sont pas accompagnés d'une animation ou d'une scène, et sont simplement activés lors de la connexion. diff --git a/worlds/dlcquest/docs/setup_fr.md b/worlds/dlcquest/docs/setup_fr.md index 78c69eb5a7..e4b431215d 100644 --- a/worlds/dlcquest/docs/setup_fr.md +++ b/worlds/dlcquest/docs/setup_fr.md @@ -18,7 +18,7 @@ Voir le guide d'Archipelago sur la mise en place d'un YAML de base : [Basic Mult ### Où puis-je obtenir un fichier YAML ? -Vous pouvez personnaliser vos paramètres en visitant la [page des paramètres du joueur DLC Quest] (/games/DLCQuest/player-settings). +Vous pouvez personnaliser vos paramètres en visitant la [page des paramètres du joueur DLC Quest](/games/DLCQuest/player-options). ## Rejoindre une partie multi-monde @@ -52,4 +52,4 @@ Vous pouvez personnaliser vos paramètres en visitant la [page des paramètres d Vous ne pouvez pas envoyer de commandes au serveur ou discuter avec les autres joueurs depuis DLC Quest, car le jeu ne dispose pas d'un moyen approprié pour saisir du texte. Vous pouvez suivre l'activité du serveur dans votre console BepInEx, car les messages de chat d'Archipelago y seront affichés. -Vous devrez utiliser [Archipelago Text Client] (https://github.com/ArchipelagoMW/Archipelago/releases) si vous voulez envoyer des commandes. \ No newline at end of file +Vous devrez utiliser [Archipelago Text Client] (https://github.com/ArchipelagoMW/Archipelago/releases) si vous voulez envoyer des commandes. diff --git a/worlds/generic/docs/advanced_settings_en.md b/worlds/generic/docs/advanced_settings_en.md index 6d5e20462f..8e1b1cdb46 100644 --- a/worlds/generic/docs/advanced_settings_en.md +++ b/worlds/generic/docs/advanced_settings_en.md @@ -2,27 +2,28 @@ This guide covers more the more advanced options available in YAML files. This guide is intended for the user who plans to edit their YAML file manually. This guide should take about 10 minutes to read. -If you would like to generate a basic, fully playable YAML without editing a file, then visit the settings page for the +If you would like to generate a basic, fully playable YAML without editing a file, then visit the options page for the game you intend to play. The weighted settings page can also handle most of the advanced settings discussed here. -The settings page can be found on the supported games page, just click the "Settings Page" link under the name of the -game you would like. +The options page can be found on the supported games page, just click the "Options Page" link under the name of the +game you would like. + * Supported games page: [Archipelago Games List](/games) * Weighted settings page: [Archipelago Weighted Settings](/weighted-settings) -Clicking on the "Export Settings" button at the bottom-left will provide you with a pre-filled YAML with your options. -The player settings page also has a link to download a full template file for that game which will have every option +Clicking on the "Export Options" button at the bottom-left will provide you with a pre-filled YAML with your options. +The player options page also has a link to download a full template file for that game which will have every option possible for the game including some that don't display correctly on the site. ## YAML Overview The Archipelago system generates games using player configuration files as input. These are going to be YAML files and -each world will have one of these containing their custom settings for the game that world will play. +each world will have one of these containing their custom options for the game that world will play. ## YAML Formatting YAML files are a format of human-readable config files. The basic syntax of a yaml file will have a `root` node and then -different levels of `nested` nodes that the generator reads in order to determine your settings. +different levels of `nested` nodes that the generator reads in order to determine your options. To nest text, the correct syntax is to indent **two spaces over** from its root option. A YAML file can be edited with whatever text editor you choose to use though I personally recommend that you use Sublime Text. Sublime text @@ -53,13 +54,13 @@ so `option_one_setting_one` is guaranteed to occur. For `nested_option_two`, `option_two_setting_one` will be rolled 14 times and `option_two_setting_two` will be rolled 43 times against each other. This means `option_two_setting_two` will be more likely to occur, but it isn't guaranteed, -adding more randomness and "mystery" to your settings. Every configurable setting supports weights. +adding more randomness and "mystery" to your options. Every configurable setting supports weights. ## Root Options Currently, there are only a few options that are root options. Everything else should be nested within one of these root options or in some cases nested within other nested options. The only options that should exist in root -are `description`, `name`, `game`, `requires`, and the name of the games you want settings for. +are `description`, `name`, `game`, `requires`, and the name of the games you want options for. * `description` is ignored by the generator and is simply a good way for you to organize if you have multiple files using this to detail the intention of the file. @@ -79,15 +80,15 @@ are `description`, `name`, `game`, `requires`, and the name of the games you wan * `requires` details different requirements from the generator for the YAML to work as you expect it to. Generally this is good for detailing the version of Archipelago this YAML was prepared for as, if it is rolled on an older version, - settings may be missing and as such it will not work as expected. If any plando is used in the file then requiring it + options may be missing and as such it will not work as expected. If any plando is used in the file then requiring it here to ensure it will be used is good practice. ## Game Options -One of your root settings will be the name of the game you would like to populate with settings. Since it is possible to +One of your root options will be the name of the game you would like to populate with options. Since it is possible to give a weight to any option, it is possible to have one file that can generate a seed for you where you don't know which game you'll play. For these cases you'll want to fill the game options for every game that can be rolled by these -settings. If a game can be rolled it **must** have a settings section even if it is empty. +settings. If a game can be rolled it **must** have an options section even if it is empty. ### Universal Game Options diff --git a/worlds/generic/docs/triggers_en.md b/worlds/generic/docs/triggers_en.md index dc5cf5c51e..73cca66543 100644 --- a/worlds/generic/docs/triggers_en.md +++ b/worlds/generic/docs/triggers_en.md @@ -6,7 +6,7 @@ about 5 minutes to read. ## What are triggers? -Triggers allow you to customize your game settings by allowing you to define one or many options which only occur under +Triggers allow you to customize your game options by allowing you to define one or many options which only occur under specific conditions. These are essentially "if, then" statements for options in your game. A good example of what you can do with triggers is the [custom mercenary mode YAML ](https://github.com/alwaysintreble/Archipelago-yaml-dump/blob/main/Snippets/Mercenary%20Mode%20Snippet.yaml) that was @@ -148,4 +148,4 @@ In this example, if the `start_location` option rolls `landing_site`, only a sta If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph Ball. Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key will -replace that value within the dict. \ No newline at end of file +replace that value within the dict. diff --git a/worlds/hk/GodhomeData.py b/worlds/hk/GodhomeData.py new file mode 100644 index 0000000000..6e9d77f4dc --- /dev/null +++ b/worlds/hk/GodhomeData.py @@ -0,0 +1,55 @@ +from functools import partial + + +godhome_event_names = ["Godhome_Flower_Quest", "Defeated_Pantheon_5", "GG_Atrium_Roof", "Defeated_Pantheon_1", "Defeated_Pantheon_2", "Defeated_Pantheon_3", "Opened_Pantheon_4", "Defeated_Pantheon_4", "GG_Atrium", "Hit_Pantheon_5_Unlock_Orb", "GG_Workshop", "Can_Damage_Crystal_Guardian", 'Defeated_Any_Soul_Warrior', "Defeated_Colosseum_3", "COMBAT[Radiance]", "COMBAT[Pantheon_1]", "COMBAT[Pantheon_2]", "COMBAT[Pantheon_3]", "COMBAT[Pantheon_4]", "COMBAT[Pantheon_5]", "COMBAT[Colosseum_3]", 'Warp-Junk_Pit_to_Godhome', 'Bench-Godhome_Atrium', 'Bench-Hall_of_Gods', "GODTUNERUNLOCK", "GG_Waterways", "Warp-Godhome_to_Junk_Pit", "NAILCOMBAT", "BOSS", "AERIALMINIBOSS"] + + +def set_godhome_rules(hk_world, hk_set_rule): + player = hk_world.player + fn = partial(hk_set_rule, hk_world) + + required_events = { + "Godhome_Flower_Quest": lambda state: state.count('Defeated_Pantheon_5', player) and state.count('Room_Mansion[left1]', player) and state.count('Fungus3_49[right1]', player), + + "Defeated_Pantheon_5": lambda state: state.has('GG_Atrium_Roof', player) and state.has('WINGS', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) and ((state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player) and state.has('Defeated_Pantheon_4', player) and state.has('COMBAT[Radiance]', player))), + "GG_Atrium_Roof": lambda state: state.has('GG_Atrium', player) and state.has('Hit_Pantheon_5_Unlock_Orb', player) and state.has('LEFTCLAW', player), + + "Defeated_Pantheon_1": lambda state: state.has('GG_Atrium', player) and ((state.has('Defeated_Gruz_Mother', player) and state.has('Defeated_False_Knight', player) and (state.has('Fungus1_29[left1]', player) or state.has('Fungus1_29[right1]', player)) and state.has('Defeated_Hornet_1', player) and state.has('Defeated_Gorb', player) and state.has('Defeated_Dung_Defender', player) and state.has('Defeated_Any_Soul_Warrior', player) and state.has('Defeated_Brooding_Mawlek', player))), + "Defeated_Pantheon_2": lambda state: state.has('GG_Atrium', player) and ((state.has('Defeated_Xero', player) and state.has('Defeated_Crystal_Guardian', player) and state.has('Defeated_Soul_Master', player) and state.has('Defeated_Colosseum_2', player) and state.has('Defeated_Mantis_Lords', player) and state.has('Defeated_Marmu', player) and state.has('Defeated_Nosk', player) and state.has('Defeated_Flukemarm', player) and state.has('Defeated_Broken_Vessel', player))), + "Defeated_Pantheon_3": lambda state: state.has('GG_Atrium', player) and ((state.has('Defeated_Hive_Knight', player) and state.has('Defeated_Elder_Hu', player) and state.has('Defeated_Collector', player) and state.has('Defeated_Colosseum_2', player) and state.has('Defeated_Grimm', player) and state.has('Defeated_Galien', player) and state.has('Defeated_Uumuu', player) and state.has('Defeated_Hornet_2', player))), + "Opened_Pantheon_4": lambda state: state.has('GG_Atrium', player) and (state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player)), + "Defeated_Pantheon_4": lambda state: state.has('GG_Atrium', player) and state.has('Opened_Pantheon_4', player) and ((state.has('Defeated_Enraged_Guardian', player) and state.has('Defeated_Broken_Vessel', player) and state.has('Defeated_No_Eyes', player) and state.has('Defeated_Traitor_Lord', player) and state.has('Defeated_Dung_Defender', player) and state.has('Defeated_False_Knight', player) and state.has('Defeated_Markoth', player) and state.has('Defeated_Watcher_Knights', player) and state.has('Defeated_Soul_Master', player))), + "GG_Atrium": lambda state: state.has('Warp-Junk_Pit_to_Godhome', player) and (state.has('RIGHTCLAW', player) or state.has('WINGS', player) or state.has('LEFTCLAW', player) and state.has('RIGHTSUPERDASH', player)) or state.has('GG_Workshop', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player) and state.has('WINGS', player)) or state.has('Bench-Godhome_Atrium', player), + "Hit_Pantheon_5_Unlock_Orb": lambda state: state.has('GG_Atrium', player) and state.has('WINGS', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) and (((state.has('Queen_Fragment', player) and state.has('King_Fragment', player) and state.has('Void_Heart', player)) and state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player) and state.has('Defeated_Pantheon_4', player))), + "GG_Workshop": lambda state: state.has('GG_Atrium', player) or state.has('Bench-Hall_of_Gods', player), + "Can_Damage_Crystal_Guardian": lambda state: state.has('UPSLASH', player) or state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('CYCLONE', player) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')) and (state.has('CYCLONE', player) or state.has('Great_Slash', player)) and (state.has('DREAMNAIL', player) and (state.has('SPELLS', player) or state.has('FOCUS', player) and state.has('Spore_Shroom', player) or state.has('Glowing_Womb', player)) or state.has('Weaversong', player)), + 'Defeated_Any_Soul_Warrior': lambda state: state.has('Defeated_Sanctum_Warrior', player) or state.has('Defeated_Elegant_Warrior', player) or state.has('Room_Colosseum_01[left1]', player) and state.has('Defeated_Colosseum_3', player), + "Defeated_Colosseum_3": lambda state: state.has('Room_Colosseum_01[left1]', player) and state.has('Can_Replenish_Geo', player) and ((state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) or ((state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')) and state.has('WINGS', player))) and state.has('COMBAT[Colosseum_3]', player), + + # MACROS + "COMBAT[Radiance]": lambda state: (state.has('LEFTDASH', player) and state.has('RIGHTDASH', player)) and ((((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('LEFTDASH', player)) and ((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('RIGHTDASH', player))) or state.has('QUAKE', player)) and (state.count('FIREBALL', player) > 1 and state.has('UPSLASH', player) or state.count('SCREAM', player) > 1 and state.has('UPSLASH', player) or state._hk_option(player, 'RemoveSpellUpgrades') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('UPSLASH', player) or (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_1]": lambda state: state.has('AERIALMINIBOSS', player) and state.count('SPELLS', player) > 1 and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_2]": lambda state: state.has('AERIALMINIBOSS', player) and state.count('SPELLS', player) > 1 and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))) and state.has('Can_Damage_Crystal_Guardian', player), + "COMBAT[Pantheon_3]": lambda state: state.has('AERIALMINIBOSS', player) and state.count('SPELLS', player) > 1 and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_4]": lambda state: state.has('AERIALMINIBOSS', player) and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))) and state.has('Can_Damage_Crystal_Guardian', player) and (state.has('LEFTDASH', player) and state.has('RIGHTDASH', player)) and ((((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('LEFTDASH', player)) and ((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('RIGHTDASH', player))) or state.has('QUAKE', player)) and (state.count('FIREBALL', player) > 1 and state.has('UPSLASH', player) or state.count('SCREAM', player) > 1 and state.has('UPSLASH', player) or state._hk_option(player, 'RemoveSpellUpgrades') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('UPSLASH', player) or (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Pantheon_5]": lambda state: state.has('AERIALMINIBOSS', player) and state.has('FOCUS', player) and state.has('Can_Damage_Crystal_Guardian', player) and (state.has('LEFTDASH', player) and state.has('RIGHTDASH', player)) and ((((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('LEFTDASH', player)) and ((state.count('LEFTDASH', player) > 1 or state.count('RIGHTDASH', player) > 1) and state.has('RIGHTDASH', player))) or state.has('QUAKE', player)) and (state.count('FIREBALL', player) > 1 and state.has('UPSLASH', player) or state.count('SCREAM', player) > 1 and state.has('UPSLASH', player) or state._hk_option(player, 'RemoveSpellUpgrades') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('UPSLASH', player) or (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + "COMBAT[Colosseum_3]": lambda state: state.has('BOSS', player) and (state.has('FOCUS', player) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat'))), + + # MISC + 'Warp-Junk_Pit_to_Godhome': lambda state: state.has('GG_Waterways', player) and state.has('GODTUNERUNLOCK', player) and state.has('DREAMNAIL', player), + 'Bench-Godhome_Atrium': lambda state: state.has('GG_Atrium', player) and (state.has('RIGHTCLAW', player) and (state.has('RIGHTDASH', player) or state.has('LEFTCLAW', player) and state.has('RIGHTSUPERDASH', player) or state.has('WINGS', player)) or state.has('LEFTCLAW', player) and state.has('WINGS', player)), + 'Bench-Hall_of_Gods': lambda state: state.has('GG_Workshop', player) and ((state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player))), + + "GODTUNERUNLOCK": lambda state: state.count('SIMPLE', player) > 3, + "GG_Waterways": lambda state: state.has('GG_Waterways[door1]', player) or state.has('GG_Waterways[right1]', player) and (state.has('LEFTSUPERDASH', player) or state.has('SWIM', player)) or state.has('Warp-Godhome_to_Junk_Pit', player), + "Warp-Godhome_to_Junk_Pit": lambda state: state.has('Warp-Junk_Pit_to_Godhome', player) or state.has('GG_Atrium', player), + + # COMBAT MACROS + "NAILCOMBAT": lambda state: (state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player) or state._hk_option(player, 'ProficientCombat') and (state.has('CYCLONE', player) or state.has('Great_Slash', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')), + "BOSS": lambda state: state.count('SPELLS', player) > 1 and ((state.has('LEFTDASH', player) or state.has('RIGHTDASH', player)) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player)) or state._hk_option(player, 'ProficientCombat') and state.has('NAILCOMBAT', player)), + "AERIALMINIBOSS": lambda state: (state.has('FIREBALL', player) or state.has('SCREAM', player)) and (state.has('LEFTDASH', player) or state.has('RIGHTDASH', player)) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player)) or state._hk_option(player, 'ProficientCombat') and (state.has('FIREBALL', player) or state.has('SCREAM', player)) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player)) or (state._hk_option(player, 'DifficultSkips') and state._hk_option(player, 'ProficientCombat')) and ((state.has('LEFTSLASH', player) or state.has('RIGHTSLASH', player)) or state.has('UPSLASH', player) or state.has('CYCLONE', player) or state.has('Great_Slash', player)), + + } + + for item, rule in required_events.items(): + fn(item, rule) diff --git a/worlds/hk/Items.py b/worlds/hk/Items.py index 72878dfc71..0d4ab3d55f 100644 --- a/worlds/hk/Items.py +++ b/worlds/hk/Items.py @@ -1,5 +1,6 @@ from typing import Dict, Set, NamedTuple from .ExtractedData import items, logic_items, item_effects +from .GodhomeData import godhome_event_names item_table = {} @@ -14,6 +15,9 @@ for i, (item_name, item_type) in enumerate(items.items(), start=0x1000000): item_table[item_name] = HKItemData(advancement=item_name in logic_items or item_name in item_effects, id=i, type=item_type) +for item_name in godhome_event_names: + item_table[item_name] = HKItemData(advancement=True, id=None, type=None) + lookup_id_to_name: Dict[int, str] = {data.id: item_name for item_name, data in item_table.items()} lookup_type_to_names: Dict[str, Set[str]] = {} for item, item_data in item_table.items(): diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index 70c7c16896..f7b4420c74 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -397,8 +397,8 @@ class Goal(Choice): option_hollowknight = 1 option_siblings = 2 option_radiance = 3 - # Client support exists for this, but logic is a nightmare - # option_godhome = 4 + option_godhome = 4 + option_godhome_flower = 5 default = 0 diff --git a/worlds/hk/Rules.py b/worlds/hk/Rules.py index 2dc512eca7..a3c7e13cf0 100644 --- a/worlds/hk/Rules.py +++ b/worlds/hk/Rules.py @@ -1,6 +1,7 @@ from ..generic.Rules import set_rule, add_rule from ..AutoWorld import World from .GeneratedRules import set_generated_rules +from .GodhomeData import set_godhome_rules from typing import NamedTuple @@ -39,6 +40,7 @@ def hk_set_rule(hk_world: World, location: str, rule): def set_rules(hk_world: World): player = hk_world.player set_generated_rules(hk_world, hk_set_rule) + set_godhome_rules(hk_world, hk_set_rule) # Shop costs for location in hk_world.multiworld.get_locations(player): diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 25337598ec..4057cded9a 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -307,6 +307,12 @@ class HKWorld(World): randomized = True _add("Elevator_Pass", "Elevator_Pass", randomized) + # check for any goal that godhome events are relevant to + if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]: + from .GodhomeData import godhome_event_names + for item_name in godhome_event_names: + _add(item_name, item_name, False) + for shop, locations in self.created_multi_locations.items(): for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value): loc = self.create_location(shop) @@ -431,6 +437,10 @@ class HKWorld(World): world.completion_condition[player] = lambda state: state._hk_siblings_ending(player) elif goal == Goal.option_radiance: world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player) + elif goal == Goal.option_godhome: + world.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) + elif goal == Goal.option_godhome_flower: + world.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) else: # Any goal world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player) diff --git a/worlds/lufia2ac/Options.py b/worlds/lufia2ac/Options.py index 5f33d0bd5d..1b3a39ddeb 100644 --- a/worlds/lufia2ac/Options.py +++ b/worlds/lufia2ac/Options.py @@ -593,6 +593,20 @@ class HealingFloorChance(Range): default = 16 +class InactiveExpGain(Choice): + """The rate at which characters not currently in the active party gain EXP. + + Supported values: disabled, half, full + Default value: disabled (same as in an unmodified game) + """ + + display_name = "Inactive character EXP gain" + option_disabled = 0 + option_half = 50 + option_full = 100 + default = option_disabled + + class InitialFloor(Range): """The initial floor, where you begin your journey. @@ -805,7 +819,7 @@ class ShufflePartyMembers(Toggle): false — all 6 optional party members are present in the cafe and can be recruited right away true — only Maxim is available from the start; 6 new "items" are added to your pool and shuffled into the multiworld; when one of these items is found, the corresponding party member is unlocked for you to use. - While cave diving, you can add newly unlocked ones to your party by using the character items from the inventory + While cave diving, you can add or remove unlocked party members by using the character items from the inventory Default value: false (same as in an unmodified game) """ @@ -838,6 +852,7 @@ class L2ACOptions(PerGameCommonOptions): goal: Goal gold_modifier: GoldModifier healing_floor_chance: HealingFloorChance + inactive_exp_gain: InactiveExpGain initial_floor: InitialFloor iris_floor_chance: IrisFloorChance iris_treasures_required: IrisTreasuresRequired diff --git a/worlds/lufia2ac/__init__.py b/worlds/lufia2ac/__init__.py index 9bd436fa0d..561429c825 100644 --- a/worlds/lufia2ac/__init__.py +++ b/worlds/lufia2ac/__init__.py @@ -232,6 +232,7 @@ class L2ACWorld(World): rom_bytearray[0x280018:0x280018 + 1] = self.o.shuffle_party_members.unlock.to_bytes(1, "little") rom_bytearray[0x280019:0x280019 + 1] = self.o.shuffle_capsule_monsters.unlock.to_bytes(1, "little") rom_bytearray[0x28001A:0x28001A + 1] = self.o.shop_interval.value.to_bytes(1, "little") + rom_bytearray[0x28001B:0x28001B + 1] = self.o.inactive_exp_gain.value.to_bytes(1, "little") rom_bytearray[0x280030:0x280030 + 1] = self.o.goal.value.to_bytes(1, "little") rom_bytearray[0x28003D:0x28003D + 1] = self.o.death_link.value.to_bytes(1, "little") rom_bytearray[0x281200:0x281200 + 470] = self.get_capsule_cravings_table() diff --git a/worlds/lufia2ac/basepatch/basepatch.asm b/worlds/lufia2ac/basepatch/basepatch.asm index f9c48a5fec..f25d4deada 100644 --- a/worlds/lufia2ac/basepatch/basepatch.asm +++ b/worlds/lufia2ac/basepatch/basepatch.asm @@ -309,6 +309,12 @@ org $8EFD2E ; unused region at the end of bank $8E DB $1E,$0B,$01,$2B,$05,$1A,$05,$00 ; add dekar DB $1E,$0B,$01,$2B,$04,$1A,$06,$00 ; add tia DB $1E,$0B,$01,$2B,$06,$1A,$07,$00 ; add lexis + DB $1F,$0B,$01,$2C,$01,$1B,$02,$00 ; remove selan + DB $1F,$0B,$01,$2C,$02,$1B,$03,$00 ; remove guy + DB $1F,$0B,$01,$2C,$03,$1B,$04,$00 ; remove arty + DB $1F,$0B,$01,$2C,$05,$1B,$05,$00 ; remove dekar + DB $1F,$0B,$01,$2C,$04,$1B,$06,$00 ; remove tia + DB $1F,$0B,$01,$2C,$06,$1B,$07,$00 ; remove lexis pullpc SpecialItemUse: @@ -328,11 +334,15 @@ SpecialItemUse: SEP #$20 LDA $8ED8C7,X ; load predefined bitmask with a single bit set BIT $077E ; check against EV flags $02 to $07 (party member flags) - BNE + ; abort if character already present - LDA $07A9 ; load EV register $11 (party counter) + BEQ ++ + LDA.b #$30 ; character already present; modify pointer to point to L2SASM leave script + ADC $09B7 + STA $09B7 + BRA +++ +++: LDA $07A9 ; character not present; load EV register $0B (party counter) CMP.b #$03 BPL + ; abort if party full - LDA.b #$8E ++++ LDA.b #$8E STA $09B9 PHK PEA ++ @@ -340,7 +350,6 @@ SpecialItemUse: JML $83BB76 ; initialize parser variables ++: NOP JSL $809CB8 ; call L2SASM parser - JSL $81F034 ; consume the item TSX INX #13 TXS @@ -490,6 +499,73 @@ pullpc +; allow inactive characters to gain exp +pushpc +org $81DADD + ; DB=$81, x=0, m=1 + NOP ; overwrites BNE $81DAE2 : JMP $DBED + JML HandleActiveExp +AwardExp: + ; isolate exp distribution into a subroutine, to be reused for both active party members and inactive characters +org $81DAE9 + NOP #2 ; overwrites JMP $DBBD + RTL +org $81DB42 + NOP #2 ; overwrites JMP $DBBD + RTL +org $81DD11 + ; DB=$81, x=0, m=1 + JSL HandleInactiveExp ; overwrites LDA $0A8A : CLC +pullpc + +HandleActiveExp: + BNE + ; (overwritten instruction; modified) check if statblock not empty + JML $81DBED ; (overwritten instruction; modified) abort ++: JSL AwardExp ; award exp (X=statblock pointer, Y=position in battle order, $00=position in menu order) + JML $81DBBD ; (overwritten instruction; modified) continue to next level text + +HandleInactiveExp: + LDA $F0201B ; load inactive exp gain rate + BEQ + ; zero gain; skip everything + CMP.b #$64 + BCS ++ ; full gain + LSR $1607 + ROR $1606 ; half gain + ROR $1605 +++: LDY.w #$0000 ; start looping through all characters +-: TDC + TYA + LDX.w #$0003 ; start looping through active party +--: CMP $0A7B,X + BEQ ++ ; skip if character in active party + DEX + BPL -- ; continue looping through active party + STA $153D ; inactive character detected; overwrite character index of 1st slot in party battle order + ASL + TAX + REP #$20 + LDA $859EBA,X ; convert character index to statblock pointer + SEP #$20 + TAX + PHY ; stash character loop index + LDY $0A80 + PHY ; stash 1st (in menu order) party member statblock pointer + STX $0A80 ; overwrite 1st (in menu order) party member statblock pointer + LDY.w #$0000 ; set to use 1st position (in battle order) + STY $00 ; set to use 1st position (in menu order) + JSL AwardExp ; award exp (X=statblock pointer, Y=position in battle order, $00=position in menu order) + PLY ; restore 1st (in menu order) party member statblock pointer + STY $0A80 + PLY ; restore character loop index +++: INY + CPY.w #$0007 + BCC - ; continue looping through all characters ++: LDA $0A8A ; (overwritten instruction) load current gold + CLC ; (overwritten instruction) + RTL + + + ; receive death link pushpc org $83BC91 @@ -1226,6 +1302,7 @@ pullpc ; $F02018 1 party members available ; $F02019 1 capsule monsters available ; $F0201A 1 shop interval +; $F0201B 1 inactive exp gain rate ; $F02030 1 selected goal ; $F02031 1 goal completion: boss ; $F02032 1 goal completion: iris_treasure_hunt diff --git a/worlds/lufia2ac/basepatch/basepatch.bsdiff4 b/worlds/lufia2ac/basepatch/basepatch.bsdiff4 index 664e197c4a..1dfade445e 100644 Binary files a/worlds/lufia2ac/basepatch/basepatch.bsdiff4 and b/worlds/lufia2ac/basepatch/basepatch.bsdiff4 differ diff --git a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md index 1080a77d54..4b5bf3f318 100644 --- a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md +++ b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md @@ -53,8 +53,9 @@ Your Party Leader will hold up the item they received when not in a fight or in - Randomize enemy movement patterns, enemy sprites, and which enemy types can appear at which floor numbers - Option to make shops appear in the cave so that you have a way to spend your hard-earned gold - Option to shuffle your party members and/or capsule monsters into the multiworld, meaning that someone will have to - find them in order to unlock them for you to use. While cave diving, you can add newly unlocked members to your party - by using the character items from your inventory + find them in order to unlock them for you to use. While cave diving, you can add or remove unlocked party members by + using the character items from your inventory. There's also an option to allow inactive characters to gain some EXP, + so that new party members added during a run don't have to start off at a low level ###### Quality of life: diff --git a/worlds/minecraft/docs/minecraft_fr.md b/worlds/minecraft/docs/minecraft_fr.md index e25febba42..31c48151f4 100644 --- a/worlds/minecraft/docs/minecraft_fr.md +++ b/worlds/minecraft/docs/minecraft_fr.md @@ -16,7 +16,7 @@ guide : [Guide de configuration de base de Multiworld](/tutorial/Archipelago/se ### Où puis-je obtenir un fichier YAML ? -Vous pouvez personnaliser vos paramètres Minecraft en allant sur la [page des paramètres de joueur](/games/Minecraft/player-settings) +Vous pouvez personnaliser vos paramètres Minecraft en allant sur la [page des paramètres de joueur](/games/Minecraft/player-options) ## Rejoindre une partie MultiWorld @@ -71,4 +71,4 @@ les liens suivants sont les versions des logiciels que nous utilisons. - [Page des versions du mod Minecraft Archipelago Randomizer] (https://github.com/KonoTyran/Minecraft_AP_Randomizer/releases) - **NE PAS INSTALLER CECI SUR VOTRE CLIENT** - [Amazon Corretto](https://docs.aws.amazon.com/corretto/) - - choisissez la version correspondante et sélectionnez "Téléchargements" sur la gauche \ No newline at end of file + - choisissez la version correspondante et sélectionnez "Téléchargements" sur la gauche diff --git a/worlds/minecraft/docs/minecraft_sv.md b/worlds/minecraft/docs/minecraft_sv.md index e86d293939..fd89d681ee 100644 --- a/worlds/minecraft/docs/minecraft_sv.md +++ b/worlds/minecraft/docs/minecraft_sv.md @@ -103,8 +103,6 @@ shuffle_structures: off: 0 ``` -För mer detaljer om vad varje inställning gör, kolla standardinställningen `PlayerSettings.yaml` som kommer med -Archipelago-installationen. ## Gå med i ett Multivärld-spel diff --git a/worlds/musedash/docs/setup_es.md b/worlds/musedash/docs/setup_es.md index 0d737c26d7..1b16c7af3f 100644 --- a/worlds/musedash/docs/setup_es.md +++ b/worlds/musedash/docs/setup_es.md @@ -2,7 +2,7 @@ ## Enlaces rápidos - [Página Principal](../../../../games/Muse%20Dash/info/en) -- [Página de Configuraciones](../../../../games/Muse%20Dash/player-settings) +- [Página de Configuraciones](../../../../games/Muse%20Dash/player-options) ## Software Requerido @@ -27,7 +27,7 @@ Si todo fue instalado correctamente, un botón aparecerá en la parte inferior derecha del juego una vez abierto, que te permitirá conectarte al servidor de Archipelago. ## Generar un juego MultiWorld -1. Entra a la página de [configuraciones de jugador](/games/Muse%20Dash/player-settings) y configura las opciones del juego a tu gusto. +1. Entra a la página de [configuraciones de jugador](/games/Muse%20Dash/player-options) y configura las opciones del juego a tu gusto. 2. Genera tu archivo YAML y úsalo para generar un juego nuevo en el radomizer - (Instrucciones sobre como generar un juego en Archipelago disponibles en la [guía web de Archipelago en Inglés](/tutorial/Archipelago/setup/en)) diff --git a/worlds/oot/docs/setup_fr.md b/worlds/oot/docs/setup_fr.md index f5915e1878..40b0e8f571 100644 --- a/worlds/oot/docs/setup_fr.md +++ b/worlds/oot/docs/setup_fr.md @@ -46,7 +46,7 @@ guide : [Guide de configuration de base de Multiworld](/tutorial/Archipelago/set ### Où puis-je obtenir un fichier de configuration (.yaml) ? -La page Paramètres du lecteur sur le site Web vous permet de configurer vos paramètres personnels et d'exporter un fichier de configuration depuis eux. Page des paramètres du joueur : [Page des paramètres du joueur d'Ocarina of Time](/games/Ocarina%20of%20Time/player-settings) +La page Paramètres du lecteur sur le site Web vous permet de configurer vos paramètres personnels et d'exporter un fichier de configuration depuis eux. Page des paramètres du joueur : [Page des paramètres du joueur d'Ocarina of Time](/games/Ocarina%20of%20Time/player-options) ### Vérification de votre fichier de configuration @@ -67,4 +67,4 @@ Une fois le client et l'émulateur démarrés, vous devez les connecter. Accéde Pour connecter le client au multiserveur, mettez simplement `:` dans le champ de texte en haut et appuyez sur Entrée (si le serveur utilise un mot de passe, tapez dans le champ de texte inférieur `/connect : [mot de passe]`) -Vous êtes maintenant prêt à commencer votre aventure dans Hyrule. \ No newline at end of file +Vous êtes maintenant prêt à commencer votre aventure dans Hyrule. diff --git a/worlds/pokemon_rb/docs/setup_es.md b/worlds/pokemon_rb/docs/setup_es.md index a6a6aa6ce7..9d87db224b 100644 --- a/worlds/pokemon_rb/docs/setup_es.md +++ b/worlds/pokemon_rb/docs/setup_es.md @@ -51,7 +51,7 @@ opciones. ### ¿Dónde puedo obtener un archivo YAML? -Puedes generar un archivo YAML or descargar su plantilla en la [página de configuración de jugador de Pokémon Red and Blue](/games/Pokemon%20Red%20and%20Blue/player-settings) +Puedes generar un archivo YAML or descargar su plantilla en la [página de configuración de jugador de Pokémon Red and Blue](/games/Pokemon%20Red%20and%20Blue/player-options) Es importante tener en cuenta que la opción `game_version` determina el ROM que será parcheado. Tanto el jugador como la persona que genera (si está generando localmente) necesitarán el archivo del ROM diff --git a/worlds/ror2/__init__.py b/worlds/ror2/__init__.py index 6574a176dc..5afdb797e7 100644 --- a/worlds/ror2/__init__.py +++ b/worlds/ror2/__init__.py @@ -44,8 +44,8 @@ class RiskOfRainWorld(World): } location_name_to_id = item_pickups - data_version = 8 - required_client_version = (0, 4, 4) + data_version = 9 + required_client_version = (0, 4, 5) web = RiskOfWeb() total_revivals: int @@ -91,6 +91,17 @@ class RiskOfRainWorld(World): # only mess with the environments if they are set as items if self.options.goal == "explore": + # check to see if the user doesn't want to use stages, and to figure out what type of stages are being used. + if not self.options.require_stages: + if not self.options.progressive_stages: + self.multiworld.push_precollected(self.multiworld.create_item("Stage 1", self.player)) + self.multiworld.push_precollected(self.multiworld.create_item("Stage 2", self.player)) + self.multiworld.push_precollected(self.multiworld.create_item("Stage 3", self.player)) + self.multiworld.push_precollected(self.multiworld.create_item("Stage 4", self.player)) + else: + for _ in range(4): + self.multiworld.push_precollected(self.multiworld.create_item("Progressive Stage", self.player)) + # figure out all available ordered stages for each tier environment_available_orderedstages_table = environment_vanilla_orderedstages_table if self.options.dlc_sotv: @@ -121,8 +132,12 @@ class RiskOfRainWorld(World): total_locations = self.options.total_locations.value else: # explore mode - # Add Stage items for logic gates - itempool += ["Stage 1", "Stage 2", "Stage 3", "Stage 4"] + + # Add Stage items to the pool + if self.options.require_stages: + itempool += ["Stage 1", "Stage 2", "Stage 3", "Stage 4"] if not self.options.progressive_stages else \ + ["Progressive Stage"] * 4 + total_locations = len( get_locations( chests=self.options.chests_per_stage.value, @@ -206,8 +221,8 @@ class RiskOfRainWorld(World): options_dict = self.options.as_dict("item_pickup_step", "shrine_use_step", "goal", "victory", "total_locations", "chests_per_stage", "shrines_per_stage", "scavengers_per_stage", "scanner_per_stage", "altars_per_stage", "total_revivals", - "start_with_revive", "final_stage_death", "death_link", - casing="camel") + "start_with_revive", "final_stage_death", "death_link", "require_stages", + "progressive_stages", casing="camel") return { **options_dict, "seed": "".join(self.random.choice(string.digits) for _ in range(16)), diff --git a/worlds/ror2/docs/en_Risk of Rain 2.md b/worlds/ror2/docs/en_Risk of Rain 2.md index b2210e348d..651c89a339 100644 --- a/worlds/ror2/docs/en_Risk of Rain 2.md +++ b/worlds/ror2/docs/en_Risk of Rain 2.md @@ -57,7 +57,6 @@ options apply, so each Risk of Rain 2 player slot in the multiworld needs to be for example, have two players trade off hosting and making progress on each other's player slot, but a single co-op instance can't make progress towards multiple player slots in the multiworld. -Explore mode is untested in multiplayer and will likely not work until a later release. ## What Risk of Rain items can appear in other players' worlds? diff --git a/worlds/ror2/items.py b/worlds/ror2/items.py index 449686d04b..3586030816 100644 --- a/worlds/ror2/items.py +++ b/worlds/ror2/items.py @@ -59,7 +59,7 @@ stage_table: Dict[str, RiskOfRainItemData] = { "Stage 2": RiskOfRainItemData("Stage", 2 + stage_offset, ItemClassification.progression), "Stage 3": RiskOfRainItemData("Stage", 3 + stage_offset, ItemClassification.progression), "Stage 4": RiskOfRainItemData("Stage", 4 + stage_offset, ItemClassification.progression), - + "Progressive Stage": RiskOfRainItemData("Stage", 5 + stage_offset, ItemClassification.progression), } item_table = {**upgrade_table, **other_table, **filler_table, **trap_table, **stage_table} diff --git a/worlds/ror2/options.py b/worlds/ror2/options.py index abb8e91da2..066c8c8545 100644 --- a/worlds/ror2/options.py +++ b/worlds/ror2/options.py @@ -151,6 +151,17 @@ class DLC_SOTV(Toggle): display_name = "Enable DLC - SOTV" +class RequireStages(DefaultOnToggle): + """Add Stage items to the pool to block access to the next set of environments.""" + display_name = "Require Stages" + + +class ProgressiveStages(DefaultOnToggle): + """This will convert Stage items to be a progressive item. For example instead of "Stage 2" it would be + "Progressive Stage" """ + display_name = "Progressive Stages" + + class GreenScrap(Range): """Weight of Green Scraps in the item pool. @@ -378,6 +389,8 @@ class ROR2Options(PerGameCommonOptions): start_with_revive: StartWithRevive final_stage_death: FinalStageDeath dlc_sotv: DLC_SOTV + require_stages: RequireStages + progressive_stages: ProgressiveStages death_link: DeathLink item_pickup_step: ItemPickupStep shrine_use_step: ShrineUseStep diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py index b4d5fe68b8..2e6b018f42 100644 --- a/worlds/ror2/rules.py +++ b/worlds/ror2/rules.py @@ -15,6 +15,13 @@ def has_entrance_access_rule(multiworld: MultiWorld, stage: str, region: str, pl entrance.access_rule = rule +def has_stage_access_rule(multiworld: MultiWorld, stage: str, amount: int, region: str, player: int) -> None: + rule = lambda state: state.has(region, player) and \ + (state.has(stage, player) or state.count("Progressive Stage", player) >= amount) + for entrance in multiworld.get_region(region, player).entrances: + entrance.access_rule = rule + + def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: int) -> None: rule = lambda state: state.has_all(items, player) and state.has(region, player) for entrance in multiworld.get_region(region, player).entrances: @@ -43,15 +50,6 @@ def check_location(state, environment: str, player: int, item_number: int, item_ return state.can_reach(f"{environment}: {item_name} {item_number - 1}", "Location", player) -# unlock event to next set of stages -def get_stage_event(multiworld: MultiWorld, player: int, stage_number: int) -> None: - if stage_number == 4: - return - rule = lambda state: state.has(f"Stage {stage_number + 1}", player) - for entrance in multiworld.get_region(f"OrderedStage_{stage_number + 1}", player).entrances: - entrance.access_rule = rule - - def set_rules(ror2_world: "RiskOfRainWorld") -> None: player = ror2_world.player multiworld = ror2_world.multiworld @@ -124,8 +122,7 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None: for newt in range(1, newts + 1): has_location_access_rule(multiworld, environment_name, player, newt, "Newt Altar") if i > 0: - has_entrance_access_rule(multiworld, f"Stage {i}", environment_name, player) - get_stage_event(multiworld, player, i) + has_stage_access_rule(multiworld, f"Stage {i}", i, environment_name, player) if ror2_options.dlc_sotv: for i in range(len(environment_sotv_orderedstages_table)): @@ -143,10 +140,10 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None: for newt in range(1, newts + 1): has_location_access_rule(multiworld, environment_name, player, newt, "Newt Altar") if i > 0: - has_entrance_access_rule(multiworld, f"Stage {i}", environment_name, player) + has_stage_access_rule(multiworld, f"Stage {i}", i, environment_name, player) has_entrance_access_rule(multiworld, "Hidden Realm: A Moment, Fractured", "Hidden Realm: A Moment, Whole", player) - has_entrance_access_rule(multiworld, "Stage 1", "Hidden Realm: Bazaar Between Time", player) + has_stage_access_rule(multiworld, "Stage 1", 1, "Hidden Realm: Bazaar Between Time", player) has_entrance_access_rule(multiworld, "Hidden Realm: Bazaar Between Time", "Void Fields", player) has_entrance_access_rule(multiworld, "Stage 5", "Commencement", player) has_entrance_access_rule(multiworld, "Stage 5", "Hidden Realm: A Moment, Fractured", player) diff --git a/worlds/ror2/test/test_mithrix_goal.py b/worlds/ror2/test/test_mithrix_goal.py index 03b8231178..a52301bef5 100644 --- a/worlds/ror2/test/test_mithrix_goal.py +++ b/worlds/ror2/test/test_mithrix_goal.py @@ -3,7 +3,9 @@ from . import RoR2TestBase class MithrixGoalTest(RoR2TestBase): options = { - "victory": "mithrix" + "victory": "mithrix", + "require_stages": "true", + "progressive_stages": "false" } def test_mithrix(self) -> None: diff --git a/worlds/smz3/Options.py b/worlds/smz3/Options.py index e3d8b8dd10..ada463fa36 100644 --- a/worlds/smz3/Options.py +++ b/worlds/smz3/Options.py @@ -25,7 +25,7 @@ class SwordLocation(Choice): Randomized - The sword can be placed anywhere. Early - The sword will be placed in a location accessible from the start of the game. - Unce assured - The sword will always be placed on Link's Uncle.""" + Uncle - The sword will always be placed on Link's Uncle.""" display_name = "Sword Location" option_Randomized = 0 option_Early = 1 @@ -48,7 +48,7 @@ class MorphLocation(Choice): class Goal(Choice): """This option decides what goal is required to finish the randomizer. - Defeat Ganon and Mother Brain - Find the required crystals and boss tokens kill both bosses. + Defeat Ganon and Mother Brain - Find the required crystals and boss tokens to kill both bosses. Fast Ganon and Defeat Mother Brain - The hole to ganon is open without having to defeat Agahnim in Ganon's Tower and Ganon can be defeat as soon you have the required crystals to make Ganon vulnerable. For keysanity, this mode also removes diff --git a/worlds/timespinner/docs/setup_de.md b/worlds/timespinner/docs/setup_de.md index 463568ecbd..e864747446 100644 --- a/worlds/timespinner/docs/setup_de.md +++ b/worlds/timespinner/docs/setup_de.md @@ -42,7 +42,7 @@ Weitere Informationen zum Randomizer findest du hier: [ReadMe](https://github.co ## Woher bekomme ich eine Konfigurationsdatei? -Die [Player Settings](https://archipelago.gg/games/Timespinner/player-settings) Seite auf der Website erlaubt dir, +Die [Player Options](https://archipelago.gg/games/Timespinner/player-options) Seite auf der Website erlaubt dir, persönliche Einstellungen zu definieren und diese in eine Konfigurationsdatei zu exportieren * Die Timespinner Randomizer Option "StinkyMaw" ist in Archipelago Seeds aktuell immer an diff --git a/worlds/witness/docs/setup_en.md b/worlds/witness/docs/setup_en.md index daa9b8b9b5..7b6d631198 100644 --- a/worlds/witness/docs/setup_en.md +++ b/worlds/witness/docs/setup_en.md @@ -43,4 +43,4 @@ The Witness has a fully functional map tracker that supports auto-tracking. 3. Click on the "AP" symbol at the top. 4. Enter the AP address, slot name and password. -The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It will hide checks & adjust logic accordingly. +The rest should take care of itself! Items and checks will be marked automatically, and it even knows your options - It will hide checks & adjust logic accordingly. diff --git a/worlds/yoshisisland/__init__.py b/worlds/yoshisisland/__init__.py index b5d7e137b5..f1aba3018b 100644 --- a/worlds/yoshisisland/__init__.py +++ b/worlds/yoshisisland/__init__.py @@ -32,8 +32,7 @@ class YoshisIslandWeb(WebWorld): setup_en = Tutorial( "Multiworld Setup Guide", - "A guide to setting up the Yoshi's Island randomizer" - "and connecting to an Archipelago server.", + "A guide to setting up the Yoshi's Island randomizer and connecting to an Archipelago server.", "English", "setup_en.md", "setup/en", diff --git a/worlds/yoshisisland/docs/en_Yoshi's Island.md b/worlds/yoshisisland/docs/en_Yoshi's Island.md index 8cd825cc7f..d6770c070b 100644 --- a/worlds/yoshisisland/docs/en_Yoshi's Island.md +++ b/worlds/yoshisisland/docs/en_Yoshi's Island.md @@ -1,6 +1,6 @@ # Yoshi's Island -## Where is the settings page? +## 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. diff --git a/worlds/yoshisisland/docs/setup_en.md b/worlds/yoshisisland/docs/setup_en.md index 30aadbfa60..4c8ffad704 100644 --- a/worlds/yoshisisland/docs/setup_en.md +++ b/worlds/yoshisisland/docs/setup_en.md @@ -39,7 +39,7 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Options page on the website allows you to configure your personal settings and export a config file from +The Player Options page on the website allows you to configure your personal options and export a config file from them. ### Verifying your config file